2018-02-07 10:37:15 +00:00
|
|
|
#include "OctoPrint.hpp"
|
|
|
|
|
2018-04-04 09:18:22 +00:00
|
|
|
#include <algorithm>
|
2018-05-23 10:47:39 +00:00
|
|
|
#include <boost/filesystem/path.hpp>
|
2018-02-07 10:37:15 +00:00
|
|
|
#include <boost/format.hpp>
|
2018-05-23 10:47:39 +00:00
|
|
|
#include <boost/log/trivial.hpp>
|
2018-02-07 10:37:15 +00:00
|
|
|
|
|
|
|
#include <wx/frame.h>
|
|
|
|
#include <wx/event.h>
|
2018-04-04 09:18:22 +00:00
|
|
|
#include <wx/progdlg.h>
|
2018-05-23 10:47:39 +00:00
|
|
|
#include <wx/sizer.h>
|
|
|
|
#include <wx/stattext.h>
|
|
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <wx/checkbox.h>
|
2018-02-07 10:37:15 +00:00
|
|
|
|
|
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
|
|
#include "slic3r/GUI/GUI.hpp"
|
2018-05-23 10:47:39 +00:00
|
|
|
#include "slic3r/GUI/MsgDialog.hpp"
|
2018-02-07 10:37:15 +00:00
|
|
|
#include "Http.hpp"
|
|
|
|
|
2018-05-23 10:47:39 +00:00
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
2018-02-07 10:37:15 +00:00
|
|
|
|
|
|
|
namespace Slic3r {
|
|
|
|
|
|
|
|
|
2018-05-23 10:47:39 +00:00
|
|
|
struct SendDialog : public GUI::MsgDialog
|
|
|
|
{
|
|
|
|
wxTextCtrl *txt_filename;
|
|
|
|
wxCheckBox *box_print;
|
|
|
|
|
|
|
|
SendDialog(const fs::path &path) :
|
|
|
|
MsgDialog(nullptr, _(L("Send G-Code to printer")), _(L("Upload to OctoPrint with the following filename:")), wxID_NONE),
|
|
|
|
txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().string())),
|
|
|
|
box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
|
|
|
|
{
|
|
|
|
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
|
|
|
|
label_dir_hint->Wrap(CONTENT_WIDTH);
|
|
|
|
|
|
|
|
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
|
|
|
content_sizer->Add(label_dir_hint);
|
|
|
|
content_sizer->AddSpacer(VERT_SPACING);
|
|
|
|
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
|
|
|
|
|
|
|
|
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
|
|
|
|
|
|
|
|
txt_filename->SetFocus();
|
|
|
|
txt_filename->SetSelection(0, path.stem().size());
|
|
|
|
|
|
|
|
Fit();
|
|
|
|
}
|
|
|
|
|
|
|
|
fs::path filename() const {
|
|
|
|
// The buffer object that utf8_str() returns may just point to data owned by the source string
|
|
|
|
// so we need to copy the string in any case to be on the safe side.
|
|
|
|
return fs::path(txt_filename->GetValue().utf8_str().data());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool print() const { return box_print->GetValue(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-02-07 10:37:15 +00:00
|
|
|
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
2018-02-20 14:37:26 +00:00
|
|
|
host(config->opt_string("octoprint_host")),
|
|
|
|
apikey(config->opt_string("octoprint_apikey")),
|
|
|
|
cafile(config->opt_string("octoprint_cafile"))
|
2018-02-07 10:37:15 +00:00
|
|
|
{}
|
|
|
|
|
2018-03-15 17:06:26 +00:00
|
|
|
bool OctoPrint::test(wxString &msg) const
|
2018-02-07 10:37:15 +00:00
|
|
|
{
|
2018-02-20 14:37:26 +00:00
|
|
|
// Since the request is performed synchronously here,
|
2018-03-15 17:06:26 +00:00
|
|
|
// it is ok to refer to `msg` from within the closure
|
2018-02-20 14:37:26 +00:00
|
|
|
|
2018-03-15 17:06:26 +00:00
|
|
|
bool res = true;
|
|
|
|
auto url = std::move(make_url("api/version"));
|
2018-05-23 10:47:39 +00:00
|
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url;
|
|
|
|
|
2018-03-15 17:06:26 +00:00
|
|
|
auto http = Http::get(std::move(url));
|
2018-02-20 14:37:26 +00:00
|
|
|
set_auth(http);
|
|
|
|
http.on_error([&](std::string, std::string error, unsigned status) {
|
2018-05-23 10:47:39 +00:00
|
|
|
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1% (HTTP %2%)") % error % status;
|
2018-03-15 17:06:26 +00:00
|
|
|
res = false;
|
|
|
|
msg = format_error(error, status);
|
2018-02-20 14:37:26 +00:00
|
|
|
})
|
2018-05-23 10:47:39 +00:00
|
|
|
.on_complete([&](std::string body, unsigned) {
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body;
|
|
|
|
})
|
2018-02-20 14:37:26 +00:00
|
|
|
.perform_sync();
|
|
|
|
|
|
|
|
return res;
|
2018-02-07 10:37:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-23 10:47:39 +00:00
|
|
|
bool OctoPrint::send_gcode(const std::string &filename) const
|
2018-02-07 10:37:15 +00:00
|
|
|
{
|
2018-04-04 09:18:22 +00:00
|
|
|
enum { PROGRESS_RANGE = 1000 };
|
|
|
|
|
|
|
|
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
|
2018-05-23 10:47:39 +00:00
|
|
|
fs::path filepath(filename);
|
|
|
|
|
|
|
|
SendDialog send_dialog(filepath.filename().string());
|
|
|
|
if (send_dialog.ShowModal() != wxID_OK) { return false; }
|
|
|
|
|
|
|
|
const bool print = send_dialog.print();
|
|
|
|
const auto upload_filepath = send_dialog.filename();
|
|
|
|
const auto upload_filename = upload_filepath.filename();
|
|
|
|
const auto upload_parent_path = upload_filepath.parent_path();
|
2018-04-04 09:18:22 +00:00
|
|
|
|
|
|
|
wxProgressDialog progress_dialog(
|
|
|
|
_(L("OctoPrint upload")),
|
|
|
|
_(L("Sending G-code file to the OctoPrint server...")),
|
|
|
|
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
|
|
|
|
progress_dialog.Pulse();
|
|
|
|
|
|
|
|
wxString test_msg;
|
|
|
|
if (!test(test_msg)) {
|
|
|
|
auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
|
|
|
|
GUI::show_error(&progress_dialog, std::move(errormsg));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool res = true;
|
|
|
|
|
2018-05-23 10:47:39 +00:00
|
|
|
auto url = make_url("api/files/local");
|
|
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
|
|
|
|
% filepath
|
|
|
|
% url
|
|
|
|
% upload_filename
|
|
|
|
% upload_parent_path
|
|
|
|
% print;
|
|
|
|
|
|
|
|
auto http = Http::post(std::move(url));
|
2018-02-20 14:37:26 +00:00
|
|
|
set_auth(http);
|
|
|
|
http.form_add("print", print ? "true" : "false")
|
2018-05-23 10:47:39 +00:00
|
|
|
.form_add("path", upload_parent_path.string())
|
|
|
|
.form_add_file("file", filename, upload_filename.string())
|
2018-04-04 09:18:22 +00:00
|
|
|
.on_complete([&](std::string body, unsigned status) {
|
2018-05-23 10:47:39 +00:00
|
|
|
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
|
2018-04-04 09:18:22 +00:00
|
|
|
progress_dialog.Update(PROGRESS_RANGE);
|
|
|
|
})
|
|
|
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
2018-05-23 10:47:39 +00:00
|
|
|
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1% (HTTP %2%)") % error % status;
|
2018-04-04 09:18:22 +00:00
|
|
|
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(error, status));
|
|
|
|
GUI::show_error(&progress_dialog, std::move(errormsg));
|
|
|
|
res = false;
|
2018-02-20 14:37:26 +00:00
|
|
|
})
|
2018-04-04 09:18:22 +00:00
|
|
|
.on_progress([&](Http::Progress progress, bool &cancel) {
|
|
|
|
if (cancel) {
|
|
|
|
// Upload was canceled
|
|
|
|
res = false;
|
|
|
|
} else if (progress.ultotal > 0) {
|
|
|
|
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
|
|
|
|
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
|
|
|
|
} else {
|
|
|
|
cancel = !progress_dialog.Pulse();
|
|
|
|
}
|
2018-02-20 14:37:26 +00:00
|
|
|
})
|
2018-04-04 09:18:22 +00:00
|
|
|
.perform_sync();
|
|
|
|
|
|
|
|
return res;
|
2018-02-07 10:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void OctoPrint::set_auth(Http &http) const
|
|
|
|
{
|
2018-02-20 14:37:26 +00:00
|
|
|
http.header("X-Api-Key", apikey);
|
2018-02-07 10:37:15 +00:00
|
|
|
|
2018-02-20 14:37:26 +00:00
|
|
|
if (! cafile.empty()) {
|
|
|
|
http.ca_file(cafile);
|
|
|
|
}
|
2018-02-07 10:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string OctoPrint::make_url(const std::string &path) const
|
|
|
|
{
|
2018-02-20 14:37:26 +00:00
|
|
|
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
|
|
|
if (host.back() == '/') {
|
|
|
|
return std::move((boost::format("%1%%2%") % host % path).str());
|
|
|
|
} else {
|
|
|
|
return std::move((boost::format("%1%/%2%") % host % path).str());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return std::move((boost::format("http://%1%/%2%") % host % path).str());
|
|
|
|
}
|
2018-02-07 10:37:15 +00:00
|
|
|
}
|
|
|
|
|
2018-03-15 17:06:26 +00:00
|
|
|
wxString OctoPrint::format_error(std::string error, unsigned status)
|
2018-02-07 10:37:15 +00:00
|
|
|
{
|
2018-03-15 17:06:26 +00:00
|
|
|
const wxString wxerror = error;
|
2018-02-20 14:37:26 +00:00
|
|
|
|
2018-03-15 17:06:26 +00:00
|
|
|
if (status != 0) {
|
|
|
|
return wxString::Format("HTTP %u: %s", status,
|
|
|
|
(status == 401 ? _(L("Invalid API key")) : wxerror));
|
2018-02-20 14:37:26 +00:00
|
|
|
} else {
|
2018-03-15 17:06:26 +00:00
|
|
|
return std::move(wxerror);
|
2018-02-20 14:37:26 +00:00
|
|
|
}
|
2018-02-07 10:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|