From 9ee10a877938e371b9200601c53b69b78886d015 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 12 Jun 2018 12:32:03 +0200 Subject: [PATCH] Octoprint: Fix unicode support --- xs/src/slic3r/Utils/Http.cpp | 72 +++++++++++++++++++++++-------- xs/src/slic3r/Utils/Http.hpp | 9 ++-- xs/src/slic3r/Utils/OctoPrint.cpp | 31 +++++++------ xs/src/slic3r/Utils/OctoPrint.hpp | 3 +- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index adc8ac816..7c4126633 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -3,13 +3,16 @@ #include #include #include -#include +#include +#include #include #include #include "../../libslic3r/libslic3r.h" +namespace fs = boost::filesystem; + namespace Slic3r { @@ -34,7 +37,11 @@ struct Http::priv ::curl_httppost *form; ::curl_httppost *form_end; ::curl_slist *headerlist; + // Used for reading the body std::string buffer; + // Used for storing file streams added as multipart form parts + // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion + std::deque form_files; size_t limit; bool cancel; @@ -50,8 +57,9 @@ struct Http::priv static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); + static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp); - void form_add_file(const char *name, const char *path, const char* filename); + void form_add_file(const char *name, const fs::path &path, const char* filename); std::string curl_error(CURLcode curlcode); std::string body_size_error(); @@ -138,21 +146,42 @@ int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double return xfercb(userp, dltotal, dlnow, ultotal, ulnow); } -void Http::priv::form_add_file(const char *name, const char *path, const char* filename) +size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp) { + auto stream = reinterpret_cast(userp); + + try { + stream->read(buffer, size * nitems); + } catch (...) { + return CURL_READFUNC_ABORT; + } + + return stream->gcount(); +} + +void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename) +{ + // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows + // and so we use CURLFORM_STREAM with boost ifstream to read the file. + + if (filename == nullptr) { + filename = path.string().c_str(); + } + + fs::ifstream stream(path, std::ios::in | std::ios::binary); + stream.seekg(0, std::ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + form_files.push_back(std::move(stream)); + auto stream_ptr = &form_files.back(); + if (filename != nullptr) { ::curl_formadd(&form, &form_end, CURLFORM_COPYNAME, name, - CURLFORM_FILE, path, CURLFORM_FILENAME, filename, CURLFORM_CONTENTTYPE, "application/octet-stream", - CURLFORM_END - ); - } else { - ::curl_formadd(&form, &form_end, - CURLFORM_COPYNAME, name, - CURLFORM_FILE, path, - CURLFORM_CONTENTTYPE, "application/octet-stream", + CURLFORM_STREAM, static_cast(stream_ptr), + CURLFORM_CONTENTSLENGTH, static_cast(size), CURLFORM_END ); } @@ -177,6 +206,7 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(this)); + ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb); ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32 @@ -206,14 +236,20 @@ void Http::priv::http_perform() if (res != CURLE_OK) { if (res == CURLE_ABORTED_BY_CALLBACK) { - Progress dummyprogress(0, 0, 0, 0); - bool cancel = true; - if (progressfn) { progressfn(dummyprogress, cancel); } + if (cancel) { + // The abort comes from the request being cancelled programatically + Progress dummyprogress(0, 0, 0, 0); + bool cancel = true; + if (progressfn) { progressfn(dummyprogress, cancel); } + } else { + // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed + if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", http_status); } + } } else if (res == CURLE_WRITE_ERROR) { - if (errorfn) { errorfn(std::move(buffer), std::move(body_size_error()), http_status); } + if (errorfn) { errorfn(std::move(buffer), body_size_error(), http_status); } } else { - if (errorfn) { errorfn(std::move(buffer), std::move(curl_error(res)), http_status); } + if (errorfn) { errorfn(std::move(buffer), curl_error(res), http_status); } }; } else { if (completefn) { @@ -288,13 +324,13 @@ Http& Http::form_add(const std::string &name, const std::string &contents) return *this; } -Http& Http::form_add_file(const std::string &name, const std::string &path) +Http& Http::form_add_file(const std::string &name, const fs::path &path) { if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); } return *this; } -Http& Http::form_add_file(const std::string &name, const std::string &path, const std::string &filename) +Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename) { if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); } return *this; diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp index 2b8126334..73656bf88 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Slic3r { @@ -33,6 +34,8 @@ public: Http(Http &&other); + // Note: strings are expected to be UTF-8-encoded + // These are the primary constructors that create a HTTP object // for a GET and a POST request respectively. static Http get(std::string url); @@ -55,10 +58,10 @@ public: Http& ca_file(const std::string &filename); // Add a HTTP multipart form field Http& form_add(const std::string &name, const std::string &contents); - // Add a HTTP multipart form file data contents - Http& form_add_file(const std::string &name, const std::string &path); + // Add a HTTP multipart form file data contents, `name` is the name of the part + Http& form_add_file(const std::string &name, const boost::filesystem::path &path); // Same as above except also override the file's filename with a custom one - Http& form_add_file(const std::string &name, const std::string &path, const std::string &filename); + Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename); // Callback called on HTTP request complete Http& on_complete(CompleteFn fn); diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index 1794c6024..86049de16 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -31,7 +31,7 @@ struct SendDialog : public GUI::MsgDialog 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())), + txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())), 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."))); @@ -45,15 +45,14 @@ struct SendDialog : public GUI::MsgDialog btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); txt_filename->SetFocus(); - txt_filename->SetSelection(0, path.stem().size()); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); 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()); + return fs::path(txt_filename->GetValue().wx_str()); } bool print() const { return box_print->GetValue(); } @@ -73,7 +72,7 @@ bool OctoPrint::test(wxString &msg) const // it is ok to refer to `msg` from within the closure bool res = true; - auto url = std::move(make_url("api/version")); + auto url = make_url("api/version"); BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; @@ -99,7 +98,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the OctoPrint server")); fs::path filepath(filename); - SendDialog send_dialog(filepath.filename().string()); + SendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } const bool print = send_dialog.print(); @@ -125,10 +124,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const 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 + % filepath.string() % url - % upload_filename - % upload_parent_path + % upload_filename.string() + % upload_parent_path.string() % print; auto http = Http::post(std::move(url)); @@ -175,24 +174,24 @@ std::string OctoPrint::make_url(const std::string &path) const { if (host.find("http://") == 0 || host.find("https://") == 0) { if (host.back() == '/') { - return std::move((boost::format("%1%%2%") % host % path).str()); + return (boost::format("%1%%2%") % host % path).str(); } else { - return std::move((boost::format("%1%/%2%") % host % path).str()); + return (boost::format("%1%/%2%") % host % path).str(); } } else { - return std::move((boost::format("http://%1%/%2%") % host % path).str()); + return (boost::format("http://%1%/%2%") % host % path).str(); } } -wxString OctoPrint::format_error(std::string error, unsigned status) +wxString OctoPrint::format_error(const std::string &error, unsigned status) { - const wxString wxerror = error; + auto wxerror = wxString::FromUTF8(error.data()); if (status != 0) { return wxString::Format("HTTP %u: %s", status, (status == 401 ? _(L("Invalid API key")) : wxerror)); } else { - return std::move(wxerror); + return wxerror; } } diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index e278c8141..a8599faa9 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,6 +17,7 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) const; + // Send gcode file to octoprint, filename is expected to be in UTF-8 bool send_gcode(const std::string &filename) const; private: std::string host; @@ -25,7 +26,7 @@ private: void set_auth(Http &http) const; std::string make_url(const std::string &path) const; - static wxString format_error(std::string error, unsigned status); + static wxString format_error(const std::string &error, unsigned status); };