diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 4536dd217..cc6b0ebd9 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -36,12 +36,10 @@ const char* Duet::get_name() const { return "Duet"; } bool Duet::test(wxString &msg) const { - bool connected = connect(msg); - if (connected) { - disconnect(); - } + auto connectionType = connect(msg); + disconnect(connectionType); - return connected; + return connectionType != Duet::ConnectionType::ERROR; } wxString Duet::get_test_ok_msg () const @@ -59,33 +57,39 @@ wxString Duet::get_test_failed_msg (wxString &msg) const bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const { wxString connect_msg; - if (!connect(connect_msg)) { + auto connectionType = connect(connect_msg); + if (connectionType == Duet::ConnectionType::ERROR) { error_fn(std::move(connect_msg)); return false; } bool res = true; + bool dsf = (connectionType == Duet::ConnectionType::DSF); - auto upload_cmd = get_upload_url(upload_data.upload_path.string()); + auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType); BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") % upload_data.source_path % upload_data.upload_path % upload_data.start_print % upload_cmd; - auto http = Http::post(std::move(upload_cmd)); - http.set_post_body(upload_data.source_path) - .on_complete([&](std::string body, unsigned status) { + auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd))); + if (dsf) { + http.set_put_body(upload_data.source_path); + } else { + http.set_post_body(upload_data.source_path); + } + http.on_complete([&](std::string body, unsigned status) { BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body; - int err_code = get_err_code_from_body(body); + int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body); if (err_code != 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Request completed but error code was received: %1%") % err_code; error_fn(format_error(body, L("Unknown error occured"), 0)); res = false; } else if (upload_data.start_print) { wxString errormsg; - res = start_print(errormsg, upload_data.upload_path.string()); + res = start_print(errormsg, upload_data.upload_path.string(), connectionType); if (! res) { error_fn(std::move(errormsg)); } @@ -106,20 +110,28 @@ bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn e }) .perform_sync(); - disconnect(); + disconnect(connectionType); return res; } -bool Duet::connect(wxString &msg) const +Duet::ConnectionType Duet::connect(wxString &msg) const { - bool res = false; - auto url = get_connect_url(); + auto res = Duet::ConnectionType::ERROR; + auto url = get_connect_url(false); auto http = Http::get(std::move(url)); http.on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; - msg = format_error(body, error, status); + auto dsfUrl = get_connect_url(true); + auto dsfHttp = Http::get(std::move(dsfUrl)); + dsfHttp.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; + msg = format_error(body, error, status); + }) + .on_complete([&](std::string body, unsigned) { + res = Duet::ConnectionType::DSF; + }) + .perform_sync(); }) .on_complete([&](std::string body, unsigned) { BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body; @@ -127,7 +139,7 @@ bool Duet::connect(wxString &msg) const int err_code = get_err_code_from_body(body); switch (err_code) { case 0: - res = true; + res = Duet::ConnectionType::RRF; break; case 1: msg = format_error(body, L("Wrong password"), 0); @@ -146,8 +158,12 @@ bool Duet::connect(wxString &msg) const return res; } -void Duet::disconnect() const +void Duet::disconnect(Duet::ConnectionType connectionType) const { + // we don't need to disconnect from DSF or if it failed anyway + if (connectionType != Duet::ConnectionType::RRF) { + return; + } auto url = (boost::format("%1%rr_disconnect") % get_base_url()).str(); @@ -159,20 +175,31 @@ void Duet::disconnect() const .perform_sync(); } -std::string Duet::get_upload_url(const std::string &filename) const +std::string Duet::get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const { - return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") - % get_base_url() - % Http::url_encode(filename) - % timestamp_str()).str(); + if (connectionType == Duet::ConnectionType::DSF) { + return (boost::format("%1%machine/file/gcodes/%2%") + % get_base_url() + % Http::url_encode(filename)).str(); + } else { + return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") + % get_base_url() + % Http::url_encode(filename) + % timestamp_str()).str(); + } } -std::string Duet::get_connect_url() const +std::string Duet::get_connect_url(const bool dsfUrl) const { - return (boost::format("%1%rr_connect?password=%2%&%3%") - % get_base_url() - % (password.empty() ? "reprap" : password) - % timestamp_str()).str(); + if (dsfUrl) { + return (boost::format("%1%machine/status") + % get_base_url()).str(); + } else { + return (boost::format("%1%rr_connect?password=%2%&%3%") + % get_base_url() + % (password.empty() ? "reprap" : password) + % timestamp_str()).str(); + } } std::string Duet::get_base_url() const @@ -201,15 +228,25 @@ std::string Duet::timestamp_str() const return std::string(buffer); } -bool Duet::start_print(wxString &msg, const std::string &filename) const +bool Duet::start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const { bool res = false; + bool dsf = (connectionType == Duet::ConnectionType::DSF); - auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"") + auto url = dsf + ? (boost::format("%1%machine/code") + % get_base_url()).str() + : (boost::format("%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"") % get_base_url() % Http::url_encode(filename)).str(); - auto http = Http::get(std::move(url)); + auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url))); + if (dsf) { + http.set_post_body( + (boost::format("M32 \"0:/gcodes/%1%\"") + % filename).str() + ); + } http.on_error([&](std::string body, std::string error, unsigned status) { BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body; msg = format_error(body, error, status); diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index 702efbddb..41c6b1d75 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -29,16 +29,17 @@ public: std::string get_host() const override { return host; } private: + enum ConnectionType { RRF, DSF, ERROR }; std::string host; std::string password; - std::string get_upload_url(const std::string &filename) const; - std::string get_connect_url() const; + std::string get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const; + std::string get_connect_url(const bool dsfUrl) const; std::string get_base_url() const; std::string timestamp_str() const; - bool connect(wxString &msg) const; - void disconnect() const; - bool start_print(wxString &msg, const std::string &filename) const; + Duet::ConnectionType connect(wxString &msg) const; + void disconnect(Duet::ConnectionType connectionType) const; + bool start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const; int get_err_code_from_body(const std::string &body) const; }; diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index a16aac5b5..3360f6eff 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -35,11 +35,11 @@ struct CurlGlobalInit { static std::unique_ptr instance; std::string message; - + CurlGlobalInit() { #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON - + // Look for a set of distro specific directories. Don't change the // order: https://bugzilla.redhat.com/show_bug.cgi?id=1053882 static const char * CA_BUNDLES[] = { @@ -48,17 +48,17 @@ struct CurlGlobalInit "/usr/share/ssl/certs/ca-bundle.crt", "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD "/etc/ssl/cert.pem", - "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed + "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed }; - + namespace fs = boost::filesystem; // Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally) const char *const SSL_CA_FILE = X509_get_default_cert_file_env(); const char * ssl_cafile = ::getenv(SSL_CA_FILE); - + if (!ssl_cafile) ssl_cafile = X509_get_default_cert_file(); - + int replace = true; if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) { const char * bundle = nullptr; @@ -86,15 +86,15 @@ struct CurlGlobalInit } #endif // OPENSSL_CERT_OVERRIDE - + if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) { message += _u8L("CURL init has failed. PrusaSlicer will be unable to establish " "network connections. See logs for additional details."); - + BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec); } } - + ~CurlGlobalInit() { ::curl_global_cleanup(); } }; @@ -120,6 +120,7 @@ struct Http::priv std::string error_buffer; // Used for CURLOPT_ERRORBUFFER size_t limit; bool cancel; + fs::ifstream* putFile; std::thread io_thread; Http::CompleteFn completefn; @@ -138,6 +139,8 @@ struct Http::priv void set_timeout_connect(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); + void set_post_body(const std::string body); + void set_put_body(const fs::path &path); std::string curl_error(CURLcode curlcode); std::string body_size_error(); @@ -152,9 +155,10 @@ Http::priv::priv(const std::string &url) , error_buffer(CURL_ERROR_SIZE + 1, '\0') , limit(0) , cancel(false) + , putFile(nullptr) { Http::tls_global_init(); - + if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); } @@ -284,6 +288,22 @@ void Http::priv::set_post_body(const fs::path &path) postfields = file_content; } +void Http::priv::set_post_body(const std::string body) +{ + postfields = body; +} + +void Http::priv::set_put_body(const fs::path &path) +{ + boost::system::error_code ec; + boost::uintmax_t filesize = file_size(path, ec); + if (!ec) { + putFile = new fs::ifstream(path); + ::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile)); + ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize); + } +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1%:\n%2%\n[Error %3%]") @@ -335,6 +355,11 @@ void Http::priv::http_perform() CURLcode res = ::curl_easy_perform(curl); + if (putFile != nullptr) { + delete putFile; + putFile = nullptr; + } + if (res != CURLE_OK) { if (res == CURLE_ABORTED_BY_CALLBACK) { if (cancel) { @@ -455,6 +480,18 @@ Http& Http::set_post_body(const fs::path &path) return *this; } +Http& Http::set_post_body(const std::string body) +{ + if (p) { p->set_post_body(body); } + return *this; +} + +Http& Http::set_put_body(const fs::path &path) +{ + if (p) { p->set_put_body(path);} + return *this; +} + Http& Http::on_complete(CompleteFn fn) { if (p) { p->completefn = std::move(fn); } @@ -509,6 +546,13 @@ Http Http::post(std::string url) return http; } +Http Http::put(std::string url) +{ + Http http{std::move(url)}; + curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 1L); + return http; +} + bool Http::ca_file_supported() { ::CURL *curl = ::curl_easy_init(); @@ -521,7 +565,7 @@ std::string Http::tls_global_init() { if (!CurlGlobalInit::instance) CurlGlobalInit::instance = std::make_unique(); - + return CurlGlobalInit::instance->message; } @@ -532,7 +576,7 @@ std::string Http::tls_system_cert_store() #ifdef OPENSSL_CERT_OVERRIDE ret = ::getenv(X509_get_default_cert_file_env()); #endif - + return ret; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index f16236279..1ee47606e 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -49,6 +49,7 @@ public: // for a GET and a POST request respectively. static Http get(std::string url); static Http post(std::string url); + static Http put(std::string url); ~Http(); Http(const Http &) = delete; @@ -80,6 +81,16 @@ public: // This can be used for hosts which do not support multipart requests. Http& set_post_body(const boost::filesystem::path &path); + // Set the POST request body. + // The data is used verbatim, it is not additionally encoded in any way. + // This can be used for hosts which do not support multipart requests. + Http& set_post_body(const std::string body); + + // Set the file contents as a PUT request body. + // The data is used verbatim, it is not additionally encoded in any way. + // This can be used for hosts which do not support multipart requests. + Http& set_put_body(const boost::filesystem::path &path); + // Callback called on HTTP request complete Http& on_complete(CompleteFn fn); // Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup, @@ -100,7 +111,7 @@ public: // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); - + // Return empty string on success or error message on fail. static std::string tls_global_init(); static std::string tls_system_cert_store();