NEW: support send gcode to third-party printer

Thanks SoftForever for your works to support
sending a gcode file to third-party printer

Change-Id: I3cba43c8bd878f1f1c2fd5fae202ed4d922e8727
Signed-off-by: Stone Li <stone.li@bambulab.com>
This commit is contained in:
Stone Li 2022-09-19 09:18:48 +08:00 committed by Lane.Wei
parent 52cdf4930d
commit 91d5ba2870
29 changed files with 3067 additions and 40 deletions

View file

@ -0,0 +1,367 @@
#include "OctoPrint.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/algorithm/string/predicate.hpp>
#include <curl/curl.h>
#include <wx/progdlg.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "Http.hpp"
#include "libslic3r/AppConfig.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
#ifdef WIN32
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
namespace {
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 + "]";
#if 0
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
std::string final_addr = orig_addr;
// http
size_t double_dash = orig_addr.find("//");
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
// userinfo
size_t at = orig_addr.find("@");
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
// or it will be ']' if address is ipv6 )
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
// if there are more ':' it must be ipv6
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
size_t ipv6_end = orig_addr.find(']', host_start);
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
}
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
// now host_start and host_end should mark where to put resolved addr
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
if (host_start >= orig_addr.length()) {
return final_addr;
}
final_addr.replace(host_start, host_end - host_start, sub_addr);
return final_addr;
#else
// 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
}
} //namespace
#endif // WIN32
OctoPrint::OctoPrint(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* OctoPrint::get_name() const { return "OctoPrint"; }
bool OctoPrint::test(wxString &msg) const
{
// 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("api/version");
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 version: %2%") % name % body;
try {
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
if (! ptree.get_optional<std::string>("api")) {
res = false;
return;
}
const auto text = ptree.get_optional<std::string>("text");
res = validate_version_text(text);
if (! res) {
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str());
}
}
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;
}
wxString OctoPrint::get_test_ok_msg () const
{
return _(L("Connection to OctoPrint works correctly."));
}
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
{
return GUI::from_u8((boost::format("%s: %s\n\n%s")
% _utf8(L("Could not connect to OctoPrint"))
% std::string(msg.ToUTF8())
% _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str());
}
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
{
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.
// Otherwise on Windows it contains the resolved IP address of the host.
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())
#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("api/files/local");
}
#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
url = substitute_host(make_url("api/files/local"), 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");
auto http = Http::post(std::move(url));
set_auth(http);
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
.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) << "Octoprint: Upload canceled";
res = false;
}
})
#ifdef WIN32
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
#endif
.perform_sync();
return res;
}
bool OctoPrint::validate_version_text(const boost::optional<std::string> &version_text) const
{
return version_text ? boost::starts_with(*version_text, "OctoPrint") : true;
}
void OctoPrint::set_auth(Http &http) const
{
http.header("X-Api-Key", m_apikey);
if (!m_cafile.empty()) {
http.ca_file(m_cafile);
}
}
std::string OctoPrint::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();
}
}
SL1Host::SL1Host(DynamicPrintConfig *config) :
OctoPrint(config),
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
m_username(config->opt_string("printhost_user")),
m_password(config->opt_string("printhost_password"))
{
}
// SL1Host
const char* SL1Host::get_name() const { return "SL1Host"; }
wxString SL1Host::get_test_ok_msg () const
{
return _(L("Connection to Prusa SL1 / SL1S works correctly."));
}
wxString SL1Host::get_test_failed_msg (wxString &msg) const
{
return GUI::from_u8((boost::format("%s: %s")
% _utf8(L("Could not connect to Prusa SLA"))
% std::string(msg.ToUTF8())).str());
}
bool SL1Host::validate_version_text(const boost::optional<std::string> &version_text) const
{
return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false;
}
void SL1Host::set_auth(Http &http) const
{
switch (m_authorization_type) {
case atKeyPassword:
http.header("X-Api-Key", get_apikey());
break;
case atUserPassword:
http.auth_digest(m_username, m_password);
break;
}
if (! get_cafile().empty()) {
http.ca_file(get_cafile());
}
}
// PrusaLink
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
OctoPrint(config),
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
m_username(config->opt_string("printhost_user")),
m_password(config->opt_string("printhost_password"))
{
}
const char* PrusaLink::get_name() const { return "PrusaLink"; }
wxString PrusaLink::get_test_ok_msg() const
{
return _(L("Connection to PrusaLink works correctly."));
}
wxString PrusaLink::get_test_failed_msg(wxString& msg) const
{
return GUI::from_u8((boost::format("%s: %s")
% _utf8(L("Could not connect to PrusaLink"))
% std::string(msg.ToUTF8())).str());
}
bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const
{
return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false;
}
void PrusaLink::set_auth(Http& http) const
{
switch (m_authorization_type) {
case atKeyPassword:
http.header("X-Api-Key", get_apikey());
break;
case atUserPassword:
http.auth_digest(m_username, m_password);
break;
}
if (!get_cafile().empty()) {
http.ca_file(get_cafile());
}
}
}

View file

@ -0,0 +1,101 @@
#ifndef slic3r_OctoPrint_hpp_
#define slic3r_OctoPrint_hpp_
#include <string>
#include <wx/string.h>
#include <boost/optional.hpp>
#include "PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class OctoPrint : public PrintHost
{
public:
OctoPrint(DynamicPrintConfig *config);
~OctoPrint() override = default;
const char* get_name() const override;
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) 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:
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
private:
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;
};
class SL1Host: public OctoPrint
{
public:
SL1Host(DynamicPrintConfig *config);
~SL1Host() override = default;
const char* get_name() const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString &msg) const override;
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
protected:
bool validate_version_text(const boost::optional<std::string> &version_text) const override;
private:
void set_auth(Http &http) const override;
// Host authorization type.
AuthorizationType m_authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617)
std::string m_username;
std::string m_password;
};
class PrusaLink : public OctoPrint
{
public:
PrusaLink(DynamicPrintConfig* config);
~PrusaLink() override = default;
const char* get_name() const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
protected:
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
private:
void set_auth(Http& http) const override;
// Host authorization type.
AuthorizationType m_authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617)
std::string m_username;
std::string m_password;
};
}
#endif

View file

@ -0,0 +1,281 @@
#include "PrintHost.hpp"
#include <vector>
#include <thread>
#include <exception>
#include <boost/optional.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include <wx/string.h>
#include <wx/app.h>
#include <wx/arrstr.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
//#include "Duet.hpp"
//#include "FlashAir.hpp"
//#include "AstroBox.hpp"
//#include "Repetier.hpp"
//#include "MKS.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
using boost::optional;
using Slic3r::GUI::PrintHostQueueDialog;
namespace Slic3r {
PrintHost::~PrintHost() {}
PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
{
PrinterTechnology tech = ptFFF;
{
const auto opt = config->option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
if (opt != nullptr) {
tech = opt->value;
}
}
if (tech == ptFFF) {
const auto opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type");
const auto host_type = opt != nullptr ? opt->value : htOctoPrint;
switch (host_type) {
case htOctoPrint: return new OctoPrint(config);
//case htDuet: return new Duet(config);
//case htFlashAir: return new FlashAir(config);
//case htAstroBox: return new AstroBox(config);
//case htRepetier: return new Repetier(config);
case htPrusaLink: return new PrusaLink(config);
//case htMKS: return new MKS(config);
default: return nullptr;
}
} else {
return new SL1Host(config);
}
}
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
{
if (status != 0) {
auto wxbody = wxString::FromUTF8(body.data());
return wxString::Format("HTTP %u: %s", status, wxbody);
} else {
return wxString::FromUTF8(error.data());
}
}
struct PrintHostJobQueue::priv
{
// XXX: comment on how bg thread works
PrintHostJobQueue *q;
Channel<PrintHostJob> channel_jobs;
Channel<size_t> channel_cancels;
size_t job_id = 0;
int prev_progress = -1;
fs::path source_to_remove;
std::thread bg_thread;
bool bg_exit = false;
PrintHostQueueDialog *queue_dialog;
priv(PrintHostJobQueue *q) : q(q) {}
void emit_progress(int progress);
void emit_error(wxString error);
void emit_cancel(size_t id);
void start_bg_thread();
void stop_bg_thread();
void bg_thread_main();
void progress_fn(Http::Progress progress, bool &cancel);
void remove_source(const fs::path &path);
void remove_source();
void perform_job(PrintHostJob the_job);
};
PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
: p(new priv(this))
{
p->queue_dialog = queue_dialog;
}
PrintHostJobQueue::~PrintHostJobQueue()
{
if (p) { p->stop_bg_thread(); }
}
void PrintHostJobQueue::priv::emit_progress(int progress)
{
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, progress);
wxQueueEvent(queue_dialog, evt);
}
void PrintHostJobQueue::priv::emit_error(wxString error)
{
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_ERROR, queue_dialog->GetId(), job_id, std::move(error));
wxQueueEvent(queue_dialog, evt);
}
void PrintHostJobQueue::priv::emit_cancel(size_t id)
{
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id);
wxQueueEvent(queue_dialog, evt);
}
void PrintHostJobQueue::priv::start_bg_thread()
{
if (bg_thread.joinable()) { return; }
std::shared_ptr<priv> p2 = q->p;
bg_thread = std::thread([p2]() {
p2->bg_thread_main();
});
}
void PrintHostJobQueue::priv::stop_bg_thread()
{
if (bg_thread.joinable()) {
bg_exit = true;
channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
bg_thread.detach(); // Let the background thread go, it should exit on its own
}
}
void PrintHostJobQueue::priv::bg_thread_main()
{
// bg thread entry point
try {
// Pick up jobs from the job channel:
while (! bg_exit) {
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
if (job.empty()) {
// This happens when the thread is being stopped
break;
}
source_to_remove = job.upload_data.source_path;
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Received job: [%1%]: `%2%` -> `%3%`, cancelled: %4%")
% job_id
% job.upload_data.upload_path
% job.printhost->get_host()
% job.cancelled;
if (! job.cancelled) {
perform_job(std::move(job));
}
remove_source();
job_id++;
}
} catch (const std::exception &e) {
emit_error(e.what());
}
// Cleanup leftover files, if any
remove_source();
auto jobs = channel_jobs.lock_rw();
for (const PrintHostJob &job : *jobs) {
remove_source(job.upload_data.source_path);
}
}
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
{
if (cancel) {
// When cancel is true from the start, Http indicates request has been cancelled
emit_cancel(job_id);
return;
}
if (bg_exit) {
cancel = true;
return;
}
if (channel_cancels.size_hint() > 0) {
// Lock both queues
auto cancels = channel_cancels.lock_rw();
auto jobs = channel_jobs.lock_rw();
for (size_t cancel_id : *cancels) {
if (cancel_id == job_id) {
cancel = true;
} else if (cancel_id > job_id) {
const size_t idx = cancel_id - job_id - 1;
if (idx < jobs->size()) {
jobs->at(idx).cancelled = true;
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
emit_cancel(cancel_id);
}
}
}
cancels->clear();
}
if (! cancel) {
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
if (gui_progress != prev_progress) {
emit_progress(gui_progress);
prev_progress = gui_progress;
}
}
}
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
{
if (! path.empty()) {
boost::system::error_code ec;
fs::remove(path, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % path % ec;
}
}
}
void PrintHostJobQueue::priv::remove_source()
{
remove_source(source_to_remove);
source_to_remove.clear();
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
emit_progress(0); // Indicate the upload is starting
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](wxString error) {
emit_error(std::move(error));
}
);
if (success) {
emit_progress(100);
}
}
void PrintHostJobQueue::enqueue(PrintHostJob job)
{
p->start_bg_thread();
p->queue_dialog->append_job(job);
p->channel_jobs.push(std::move(job));
}
void PrintHostJobQueue::cancel(size_t id)
{
p->channel_cancels.push(id);
}
}

View file

@ -0,0 +1,129 @@
#ifndef slic3r_PrintHost_hpp_
#define slic3r_PrintHost_hpp_
#include <memory>
#include <set>
#include <string>
#include <functional>
#include <boost/filesystem/path.hpp>
#include <wx/string.h>
#include <libslic3r/enum_bitmask.hpp>
#include "Http.hpp"
class wxArrayString;
namespace Slic3r {
class DynamicPrintConfig;
enum class PrintHostPostUploadAction {
None,
StartPrint,
StartSimulation
};
using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>;
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
struct PrintHostUpload
{
boost::filesystem::path source_path;
boost::filesystem::path upload_path;
std::string group;
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
};
class PrintHost
{
public:
virtual ~PrintHost();
typedef Http::ProgressFn ProgressFn;
typedef std::function<void(wxString /* error */)> ErrorFn;
virtual const char* get_name() const = 0;
virtual bool test(wxString &curl_msg) const = 0;
virtual wxString get_test_ok_msg () const = 0;
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0;
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
// A print host usually does not support multiple printers, with the exception of Repetier server.
virtual bool supports_multiple_printers() const { return false; }
virtual std::string get_host() const = 0;
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
// Returns false if not supported. May throw HostNetworkError.
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
static PrintHost* get_print_host(DynamicPrintConfig *config);
protected:
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
};
struct PrintHostJob
{
PrintHostUpload upload_data;
std::unique_ptr<PrintHost> printhost;
bool cancelled = false;
PrintHostJob() {}
PrintHostJob(const PrintHostJob&) = delete;
PrintHostJob(PrintHostJob &&other)
: upload_data(std::move(other.upload_data))
, printhost(std::move(other.printhost))
, cancelled(other.cancelled)
{}
PrintHostJob(DynamicPrintConfig *config)
: printhost(PrintHost::get_print_host(config))
{}
PrintHostJob& operator=(const PrintHostJob&) = delete;
PrintHostJob& operator=(PrintHostJob &&other)
{
upload_data = std::move(other.upload_data);
printhost = std::move(other.printhost);
cancelled = other.cancelled;
return *this;
}
bool empty() const { return !printhost; }
operator bool() const { return !!printhost; }
};
namespace GUI { class PrintHostQueueDialog; }
class PrintHostJobQueue
{
public:
PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
~PrintHostJobQueue();
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
void enqueue(PrintHostJob job);
void cancel(size_t id);
private:
struct priv;
std::shared_ptr<priv> p;
};
}
#endif