Merge branch 'dk_mainsail'
This commit is contained in:
commit
055b0c1e52
@ -44,7 +44,7 @@ enum class MachineLimitsUsage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum PrintHostType {
|
enum PrintHostType {
|
||||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htMainSail
|
htPrusaLink, htPrusaConnect, htOctoPrint, htMainSail, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AuthorizationType {
|
enum AuthorizationType {
|
||||||
|
@ -250,6 +250,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
Utils/Http.hpp
|
Utils/Http.hpp
|
||||||
Utils/FixModelByWin10.cpp
|
Utils/FixModelByWin10.cpp
|
||||||
Utils/FixModelByWin10.hpp
|
Utils/FixModelByWin10.hpp
|
||||||
|
Utils/Mainsail.cpp
|
||||||
|
Utils/Mainsail.hpp
|
||||||
Utils/OctoPrint.cpp
|
Utils/OctoPrint.cpp
|
||||||
Utils/OctoPrint.hpp
|
Utils/OctoPrint.hpp
|
||||||
Utils/Duet.cpp
|
Utils/Duet.cpp
|
||||||
|
257
src/slic3r/Utils/Mainsail.cpp
Normal file
257
src/slic3r/Utils/Mainsail.cpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#include "Mainsail.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <exception>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/nowide/convert.hpp>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/format.hpp"
|
||||||
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "Http.hpp"
|
||||||
|
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#ifdef WIN32
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||||
|
{
|
||||||
|
// put ipv6 into [] brackets
|
||||||
|
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||||
|
sub_addr = "[" + sub_addr + "]";
|
||||||
|
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||||
|
// If anything fails, return the input unchanged.
|
||||||
|
std::string out = orig_addr;
|
||||||
|
CURLU* hurl = curl_url();
|
||||||
|
if (hurl) {
|
||||||
|
// Parse the input URL.
|
||||||
|
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
// Replace the address.
|
||||||
|
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
// Extract a string fromt the CURL URL handle.
|
||||||
|
char* url;
|
||||||
|
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||||
|
if (rc == CURLUE_OK) {
|
||||||
|
out = url;
|
||||||
|
curl_free(url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr;
|
||||||
|
curl_url_cleanup(hurl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
Mainsail::Mainsail(DynamicPrintConfig *config) :
|
||||||
|
m_host(config->opt_string("print_host")),
|
||||||
|
m_apikey(config->opt_string("printhost_apikey")),
|
||||||
|
m_cafile(config->opt_string("printhost_cafile")),
|
||||||
|
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char* Mainsail::get_name() const { return "Mainsail"; }
|
||||||
|
|
||||||
|
wxString Mainsail::get_test_ok_msg () const
|
||||||
|
{
|
||||||
|
return _(L("Connection to Mainsail works correctly."));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString Mainsail::get_test_failed_msg (wxString &msg) const
|
||||||
|
{
|
||||||
|
return GUI::format_wxstr("%s: %s"
|
||||||
|
, _L("Could not connect to Mainsail")
|
||||||
|
, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mainsail::test(wxString& msg) const
|
||||||
|
{
|
||||||
|
// GET /server/info
|
||||||
|
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("server/info");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got server/info: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// All successful HTTP requests will return a json encoded object in the form of :
|
||||||
|
// {result: <response data>}
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
if (ptree.front().first != "result") {
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ptree.front().second.get_optional<std::string>("moonraker_version")) {
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got version: %2%") % name % ptree.front().second.get_optional<std::string>("moonraker_version");
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef _WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.on_ip_resolve([&](std::string address) {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Remember resolved address to be reused at successive REST API call.
|
||||||
|
msg = GUI::from_u8(address);
|
||||||
|
})
|
||||||
|
#endif // _WIN32
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mainsail::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
|
{
|
||||||
|
// POST /server/files/upload
|
||||||
|
|
||||||
|
const char* name = get_name();
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
|
||||||
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
|
wxString test_msg_or_host_ip;
|
||||||
|
if (!test(test_msg_or_host_ip)) {
|
||||||
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
bool res = true;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
|
||||||
|
#endif // _WIN32
|
||||||
|
{
|
||||||
|
// If https is entered we assume signed ceritificate is being used
|
||||||
|
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||||
|
url = make_url("server/files/upload");
|
||||||
|
}
|
||||||
|
#ifdef WIN32
|
||||||
|
else {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||||
|
// If it got the address use it instead of the stored in "host" variable.
|
||||||
|
// This new address returns in "test_msg_or_host_ip" variable.
|
||||||
|
// Solves troubles of uploades failing with name address.
|
||||||
|
// in original address (m_host) replace host for resolved ip
|
||||||
|
info_fn(L"resolve", test_msg_or_host_ip);
|
||||||
|
url = substitute_host(make_url("server/files/upload"), GUI::into_u8(test_msg_or_host_ip));
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||||
|
/*
|
||||||
|
The file must be uploaded in the request's body multipart/form-data (ie: <input type="file">). The following arguments may also be added to the form-data:
|
||||||
|
root: The root location in which to upload the file.Currently this may be gcodes or config.If not specified the default is gcodes.
|
||||||
|
path : This argument may contain a path(relative to the root) indicating a subdirectory to which the file is written.If a path is present the server will attempt to create any subdirectories that do not exist.
|
||||||
|
checksum : A SHA256 hex digest calculated by the client for the uploaded file.If this argument is supplied the server will compare it to its own checksum calculation after the upload has completed.A checksum mismatch will result in a 422 error.
|
||||||
|
Arguments available only for the gcodes root :
|
||||||
|
print: If set to "true", Klippy will attempt to start the print after uploading.Note that this value should be a string type, not boolean.This provides compatibility with OctoPrint's upload API.
|
||||||
|
*/
|
||||||
|
auto http = Http::post(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
|
||||||
|
http.form_add("root", "gcodes");
|
||||||
|
if (!upload_parent_path.empty())
|
||||||
|
http.form_add("path", upload_parent_path.string());
|
||||||
|
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
|
||||||
|
http.form_add("print", "true");
|
||||||
|
|
||||||
|
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||||
|
prorgess_fn(std::move(progress), cancel);
|
||||||
|
if (cancel) {
|
||||||
|
// Upload was canceled
|
||||||
|
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
#endif
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mainsail::set_auth(Http &http) const
|
||||||
|
{
|
||||||
|
if (!m_apikey.empty())
|
||||||
|
http.header("X-Api-Key", m_apikey);
|
||||||
|
if (!m_cafile.empty())
|
||||||
|
http.ca_file(m_cafile);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Mainsail::make_url(const std::string &path) const
|
||||||
|
{
|
||||||
|
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||||
|
if (m_host.back() == '/') {
|
||||||
|
return (boost::format("%1%%2%") % m_host % path).str();
|
||||||
|
} else {
|
||||||
|
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (boost::format("http://%1%/%2%") % m_host % path).str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
64
src/slic3r/Utils/Mainsail.hpp
Normal file
64
src/slic3r/Utils/Mainsail.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#ifndef slic3r_Mainsail_hpp_
|
||||||
|
#define slic3r_Mainsail_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wx/string.h>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/asio/ip/address.hpp>
|
||||||
|
|
||||||
|
#include "PrintHost.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class DynamicPrintConfig;
|
||||||
|
class Http;
|
||||||
|
|
||||||
|
// https://moonraker.readthedocs.io/en/latest/web_api
|
||||||
|
class Mainsail : public PrintHost
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Mainsail(DynamicPrintConfig *config);
|
||||||
|
~Mainsail() override = default;
|
||||||
|
|
||||||
|
const char* get_name() const override;
|
||||||
|
|
||||||
|
virtual bool test(wxString &curl_msg) const override;
|
||||||
|
wxString get_test_ok_msg () const override;
|
||||||
|
wxString get_test_failed_msg (wxString &msg) const override;
|
||||||
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
|
bool has_auto_discovery() const override { return true; }
|
||||||
|
bool can_test() const override { return true; }
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
std::string get_host() const override { return m_host; }
|
||||||
|
const std::string& get_apikey() const { return m_apikey; }
|
||||||
|
const std::string& get_cafile() const { return m_cafile; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*
|
||||||
|
#ifdef WIN32
|
||||||
|
virtual bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const;
|
||||||
|
#endif
|
||||||
|
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||||
|
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||||
|
*/
|
||||||
|
std::string m_host;
|
||||||
|
std::string m_apikey;
|
||||||
|
std::string m_cafile;
|
||||||
|
bool m_ssl_revoke_best_effort;
|
||||||
|
|
||||||
|
virtual void set_auth(Http &http) const;
|
||||||
|
std::string make_url(const std::string &path) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*
|
||||||
|
#ifdef WIN32
|
||||||
|
bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||||
|
#endif
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -203,7 +203,7 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const
|
|||||||
const auto text = ptree.get_optional<std::string>("text");
|
const auto text = ptree.get_optional<std::string>("text");
|
||||||
res = validate_version_text(text);
|
res = validate_version_text(text);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint"));
|
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception&) {
|
catch (const std::exception&) {
|
||||||
@ -252,7 +252,7 @@ bool OctoPrint::test(wxString& msg) const
|
|||||||
const auto text = ptree.get_optional<std::string>("text");
|
const auto text = ptree.get_optional<std::string>("text");
|
||||||
res = validate_version_text(text);
|
res = validate_version_text(text);
|
||||||
if (! res) {
|
if (! res) {
|
||||||
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint"));
|
msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &) {
|
catch (const std::exception &) {
|
||||||
@ -396,7 +396,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
|
|||||||
prorgess_fn(std::move(progress), cancel);
|
prorgess_fn(std::move(progress), cancel);
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
// Upload was canceled
|
// Upload was canceled
|
||||||
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
|
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -473,7 +473,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
|
|||||||
prorgess_fn(std::move(progress), cancel);
|
prorgess_fn(std::move(progress), cancel);
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
// Upload was canceled
|
// Upload was canceled
|
||||||
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
|
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||||
res = false;
|
res = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1126,5 +1126,4 @@ wxString PrusaConnect::get_test_failed_msg(wxString& msg) const
|
|||||||
{
|
{
|
||||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa Connect"), msg);
|
return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa Connect"), msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -117,16 +117,6 @@ protected:
|
|||||||
void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
|
void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Mainsail : public OctoPrint
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Mainsail(DynamicPrintConfig* config) : OctoPrint(config) {}
|
|
||||||
~Mainsail() override = default;
|
|
||||||
|
|
||||||
const char* get_name() const override { return "Mainsail/Fluidd"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class SL1Host : public PrusaLink
|
class SL1Host : public PrusaLink
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "AstroBox.hpp"
|
#include "AstroBox.hpp"
|
||||||
#include "Repetier.hpp"
|
#include "Repetier.hpp"
|
||||||
#include "MKS.hpp"
|
#include "MKS.hpp"
|
||||||
|
#include "Mainsail.hpp"
|
||||||
#include "../GUI/PrintHostDialogs.hpp"
|
#include "../GUI/PrintHostDialogs.hpp"
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
|
Loading…
Reference in New Issue
Block a user