mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 15:07:31 -06:00
Support for SimplyPrint cloud integration (#4525)
* Make httpserver more generic and reusable * Add OAuthJob * Fix issue caused by the fact that the backing widget of the `TextCtrl` is no longer `wxTextCtrl` * Implement login and token refresh * Implement file upload * Try fix build error * Support BBL printers * Show error message if user hasn't done OAuth * Fix typo * Update error message * Disable unsupported options when SimplyPrint is selected
This commit is contained in:
parent
f3b3e92782
commit
e29bbac193
27 changed files with 1075 additions and 228 deletions
|
@ -874,7 +874,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure",
|
||||
"best_object_pos","head_wrap_detect_zone",
|
||||
//SoftFever
|
||||
"host_type", "print_host", "printhost_apikey",
|
||||
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost",
|
||||
"print_host_webui",
|
||||
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
|
||||
|
@ -2941,6 +2941,7 @@ static std::vector<std::string> s_PhysicalPrinter_opts {
|
|||
"preset_name", // temporary option to compatibility with older Slicer
|
||||
"preset_names",
|
||||
"printer_technology",
|
||||
"bbl_use_printhost",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"print_host_webui",
|
||||
|
|
|
@ -344,6 +344,13 @@ VendorType PresetBundle::get_current_vendor_type()
|
|||
return t;
|
||||
}
|
||||
|
||||
bool PresetBundle::use_bbl_network()
|
||||
{
|
||||
const auto cfg = printers.get_edited_preset().config;
|
||||
const bool use_bbl_network = is_bbl_vendor() && !cfg.opt_bool("bbl_use_printhost");
|
||||
return use_bbl_network;
|
||||
}
|
||||
|
||||
//BBS: load project embedded presets
|
||||
PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
|
|
|
@ -97,6 +97,7 @@ public:
|
|||
VendorType get_current_vendor_type();
|
||||
// Vendor related handy functions
|
||||
bool is_bbl_vendor() { return get_current_vendor_type() == VendorType::Marlin_BBL; }
|
||||
bool use_bbl_network();
|
||||
|
||||
//BBS: project embedded preset logic
|
||||
PresetsConfigSubstitutions load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule);
|
||||
|
|
|
@ -104,7 +104,8 @@ static t_config_enum_values s_keys_map_PrintHostType {
|
|||
{ "repetier", htRepetier },
|
||||
{ "mks", htMKS },
|
||||
{ "obico", htObico },
|
||||
{ "flashforge", htFlashforge}
|
||||
{ "flashforge", htFlashforge },
|
||||
{ "simplyprint", htSimplyPrint },
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
|
||||
|
||||
|
@ -542,6 +543,13 @@ void PrintConfigDef::init_common_params()
|
|||
def->mode = comDevelop;
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
def = this->add("bbl_use_printhost", coBool);
|
||||
def->label = L("Use 3rd-party print host");
|
||||
def->tooltip = L("Allow controlling BambuLab's printer through 3rd party print hosts");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("print_host", coString);
|
||||
def->label = L("Hostname, IP or URL");
|
||||
def->tooltip = L("Orca Slicer can upload G-code files to a printer host. This field should contain "
|
||||
|
@ -3068,6 +3076,7 @@ def = this->add("filament_loading_speed", coFloats);
|
|||
def->enum_values.push_back("mks");
|
||||
def->enum_values.push_back("obico");
|
||||
def->enum_values.push_back("flashforge");
|
||||
def->enum_values.push_back("simplyprint");
|
||||
def->enum_labels.push_back("PrusaLink");
|
||||
def->enum_labels.push_back("PrusaConnect");
|
||||
def->enum_labels.push_back("Octo/Klipper");
|
||||
|
@ -3078,6 +3087,7 @@ def = this->add("filament_loading_speed", coFloats);
|
|||
def->enum_labels.push_back("MKS");
|
||||
def->enum_labels.push_back("Obico");
|
||||
def->enum_labels.push_back("Flashforge");
|
||||
def->enum_labels.push_back("SimplyPrint");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||
|
|
|
@ -59,7 +59,7 @@ enum class FuzzySkinType {
|
|||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htObico, htFlashforge
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htObico, htFlashforge, htSimplyPrint
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
|
|
|
@ -532,6 +532,13 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/Obico.hpp
|
||||
Utils/Flashforge.cpp
|
||||
Utils/Flashforge.hpp
|
||||
|
||||
GUI/OAuthDialog.cpp
|
||||
GUI/OAuthDialog.hpp
|
||||
GUI/Jobs/OAuthJob.cpp
|
||||
GUI/Jobs/OAuthJob.hpp
|
||||
Utils/SimplyPrint.cpp
|
||||
Utils/SimplyPrint.hpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -7,35 +7,179 @@
|
|||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static std::string parse_params(std::string url, std::string key)
|
||||
std::string url_get_param(const std::string& url, const std::string& key)
|
||||
{
|
||||
size_t start = url.find(key);
|
||||
if (start < 0) return "";
|
||||
if (start == std::string::npos) return "";
|
||||
size_t eq = url.find('=', start);
|
||||
if (eq < 0) return "";
|
||||
if (eq == std::string::npos) return "";
|
||||
std::string key_str = url.substr(start, eq - start);
|
||||
if (key_str != key)
|
||||
return "";
|
||||
start += key.size() + 1;
|
||||
size_t end = url.find('&', start);
|
||||
if (end < 0)
|
||||
return "";
|
||||
if (end == std::string::npos) end = url.length(); // Last param
|
||||
std::string result = url.substr(start, end - start);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string http_headers::get_response()
|
||||
void session::start()
|
||||
{
|
||||
read_first_line();
|
||||
}
|
||||
|
||||
void session::stop()
|
||||
{
|
||||
boost::system::error_code ignored_ec;
|
||||
socket.shutdown(boost::asio::socket_base::shutdown_both, ignored_ec);
|
||||
socket.close(ignored_ec);
|
||||
}
|
||||
|
||||
void session::read_first_line()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
|
||||
async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) {
|
||||
if (!e) {
|
||||
std::string line, ignore;
|
||||
std::istream stream{&buff};
|
||||
std::getline(stream, line, '\r');
|
||||
std::getline(stream, ignore, '\n');
|
||||
headers.on_read_request_line(line);
|
||||
read_next_line();
|
||||
} else if (e != boost::asio::error::operation_aborted) {
|
||||
server.stop(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void session::read_body()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
|
||||
int nbuffer = 1000;
|
||||
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
|
||||
async_read(socket, boost::asio::buffer(*bufptr, nbuffer),
|
||||
[this, self](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
|
||||
}
|
||||
|
||||
void session::read_next_line()
|
||||
{
|
||||
auto self(shared_from_this());
|
||||
|
||||
async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) {
|
||||
if (!e) {
|
||||
std::string line, ignore;
|
||||
std::istream stream{&buff};
|
||||
std::getline(stream, line, '\r');
|
||||
std::getline(stream, ignore, '\n');
|
||||
headers.on_read_header(line);
|
||||
|
||||
if (line.length() == 0) {
|
||||
if (headers.content_length() == 0) {
|
||||
const std::string url_str = Http::url_decode(headers.get_url());
|
||||
const auto resp = server.server.m_request_handler(url_str);
|
||||
std::stringstream ssOut;
|
||||
resp->write_response(ssOut);
|
||||
std::shared_ptr<std::string> str = std::make_shared<std::string>(ssOut.str());
|
||||
async_write(socket, boost::asio::buffer(str->c_str(), str->length()),
|
||||
[this, self](const boost::beast::error_code& e, std::size_t s) {
|
||||
std::cout << "done" << std::endl;
|
||||
server.stop(self);
|
||||
});
|
||||
} else {
|
||||
read_body();
|
||||
}
|
||||
} else {
|
||||
read_next_line();
|
||||
}
|
||||
} else if (e != boost::asio::error::operation_aborted) {
|
||||
server.stop(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::IOServer::do_accept()
|
||||
{
|
||||
acceptor.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
|
||||
if (!acceptor.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ec) {
|
||||
const auto ss = std::make_shared<session>(*this, std::move(socket));
|
||||
start(ss);
|
||||
}
|
||||
|
||||
do_accept();
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::IOServer::start(std::shared_ptr<session> session)
|
||||
{
|
||||
sessions.insert(session);
|
||||
session->start();
|
||||
}
|
||||
|
||||
void HttpServer::IOServer::stop(std::shared_ptr<session> session)
|
||||
{
|
||||
sessions.erase(session);
|
||||
session->stop();
|
||||
}
|
||||
|
||||
void HttpServer::IOServer::stop_all()
|
||||
{
|
||||
for (auto s : sessions) {
|
||||
s->stop();
|
||||
}
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
|
||||
HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {}
|
||||
|
||||
void HttpServer::start()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
|
||||
start_http_server = true;
|
||||
m_http_server_thread = create_thread([this] {
|
||||
set_current_thread_name("http_server");
|
||||
server_ = std::make_unique<IOServer>(*this);
|
||||
server_->acceptor.listen();
|
||||
|
||||
server_->do_accept();
|
||||
|
||||
server_->io_service.run();
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
start_http_server = false;
|
||||
if (server_) {
|
||||
server_->acceptor.close();
|
||||
server_->stop_all();
|
||||
}
|
||||
if (m_http_server_thread.joinable())
|
||||
m_http_server_thread.join();
|
||||
server_.reset();
|
||||
}
|
||||
|
||||
void HttpServer::set_request_handler(const std::function<std::shared_ptr<Response>(const std::string&)>& request_handler)
|
||||
{
|
||||
this->m_request_handler = request_handler;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const std::string& url)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
|
||||
std::stringstream ssOut;
|
||||
std::string url_str = Http::url_decode(url);
|
||||
if (boost::contains(url_str, "access_token")) {
|
||||
std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
|
||||
std::string redirect_url = parse_params(url_str, "redirect_url");
|
||||
std::string access_token = parse_params(url_str, "access_token");
|
||||
std::string refresh_token = parse_params(url_str, "refresh_token");
|
||||
std::string expires_in_str = parse_params(url_str, "expires_in");
|
||||
std::string refresh_expires_in_str = parse_params(url_str, "refresh_expires_in");
|
||||
|
||||
if (boost::contains(url, "access_token")) {
|
||||
std::string redirect_url = url_get_param(url, "redirect_url");
|
||||
std::string access_token = url_get_param(url, "access_token");
|
||||
std::string refresh_token = url_get_param(url, "refresh_token");
|
||||
std::string expires_in_str = url_get_param(url, "expires_in");
|
||||
std::string refresh_expires_in_str = url_get_param(url, "refresh_expires_in");
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
|
||||
unsigned int http_code;
|
||||
|
@ -72,79 +216,38 @@ std::string http_headers::get_response()
|
|||
if (agent->is_user_login()) {
|
||||
wxGetApp().request_user_login(1);
|
||||
}
|
||||
GUI::wxGetApp().CallAfter([this] {
|
||||
wxGetApp().ShowUserLogin(false);
|
||||
});
|
||||
std::string location_str = (boost::format("Location: %1%?result=success") % redirect_url).str();
|
||||
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
||||
ssOut << location_str << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||
ssOut << std::endl;
|
||||
ssOut << sHTML;
|
||||
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||
std::string location_str = (boost::format("%1%?result=success") % redirect_url).str();
|
||||
return std::make_shared<ResponseRedirect>(location_str);
|
||||
} else {
|
||||
std::string error_str = "get_user_profile_error_" + std::to_string(result);
|
||||
std::string location_str = (boost::format("Location: %1%?result=fail&error=%2%") % redirect_url % error_str).str();
|
||||
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
||||
ssOut << location_str << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||
ssOut << std::endl;
|
||||
ssOut << sHTML;
|
||||
std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str();
|
||||
return std::make_shared<ResponseRedirect>(location_str);
|
||||
}
|
||||
} else {
|
||||
std::string sHTML = "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
|
||||
return std::make_shared<ResponseNotFound>();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut)
|
||||
{
|
||||
const std::string sHTML = "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
|
||||
ssOut << "HTTP/1.1 404 Not Found" << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||
ssOut << std::endl;
|
||||
ssOut << sHTML;
|
||||
}
|
||||
return ssOut.str();
|
||||
}
|
||||
|
||||
|
||||
void accept_and_run(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& io_service)
|
||||
void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
|
||||
{
|
||||
std::shared_ptr<session> sesh = std::make_shared<session>(io_service);
|
||||
acceptor.async_accept(sesh->socket,
|
||||
[sesh, &acceptor, &io_service](const boost::beast::error_code& accept_error)
|
||||
{
|
||||
accept_and_run(acceptor, io_service);
|
||||
if (!accept_error)
|
||||
{
|
||||
session::interact(sesh);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HttpServer::HttpServer()
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void HttpServer::start()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
|
||||
start_http_server = true;
|
||||
m_http_server_thread = Slic3r::create_thread(
|
||||
[this] {
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::ip::tcp::endpoint endpoint{ boost::asio::ip::tcp::v4(), LOCALHOST_PORT};
|
||||
boost::asio::ip::tcp::acceptor acceptor { io_service, endpoint};
|
||||
acceptor.listen();
|
||||
accept_and_run(acceptor, io_service);
|
||||
while (start_http_server) {
|
||||
io_service.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
start_http_server = false;
|
||||
if (m_http_server_thread.joinable())
|
||||
m_http_server_thread.join();
|
||||
const std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
|
||||
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
||||
ssOut << "Location: " << location_str << std::endl;
|
||||
ssOut << "content-type: text/html" << std::endl;
|
||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||
ssOut << std::endl;
|
||||
ssOut << sHTML;
|
||||
}
|
||||
|
||||
} // GUI
|
||||
|
|
|
@ -13,14 +13,12 @@
|
|||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
using namespace boost::system;
|
||||
using namespace boost::asio;
|
||||
|
||||
#define LOCALHOST_PORT 13618
|
||||
#define LOCALHOST_URL "http://localhost:"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class session;
|
||||
|
||||
class http_headers
|
||||
{
|
||||
|
@ -31,14 +29,12 @@ class http_headers
|
|||
std::map<std::string, std::string> headers;
|
||||
|
||||
public:
|
||||
|
||||
std::string get_response();
|
||||
std::string get_url() { return url; }
|
||||
|
||||
int content_length()
|
||||
{
|
||||
auto request = headers.find("content-length");
|
||||
if (request != headers.end())
|
||||
{
|
||||
if (request != headers.end()) {
|
||||
std::stringstream ssLength(request->second);
|
||||
int content_length;
|
||||
ssLength >> content_length;
|
||||
|
@ -49,7 +45,7 @@ public:
|
|||
|
||||
void on_read_header(std::string line)
|
||||
{
|
||||
//std::cout << "header: " << line << std::endl;
|
||||
// std::cout << "header: " << line << std::endl;
|
||||
|
||||
std::stringstream ssHeader(line);
|
||||
std::string headerName;
|
||||
|
@ -71,82 +67,36 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class session
|
||||
class HttpServer
|
||||
{
|
||||
boost::asio::streambuf buff;
|
||||
http_headers headers;
|
||||
|
||||
static void read_body(std::shared_ptr<session> pThis)
|
||||
{
|
||||
int nbuffer = 1000;
|
||||
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
|
||||
boost::asio::async_read(pThis->socket, boost::asio::buffer(*bufptr, nbuffer), [pThis](const boost::beast::error_code& e, std::size_t s)
|
||||
{
|
||||
});
|
||||
}
|
||||
|
||||
static void read_next_line(std::shared_ptr<session> pThis)
|
||||
{
|
||||
boost::asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s)
|
||||
{
|
||||
std::string line, ignore;
|
||||
std::istream stream{ &pThis->buff };
|
||||
std::getline(stream, line, '\r');
|
||||
std::getline(stream, ignore, '\n');
|
||||
pThis->headers.on_read_header(line);
|
||||
|
||||
if (line.length() == 0)
|
||||
{
|
||||
if (pThis->headers.content_length() == 0)
|
||||
{
|
||||
std::shared_ptr<std::string> str = std::make_shared<std::string>(pThis->headers.get_response());
|
||||
boost::asio::async_write(pThis->socket, boost::asio::buffer(str->c_str(), str->length()), [pThis, str](const boost::beast::error_code& e, std::size_t s)
|
||||
{
|
||||
std::cout << "done" << std::endl;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
pThis->read_body(pThis);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pThis->read_next_line(pThis);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void read_first_line(std::shared_ptr<session> pThis)
|
||||
{
|
||||
boost::asio::async_read_until(pThis->socket, pThis->buff, '\r', [pThis](const boost::beast::error_code& e, std::size_t s)
|
||||
{
|
||||
std::string line, ignore;
|
||||
std::istream stream{ &pThis->buff };
|
||||
std::getline(stream, line, '\r');
|
||||
std::getline(stream, ignore, '\n');
|
||||
pThis->headers.on_read_request_line(line);
|
||||
pThis->read_next_line(pThis);
|
||||
});
|
||||
}
|
||||
boost::asio::ip::port_type port;
|
||||
|
||||
public:
|
||||
boost::asio::ip::tcp::socket socket;
|
||||
|
||||
session(io_service& io_service)
|
||||
:socket(io_service)
|
||||
class Response
|
||||
{
|
||||
}
|
||||
public:
|
||||
virtual ~Response() = default;
|
||||
virtual void write_response(std::stringstream& ssOut) = 0;
|
||||
};
|
||||
|
||||
static void interact(std::shared_ptr<session> pThis)
|
||||
class ResponseNotFound : public Response
|
||||
{
|
||||
read_first_line(pThis);
|
||||
}
|
||||
};
|
||||
public:
|
||||
~ResponseNotFound() override = default;
|
||||
void write_response(std::stringstream& ssOut) override;
|
||||
};
|
||||
|
||||
class HttpServer {
|
||||
public:
|
||||
HttpServer();
|
||||
class ResponseRedirect : public Response
|
||||
{
|
||||
const std::string location_str;
|
||||
|
||||
public:
|
||||
ResponseRedirect(const std::string& location) : location_str(location) {}
|
||||
~ResponseRedirect() override = default;
|
||||
void write_response(std::stringstream& ssOut) override;
|
||||
};
|
||||
|
||||
HttpServer(boost::asio::ip::port_type port = LOCALHOST_PORT);
|
||||
|
||||
boost::thread m_http_server_thread;
|
||||
bool start_http_server = false;
|
||||
|
@ -154,9 +104,55 @@ public:
|
|||
bool is_started() { return start_http_server; }
|
||||
void start();
|
||||
void stop();
|
||||
void set_request_handler(const std::function<std::shared_ptr<Response>(const std::string&)>& m_request_handler);
|
||||
|
||||
static std::shared_ptr<Response> bbl_auth_handle_request(const std::string& url);
|
||||
|
||||
private:
|
||||
class IOServer
|
||||
{
|
||||
public:
|
||||
HttpServer& server;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::ip::tcp::acceptor acceptor;
|
||||
std::set<std::shared_ptr<session>> sessions;
|
||||
|
||||
IOServer(HttpServer& server) : server(server), acceptor(io_service, {boost::asio::ip::tcp::v4(), server.port}) {}
|
||||
|
||||
void do_accept();
|
||||
|
||||
void start(std::shared_ptr<session> session);
|
||||
void stop(std::shared_ptr<session> session);
|
||||
void stop_all();
|
||||
};
|
||||
friend class session;
|
||||
|
||||
std::unique_ptr<IOServer> server_{nullptr};
|
||||
|
||||
std::function<std::shared_ptr<Response>(const std::string&)> m_request_handler{&HttpServer::bbl_auth_handle_request};
|
||||
};
|
||||
|
||||
}
|
||||
class session : public std::enable_shared_from_this<session>
|
||||
{
|
||||
HttpServer::IOServer& server;
|
||||
boost::asio::ip::tcp::socket socket;
|
||||
|
||||
boost::asio::streambuf buff;
|
||||
http_headers headers;
|
||||
|
||||
void read_first_line();
|
||||
void read_next_line();
|
||||
void read_body();
|
||||
|
||||
public:
|
||||
session(HttpServer::IOServer& server, boost::asio::ip::tcp::socket socket) : server(server), socket(std::move(socket)) {}
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
};
|
||||
|
||||
std::string url_get_param(const std::string& url, const std::string& key);
|
||||
|
||||
}};
|
||||
|
||||
#endif
|
||||
|
|
124
src/slic3r/GUI/Jobs/OAuthJob.cpp
Normal file
124
src/slic3r/GUI/Jobs/OAuthJob.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#include "OAuthJob.hpp"
|
||||
|
||||
#include "Http.hpp"
|
||||
#include "ThreadSafeQueue.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(EVT_OAUTH_COMPLETE_MESSAGE, wxCommandEvent);
|
||||
|
||||
OAuthJob::OAuthJob(const OAuthData& input) : local_authorization_server(input.params.callback_port), _data(input) {}
|
||||
|
||||
void OAuthJob::parse_token_response(const std::string& body, bool error, OAuthResult& result)
|
||||
{
|
||||
const auto j = nlohmann::json::parse(body, nullptr, false, true);
|
||||
if (j.is_discarded()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Invalid or no JSON data on token response: " << body;
|
||||
result.error_message = _u8L("Unknown error");
|
||||
} else if (error) {
|
||||
if (j.contains("error_description")) {
|
||||
j.at("error_description").get_to(result.error_message);
|
||||
} else {
|
||||
result.error_message = _u8L("Unknown error");
|
||||
}
|
||||
} else {
|
||||
j.at("access_token").get_to(result.access_token);
|
||||
j.at("refresh_token").get_to(result.refresh_token);
|
||||
result.success = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OAuthJob::process(Ctl& ctl)
|
||||
{
|
||||
// Prepare auth process
|
||||
ThreadSafeQueueSPSC<OAuthResult> queue;
|
||||
|
||||
// Setup auth server to receive OAuth code from callback url
|
||||
local_authorization_server.set_request_handler([this, &queue](const std::string& url) -> std::shared_ptr<HttpServer::Response> {
|
||||
if (boost::contains(url, "/callback")) {
|
||||
const auto code = url_get_param(url, "code");
|
||||
const auto state = url_get_param(url, "state");
|
||||
|
||||
const auto handle_auth_fail = [this, &queue](const std::string& message) -> std::shared_ptr<HttpServer::ResponseRedirect> {
|
||||
queue.push(OAuthResult{false, message});
|
||||
return std::make_shared<HttpServer::ResponseRedirect>(this->_data.params.auth_fail_redirect_url);
|
||||
};
|
||||
|
||||
if (state != _data.params.state) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "The provided state was not correct. Got " << state << " and expected " << _data.params.state;
|
||||
return handle_auth_fail(_u8L("The provided state is not correct."));
|
||||
}
|
||||
|
||||
if (code.empty()) {
|
||||
const auto error_code = url_get_param(url, "error_code");
|
||||
if (error_code == "user_denied") {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User did not give the required permission when authorizing this application";
|
||||
return handle_auth_fail(_u8L("Please give the required permissions when authorizing this application."));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Unexpected error when logging in. Error_code: " << error_code << ", State: " << state;
|
||||
return handle_auth_fail(_u8L("Something unexpected happened when trying to log in, please try again."));
|
||||
}
|
||||
|
||||
|
||||
OAuthResult r;
|
||||
// Request the access token from the authorization server.
|
||||
auto http = Http::post(_data.params.token_url);
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(5)
|
||||
.form_add("client_id", _data.params.client_id)
|
||||
.form_add("redirect_uri", _data.params.callback_url)
|
||||
.form_add("grant_type", "authorization_code")
|
||||
.form_add("code", code)
|
||||
.form_add("code_verifier", _data.params.verification_code)
|
||||
.form_add("scope", _data.params.scope)
|
||||
.on_complete([&](std::string body, unsigned status) { parse_token_response(body, false, r); })
|
||||
.on_error([&](std::string body, std::string error, unsigned status) { parse_token_response(body, true, r); })
|
||||
.perform_sync();
|
||||
|
||||
queue.push(r);
|
||||
return std::make_shared<HttpServer::ResponseRedirect>(r.success ? _data.params.auth_success_redirect_url :
|
||||
_data.params.auth_fail_redirect_url);
|
||||
} else {
|
||||
queue.push(OAuthResult{false});
|
||||
return std::make_shared<HttpServer::ResponseNotFound>();
|
||||
}
|
||||
});
|
||||
|
||||
// Run the local server
|
||||
local_authorization_server.start();
|
||||
|
||||
// Wait until we received the result
|
||||
bool received = false;
|
||||
while (!ctl.was_canceled() && !received ) {
|
||||
queue.consume_one(BlockingWait{1000}, [this, &received](const OAuthResult& result) {
|
||||
*_data.result = result;
|
||||
received = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Handle timeout
|
||||
if (!received && !ctl.was_canceled()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Timeout when authenticating with the account server.";
|
||||
_data.result->error_message = _u8L("Timeout when authenticating with the account server.");
|
||||
} else if (ctl.was_canceled()) {
|
||||
_data.result->error_message = _u8L("User cancelled.");
|
||||
}
|
||||
}
|
||||
|
||||
void OAuthJob::finalize(bool canceled, std::exception_ptr& e)
|
||||
{
|
||||
// Make sure it's stopped
|
||||
local_authorization_server.stop();
|
||||
|
||||
wxCommandEvent event(EVT_OAUTH_COMPLETE_MESSAGE);
|
||||
event.SetEventObject(m_event_handle);
|
||||
wxPostEvent(m_event_handle, event);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
62
src/slic3r/GUI/Jobs/OAuthJob.hpp
Normal file
62
src/slic3r/GUI/Jobs/OAuthJob.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef __OAuthJob_HPP__
|
||||
#define __OAuthJob_HPP__
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "slic3r/GUI/HttpServer.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class Plater;
|
||||
|
||||
struct OAuthParams
|
||||
{
|
||||
std::string login_url;
|
||||
std::string client_id;
|
||||
boost::asio::ip::port_type callback_port;
|
||||
std::string callback_url;
|
||||
std::string scope;
|
||||
std::string response_type;
|
||||
std::string auth_success_redirect_url;
|
||||
std::string auth_fail_redirect_url;
|
||||
std::string token_url;
|
||||
std::string verification_code;
|
||||
std::string state;
|
||||
};
|
||||
|
||||
struct OAuthResult
|
||||
{
|
||||
bool success{false};
|
||||
std::string error_message{""};
|
||||
std::string access_token{""};
|
||||
std::string refresh_token{""};
|
||||
};
|
||||
|
||||
struct OAuthData
|
||||
{
|
||||
OAuthParams params;
|
||||
std::shared_ptr<OAuthResult> result;
|
||||
};
|
||||
|
||||
class OAuthJob : public Job
|
||||
{
|
||||
HttpServer local_authorization_server;
|
||||
OAuthData _data;
|
||||
wxWindow* m_event_handle{nullptr};
|
||||
|
||||
public:
|
||||
explicit OAuthJob(const OAuthData& input);
|
||||
|
||||
void process(Ctl& ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr& e) override;
|
||||
|
||||
void set_event_handle(wxWindow* hanle) { m_event_handle = hanle; }
|
||||
|
||||
static void parse_token_response(const std::string& body, bool error, OAuthResult& result);
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_OAUTH_COMPLETE_MESSAGE, wxCommandEvent);
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // OAUTHJOB_HPP
|
|
@ -562,7 +562,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
|||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
if (m_print_enable) {
|
||||
if (wxGetApp().preset_bundle->is_bbl_vendor())
|
||||
if (wxGetApp().preset_bundle->use_bbl_network())
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
|
||||
else
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
||||
|
@ -1600,7 +1600,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
|||
SidePopup* p = new SidePopup(this);
|
||||
|
||||
if (wxGetApp().preset_bundle
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
&& !wxGetApp().preset_bundle->use_bbl_network()) {
|
||||
// ThirdParty Buttons
|
||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||
export_gcode_btn->SetCornerRadius(0);
|
||||
|
@ -3584,7 +3584,7 @@ void MainFrame::load_printer_url(wxString url, wxString apikey)
|
|||
void MainFrame::load_printer_url()
|
||||
{
|
||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.is_bbl_vendor())
|
||||
if (preset_bundle.use_bbl_network())
|
||||
return;
|
||||
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
|
|
84
src/slic3r/GUI/OAuthDialog.cpp
Normal file
84
src/slic3r/GUI/OAuthDialog.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include "OAuthDialog.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "Jobs/BoostThreadWorker.hpp"
|
||||
#include "Jobs/PlaterWorker.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
#define BORDER_W FromDIP(10)
|
||||
|
||||
OAuthDialog::OAuthDialog(wxWindow* parent, OAuthParams params)
|
||||
: DPIDialog(parent, wxID_ANY, _L("Login"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE)
|
||||
, _params(params)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_worker = std::make_unique<PlaterWorker<BoostThreadWorker>>(this, nullptr, "auth_worker");
|
||||
|
||||
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxCANCEL);
|
||||
btnCancel = static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this));
|
||||
wxGetApp().UpdateDarkUI(btnCancel);
|
||||
btnCancel->Bind(wxEVT_BUTTON, &OAuthDialog::on_cancel, this);
|
||||
|
||||
const auto message_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
const auto message = new wxStaticText(this, wxID_ANY, _L("Authorizing..."), wxDefaultPosition, wxDefaultSize, 0);
|
||||
message->SetForegroundColour(*wxBLACK);
|
||||
message_sizer->Add(message, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, BORDER_W);
|
||||
|
||||
const auto topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
topSizer->Add(message_sizer, 0, wxEXPAND | wxALL, BORDER_W);
|
||||
topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W);
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, &OAuthDialog::on_cancel, this);
|
||||
|
||||
SetSizer(topSizer);
|
||||
topSizer->SetSizeHints(this);
|
||||
this->CenterOnParent();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
void OAuthDialog::on_cancel(wxEvent& event)
|
||||
{
|
||||
m_worker->cancel_all();
|
||||
m_worker->wait_for_idle();
|
||||
EndModal(wxID_NO);
|
||||
}
|
||||
|
||||
bool OAuthDialog::Show(bool show)
|
||||
{
|
||||
if (show) {
|
||||
// Prepare login job
|
||||
_result = std::make_shared<OAuthResult>();
|
||||
auto job = std::make_unique<OAuthJob>(OAuthData{_params, _result});
|
||||
job->set_event_handle(this);
|
||||
Bind(EVT_OAUTH_COMPLETE_MESSAGE, [this](wxCommandEvent& evt) { EndModal(wxID_NO); });
|
||||
|
||||
// Start auth job
|
||||
replace_job(*m_worker, std::move(job));
|
||||
|
||||
// Open login URL in external browser
|
||||
wxLaunchDefaultBrowser(_params.login_url);
|
||||
}
|
||||
|
||||
return DPIDialog::Show(show);
|
||||
}
|
||||
|
||||
void OAuthDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, {wxID_CANCEL});
|
||||
|
||||
const wxSize& size = wxSize(45 * em, 35 * em);
|
||||
SetMinSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
}}
|
34
src/slic3r/GUI/OAuthDialog.hpp
Normal file
34
src/slic3r/GUI/OAuthDialog.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef __OAuthDialog_HPP__
|
||||
#define __OAuthDialog_HPP__
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "Jobs/OAuthJob.hpp"
|
||||
#include "Jobs/Worker.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class OAuthDialog : public DPIDialog
|
||||
{
|
||||
private:
|
||||
OAuthParams _params;
|
||||
std::shared_ptr<OAuthResult> _result;
|
||||
|
||||
wxButton* btnCancel{nullptr};
|
||||
std::unique_ptr<Worker> m_worker;
|
||||
|
||||
void on_cancel(wxEvent& event);
|
||||
|
||||
protected:
|
||||
bool Show(bool show) override;
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
public:
|
||||
OAuthDialog(wxWindow* parent, OAuthParams params);
|
||||
|
||||
OAuthResult get_result() { return *_result; }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif
|
|
@ -194,6 +194,13 @@ void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* =
|
|||
}
|
||||
}
|
||||
|
||||
void OptionsGroup::enable_field(const t_config_option_key& opt_key, bool enable)
|
||||
{
|
||||
if (Field* f = get_field(opt_key); f) {
|
||||
f->toggle(enable);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionsGroup::set_name(const wxString& new_name)
|
||||
{
|
||||
stb->SetLabel(new_name);
|
||||
|
|
|
@ -173,6 +173,9 @@ public:
|
|||
void show_field(const t_config_option_key& opt_key, bool show = true);
|
||||
void hide_field(const t_config_option_key& opt_key) { show_field(opt_key, false); }
|
||||
|
||||
void enable_field(const t_config_option_key& opt_key, bool enable = true);
|
||||
void disable_field(const t_config_option_key& opt_key) { enable_field(opt_key, false); }
|
||||
|
||||
void set_name(const wxString& new_name);
|
||||
|
||||
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
#include "BitmapCache.hpp"
|
||||
#include "BonjourDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "OAuthDialog.hpp"
|
||||
#include "SimplyPrint.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -174,20 +176,24 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
result = host->test(msg);
|
||||
|
||||
if (!result && host->is_cloud()) {
|
||||
if (const auto h = dynamic_cast<SimplyPrint*>(host.get()); h) {
|
||||
OAuthDialog dlg(this, h->get_oauth_params());
|
||||
dlg.ShowModal();
|
||||
|
||||
const auto& r = dlg.get_result();
|
||||
result = r.success;
|
||||
if (r.success) {
|
||||
h->save_oauth_credential(r);
|
||||
} else {
|
||||
msg = r.error_message;
|
||||
}
|
||||
} else {
|
||||
PrinterCloudAuthDialog dlg(this->GetParent(), host.get());
|
||||
dlg.ShowModal();
|
||||
|
||||
auto api_key = dlg.GetApiKey();
|
||||
const auto api_key = dlg.GetApiKey();
|
||||
m_config->opt_string("printhost_apikey") = api_key;
|
||||
result = !api_key.empty();
|
||||
if (result) {
|
||||
if (Field* print_host_webui_field = this->m_optgroup->get_field("printhost_apikey"); print_host_webui_field) {
|
||||
if (TextInput* temp_input = dynamic_cast<TextInput*>(print_host_webui_field->getWindow()); temp_input) {
|
||||
if (wxTextCtrl* temp = temp_input->GetTextCtrl()) {
|
||||
temp->SetValue(wxString(api_key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +201,31 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
show_info(this, host->get_test_ok_msg(), _L("Success!"));
|
||||
else
|
||||
show_error(this, host->get_test_failed_msg(msg));
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
auto print_host_logout = [&](wxWindow* parent) {
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_logout_btn, "", _L("Log Out"));
|
||||
|
||||
m_printhost_logout_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||
if (!host) {
|
||||
const wxString text = _L("Could not get a valid Printer Host reference");
|
||||
show_error(this, text);
|
||||
return;
|
||||
}
|
||||
|
||||
wxString msg_text = _L("Are you sure to log out?");
|
||||
MessageDialog dialog(this, msg_text, "", wxICON_QUESTION | wxYES_NO);
|
||||
|
||||
if (dialog.ShowModal() == wxID_YES) {
|
||||
host->log_out();
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
|
@ -215,6 +246,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
Line host_line = m_optgroup->create_single_option_line(option);
|
||||
host_line.append_widget(printhost_browse);
|
||||
host_line.append_widget(print_host_test);
|
||||
host_line.append_widget(print_host_logout);
|
||||
m_optgroup->append_line(host_line);
|
||||
|
||||
option = m_optgroup->get_option("print_host_webui");
|
||||
|
@ -375,7 +407,9 @@ void PhysicalPrinterDialog::update_printhost_buttons()
|
|||
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||
if (host) {
|
||||
m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
|
||||
m_printhost_browse_btn->Enable(host->has_auto_discovery());
|
||||
m_printhost_browse_btn->Show(host->has_auto_discovery());
|
||||
m_printhost_logout_btn->Show(host->is_logged_in());
|
||||
m_printhost_test_btn->SetLabel(host->is_cloud() ? _L("Login/Test") : _L("Test"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,14 +503,28 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
|||
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||
m_optgroup->show_field("host_type");
|
||||
|
||||
// hide PrusaConnect address
|
||||
m_optgroup->enable_field("print_host");
|
||||
m_optgroup->enable_field("print_host_webui");
|
||||
m_optgroup->enable_field("printhost_cafile");
|
||||
m_optgroup->enable_field("printhost_ssl_ignore_revoke");
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->Enable();
|
||||
|
||||
// hide pre-configured address, in case user switched to a different host type
|
||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
||||
if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") {
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_field)->text_ctrl(); temp) {
|
||||
const auto current_host = temp->GetValue();
|
||||
if (current_host == L"https://connect.prusa3d.com" ||
|
||||
current_host == L"https://app.obico.io" ||
|
||||
current_host == "https://simplyprint.io") {
|
||||
temp->SetValue(wxString());
|
||||
}
|
||||
|
||||
if (TextInput* temp_input = dynamic_cast<TextInput*>(printhost_field->getWindow()); temp_input) {
|
||||
if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp &&temp->GetValue() == L"https://app.obico.io") {
|
||||
}
|
||||
}
|
||||
if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) {
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_webui_field)->text_ctrl(); temp) {
|
||||
const auto current_host = temp->GetValue();
|
||||
if (current_host == "https://simplyprint.io/panel") {
|
||||
temp->SetValue(wxString());
|
||||
}
|
||||
}
|
||||
|
@ -492,25 +540,42 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
|||
m_optgroup->show_field("printhost_apikey", true);
|
||||
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
|
||||
m_optgroup->hide_field(opt_key);
|
||||
supports_multiple_printers = opt && opt->value == htRepetier;
|
||||
supports_multiple_printers = opt->value == htRepetier || opt->value == htObico;
|
||||
|
||||
if (opt->value == htPrusaConnect) { // automatically show default prusaconnect address
|
||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
||||
if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(printhost_field->getWindow()); temp && temp->GetValue().IsEmpty()) {
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) {
|
||||
temp->SetValue(L"https://connect.prusa3d.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opt->value == htObico) {
|
||||
supports_multiple_printers = true;
|
||||
} else if (opt->value == htObico) { // automatically show default obico address
|
||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
||||
if (TextInput* temp_input = dynamic_cast<TextInput*>(printhost_field->getWindow()); temp_input) {
|
||||
if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp && temp->GetValue().IsEmpty()) {
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) {
|
||||
temp->SetValue(L"https://app.obico.io");
|
||||
m_config->opt_string("print_host") = "https://app.obico.io";
|
||||
}
|
||||
}
|
||||
} else if (opt->value == htSimplyPrint) {
|
||||
// Set the host url
|
||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
||||
printhost_field->disable();
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) {
|
||||
temp->SetValue("https://simplyprint.io");
|
||||
}
|
||||
m_config->opt_string("print_host") = "https://simplyprint.io";
|
||||
}
|
||||
if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) {
|
||||
printhost_webui_field->disable();
|
||||
if (wxTextCtrl* temp = dynamic_cast<TextCtrl*>(printhost_webui_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) {
|
||||
temp->SetValue("https://simplyprint.io/panel");
|
||||
}
|
||||
m_config->opt_string("print_host_webui") = "https://simplyprint.io/panel";
|
||||
}
|
||||
m_optgroup->hide_field("printhost_apikey");
|
||||
m_optgroup->disable_field("printhost_cafile");
|
||||
m_optgroup->disable_field("printhost_ssl_ignore_revoke");
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->Disable();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,6 +664,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect)
|
|||
|
||||
m_printhost_browse_btn->msw_rescale();
|
||||
m_printhost_test_btn->msw_rescale();
|
||||
m_printhost_logout_btn->msw_rescale();
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->msw_rescale();
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ class PhysicalPrinterDialog : public DPIDialog
|
|||
|
||||
ScalableButton* m_printhost_browse_btn {nullptr};
|
||||
ScalableButton* m_printhost_test_btn {nullptr};
|
||||
ScalableButton* m_printhost_logout_btn {nullptr};
|
||||
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
|
||||
ScalableButton* m_printhost_client_cert_browse_btn {nullptr};
|
||||
ScalableButton* m_printhost_port_browse_btn {nullptr};
|
||||
|
|
|
@ -1132,13 +1132,14 @@ void Sidebar::update_all_preset_comboboxes()
|
|||
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
|
||||
|
||||
bool is_bbl_vendor = preset_bundle.is_bbl_vendor();
|
||||
const bool use_bbl_network = preset_bundle.use_bbl_network();
|
||||
|
||||
// Orca:: show device tab based on vendor type
|
||||
auto p_mainframe = wxGetApp().mainframe;
|
||||
p_mainframe->show_device(is_bbl_vendor);
|
||||
p_mainframe->show_device(use_bbl_network);
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
|
||||
if (is_bbl_vendor) {
|
||||
if (use_bbl_network) {
|
||||
//only show connection button for not-BBL printer
|
||||
connection_btn->Hide();
|
||||
//only show sync-ams button for BBL printer
|
||||
|
@ -1270,7 +1271,7 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
|||
}
|
||||
|
||||
Preset& printer_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
bool isBBL = preset_bundle.is_bbl_vendor();
|
||||
bool isBBL = preset_bundle.use_bbl_network();
|
||||
wxGetApp().mainframe->show_calibration_button(!isBBL);
|
||||
|
||||
if (auto printer_structure_opt = printer_preset.config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
|
||||
|
@ -6836,7 +6837,7 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
|||
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->is_bbl_vendor() && new_sel == MainFrame::tpMonitor) {
|
||||
if (wxGetApp().preset_bundle && wxGetApp().preset_bundle->use_bbl_network() && new_sel == MainFrame::tpMonitor) {
|
||||
if (!wxGetApp().getAgent()) {
|
||||
e.Veto();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
namespace Slic3r { namespace GUI {
|
||||
|
||||
PrinterCloudAuthDialog::PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host)
|
||||
: wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "Login"), m_host(host)
|
||||
: wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "Login")
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
// Url
|
||||
|
@ -91,7 +91,6 @@ void PrinterCloudAuthDialog::OnScriptMessage(wxWebViewEvent& evt)
|
|||
wxString strCmd = j["command"];
|
||||
if (strCmd == "login_token") {
|
||||
auto token = j["data"]["token"];
|
||||
m_host->set_api_key(token);
|
||||
m_apikey = token;
|
||||
}
|
||||
Close();
|
||||
|
|
|
@ -29,7 +29,6 @@ protected:
|
|||
|
||||
wxString m_javascript;
|
||||
wxString m_response_js;
|
||||
PrintHost* m_host;
|
||||
std::string m_apikey;
|
||||
|
||||
public:
|
||||
|
|
|
@ -3546,6 +3546,7 @@ void TabPrinter::build_fff()
|
|||
optgroup = page->new_optgroup(L("Advanced"), L"param_advanced");
|
||||
optgroup->append_single_option_line("printer_structure");
|
||||
optgroup->append_single_option_line("gcode_flavor");
|
||||
optgroup->append_single_option_line("bbl_use_printhost");
|
||||
optgroup->append_single_option_line("disable_m73");
|
||||
option = optgroup->get_option("thumbnails");
|
||||
option.opt.full_width = true;
|
||||
|
@ -4188,7 +4189,7 @@ void TabPrinter::toggle_options()
|
|||
|
||||
// SoftFever: hide BBL specific settings
|
||||
for (auto el :
|
||||
{"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "bbl_calib_mark_logo"})
|
||||
{"scan_first_layer", "machine_load_filament_time", "machine_unload_filament_time", "bbl_calib_mark_logo", "bbl_use_printhost"})
|
||||
toggle_line(el, is_BBL_printer);
|
||||
|
||||
// SoftFever: hide non-BBL settings
|
||||
|
|
|
@ -42,8 +42,6 @@ Obico::Obico(DynamicPrintConfig* config) :
|
|||
|
||||
const char* Obico::get_name() const { return "Obico"; }
|
||||
|
||||
void Obico::set_api_key(const std::string auth_api_key) { m_apikey = auth_api_key; }
|
||||
|
||||
std::string Obico::get_host() const {
|
||||
return m_host;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ public:
|
|||
bool has_auto_discovery() const override { return false; }
|
||||
bool is_cloud() const override { return true; }
|
||||
bool get_login_url(wxString& auth_url) const override;
|
||||
void set_api_key(const std::string auth_api_key) override;
|
||||
std::string get_host() const override;
|
||||
|
||||
wxString get_test_ok_msg() const override;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "../GUI/PrintHostDialogs.hpp"
|
||||
#include "Obico.hpp"
|
||||
#include "Flashforge.hpp"
|
||||
#include "SimplyPrint.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::optional;
|
||||
|
@ -58,6 +59,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
|||
case htMKS: return new MKS(config);
|
||||
case htObico: return new Obico(config);
|
||||
case htFlashforge: return new Flashforge(config);
|
||||
case htSimplyPrint: return new SimplyPrint(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -72,8 +72,9 @@ public:
|
|||
|
||||
//Support for cloud webui login
|
||||
virtual bool is_cloud() const { return false; }
|
||||
virtual bool is_logged_in() const { return false; }
|
||||
virtual void log_out() const {}
|
||||
virtual bool get_login_url(wxString& auth_url) const { return false; }
|
||||
virtual void set_api_key(const std::string auth_api_key) {}
|
||||
|
||||
protected:
|
||||
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
|
||||
|
|
296
src/slic3r/Utils/SimplyPrint.cpp
Normal file
296
src/slic3r/Utils/SimplyPrint.cpp
Normal file
|
@ -0,0 +1,296 @@
|
|||
#include "SimplyPrint.hpp"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static constexpr boost::asio::ip::port_type CALLBACK_PORT = 21328;
|
||||
static const std::string CALLBACK_URL = "http://localhost:21328/callback";
|
||||
static const std::string RESPONSE_TYPE = "code";
|
||||
static const std::string CLIENT_ID = "simplyprintorcaslicer";
|
||||
static const std::string CLIENT_SCOPES = "user.read files.temp_upload";
|
||||
static const std::string OAUTH_CREDENTIAL_PATH = "simplyprint_oauth.json";
|
||||
static const std::string TOKEN_URL = "https://simplyprint.io/api/oauth2/Token";
|
||||
|
||||
static std::string generate_verification_code(int code_length = 32)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (auto i = 0; i < code_length; i++) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << (rand() % 0x100);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static std::string sha256b64(const std::string& inputStr)
|
||||
{
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
const unsigned char* data = (const unsigned char*) inputStr.c_str();
|
||||
SHA256(data, inputStr.size(), hash);
|
||||
|
||||
std::string b64;
|
||||
b64.resize(boost::beast::detail::base64::encoded_size(sizeof(hash)));
|
||||
b64.resize(boost::beast::detail::base64::encode(&b64[0], hash, sizeof(hash)));
|
||||
|
||||
// uses '-' instead of '+' and '_' instead of '/' for url-safe
|
||||
std::replace(b64.begin(), b64.end(), '+', '-');
|
||||
std::replace(b64.begin(), b64.end(), '/', '_');
|
||||
|
||||
// Stripping "=" is for RFC 7636 compliance
|
||||
b64.erase(std::remove(b64.begin(), b64.end(), '='), b64.end());
|
||||
|
||||
return b64;
|
||||
}
|
||||
|
||||
static std::string url_encode(const std::vector<std::pair<std::string, std::string>> query)
|
||||
{
|
||||
std::vector<std::string> q;
|
||||
q.reserve(query.size());
|
||||
|
||||
std::transform(query.begin(), query.end(), std::back_inserter(q), [](const auto& kv) {
|
||||
return Http::url_encode(kv.first) + "=" + Http::url_encode(kv.second);
|
||||
});
|
||||
|
||||
return boost::algorithm::join(q, "&");
|
||||
}
|
||||
|
||||
static void set_auth(Http& http, const std::string& access_token) { http.header("Authorization", "Bearer " + access_token); }
|
||||
|
||||
SimplyPrint::SimplyPrint(DynamicPrintConfig* config)
|
||||
{
|
||||
cred_file = (boost::filesystem::path(data_dir()) / OAUTH_CREDENTIAL_PATH).make_preferred().string();
|
||||
load_oauth_credential();
|
||||
}
|
||||
|
||||
GUI::OAuthParams SimplyPrint::get_oauth_params() const
|
||||
{
|
||||
const auto verification_code = generate_verification_code();
|
||||
// SimplyPrint uses S256 for PKCE
|
||||
const auto code_challenge = sha256b64(verification_code);
|
||||
const auto state = generate_verification_code();
|
||||
|
||||
const std::vector<std::pair<std::string, std::string>> query_parameters{
|
||||
{"client_id", CLIENT_ID},
|
||||
{"redirect_uri", CALLBACK_URL},
|
||||
{"scope", CLIENT_SCOPES},
|
||||
{"response_type", RESPONSE_TYPE},
|
||||
{"state", state},
|
||||
{"code_challenge", code_challenge},
|
||||
{"code_challenge_method", "S256"},
|
||||
};
|
||||
const auto login_url = (boost::format("https://simplyprint.io/panel/oauth2/authorize?%s") % url_encode(query_parameters)).str();
|
||||
|
||||
return GUI::OAuthParams{
|
||||
login_url,
|
||||
CLIENT_ID,
|
||||
CALLBACK_PORT,
|
||||
CALLBACK_URL,
|
||||
CLIENT_SCOPES,
|
||||
RESPONSE_TYPE,
|
||||
"https://simplyprint.io/login-success",
|
||||
"https://simplyprint.io/login-success",
|
||||
TOKEN_URL,
|
||||
verification_code,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
void SimplyPrint::load_oauth_credential()
|
||||
{
|
||||
cred.clear();
|
||||
if (boost::filesystem::exists(cred_file)) {
|
||||
nlohmann::json j;
|
||||
try {
|
||||
boost::nowide::ifstream ifs(cred_file);
|
||||
ifs >> j;
|
||||
ifs.close();
|
||||
|
||||
cred["access_token"] = j["access_token"];
|
||||
cred["refresh_token"] = j["refresh_token"];
|
||||
} catch (std::exception& err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << cred_file << " failed, reason = " << err.what();
|
||||
cred.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimplyPrint::save_oauth_credential(const GUI::OAuthResult& cred) const
|
||||
{
|
||||
nlohmann::json j;
|
||||
j["access_token"] = cred.access_token;
|
||||
j["refresh_token"] = cred.refresh_token;
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(cred_file, std::ios::out | std::ios::trunc);
|
||||
c << std::setw(4) << j << std::endl;
|
||||
c.close();
|
||||
}
|
||||
|
||||
wxString SimplyPrint::get_test_ok_msg() const { return _(L("Connected to SimplyPrint successfully!")); }
|
||||
|
||||
wxString SimplyPrint::get_test_failed_msg(wxString& msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to SimplyPrint"), msg.Truncate(256));
|
||||
}
|
||||
|
||||
void SimplyPrint::log_out() const
|
||||
{
|
||||
boost::nowide::remove(cred_file.c_str());
|
||||
}
|
||||
|
||||
bool SimplyPrint::do_api_call(std::function<Http(bool)> build_request,
|
||||
std::function<bool(std::string, unsigned)> on_complete,
|
||||
std::function<bool(std::string, std::string, unsigned)> on_error) const
|
||||
{
|
||||
if (cred.find("access_token") == cred.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
|
||||
const auto create_request = [this, &build_request, &res, &on_complete](const std::string& access_token, bool is_retry) {
|
||||
auto http = build_request(is_retry);
|
||||
set_auth(http, access_token);
|
||||
http.header("User-Agent", "SimplyPrint Orca Plugin")
|
||||
.on_complete([&](std::string body, unsigned http_status) {
|
||||
res = on_complete(body, http_status);
|
||||
});
|
||||
|
||||
return http;
|
||||
};
|
||||
|
||||
create_request(cred.at("access_token"), false)
|
||||
.on_error([&res, &on_error, this, &create_request](std::string body, std::string error, unsigned http_status) {
|
||||
if (http_status == 401) {
|
||||
// Refresh token
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("SimplyPrint: Access token invalid: %1%, HTTP %2%, body: `%3%`") % error %
|
||||
http_status % body;
|
||||
BOOST_LOG_TRIVIAL(info) << "SimplyPrint: Attempt to refresh access token";
|
||||
|
||||
auto http = Http::post(TOKEN_URL);
|
||||
http.timeout_connect(5)
|
||||
.timeout_max(5)
|
||||
.form_add("grant_type", "refresh_token")
|
||||
.form_add("client_id", CLIENT_ID)
|
||||
.form_add("refresh_token", cred.at("refresh_token"))
|
||||
.on_complete([this, &res, &on_error, &create_request](std::string body, unsigned http_status) {
|
||||
GUI::OAuthResult r;
|
||||
GUI::OAuthJob::parse_token_response(body, false, r);
|
||||
if (r.success) {
|
||||
BOOST_LOG_TRIVIAL(info) << "SimplyPrint: Successfully refreshed access token";
|
||||
this->save_oauth_credential(r);
|
||||
|
||||
// Run the api call again
|
||||
create_request(r.access_token, true)
|
||||
.on_error([&res, &on_error](std::string body, std::string error, unsigned http_status) {
|
||||
res = on_error(body, error, http_status);
|
||||
})
|
||||
.perform_sync();
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("SimplyPrint: Failed to refresh access token: %1%, body: `%2%`") % r.error_message % body;
|
||||
res = on_error(body, r.error_message, http_status);
|
||||
}
|
||||
})
|
||||
.on_error([&res, &on_error](std::string body, std::string error, unsigned http_status) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< boost::format("SimplyPrint: Failed to refresh access token: %1%, HTTP %2%, body: `%3%`") % error %
|
||||
http_status % body;
|
||||
res = on_error(body, error, http_status);
|
||||
})
|
||||
.perform_sync();
|
||||
} else {
|
||||
res = on_error(body, error, http_status);
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
bool SimplyPrint::test(wxString& curl_msg) const
|
||||
{
|
||||
if (cred.find("access_token") == cred.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return do_api_call(
|
||||
[](bool is_retry) {
|
||||
auto http = Http::get("https://api.simplyprint.io/oauth2/TokenInfo");
|
||||
http.header("Accept", "application/json");
|
||||
return http;
|
||||
},
|
||||
[](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: Got token info: %1%") % body;
|
||||
return true;
|
||||
},
|
||||
[](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error getting token info: %1%, HTTP %2%, body: `%3%`") % error %
|
||||
status % body;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool SimplyPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
if (cred.find("access_token") == cred.end()) {
|
||||
error_fn(_L("SimplyPrint account not linked. Go to Connect options to set it up."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If file is over 100 MB, fail
|
||||
if (boost::filesystem::file_size(upload_data.source_path) > 104857600ull) {
|
||||
error_fn(_L("File size exceeds the 100MB upload limit. Please upload your file through the panel."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto filename = upload_data.upload_path.filename().string();
|
||||
|
||||
return do_api_call(
|
||||
[&upload_data, &prorgess_fn, &filename](bool is_retry) {
|
||||
auto http = Http::post("https://simplyprint.io/api/files/TempUpload");
|
||||
http.form_add_file("file", upload_data.source_path.string(), filename)
|
||||
.on_progress([&prorgess_fn](Http::Progress progress, bool& cancel) { prorgess_fn(std::move(progress), cancel); });
|
||||
|
||||
return http;
|
||||
},
|
||||
[&error_fn, &filename](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("SimplyPrint: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
|
||||
// Get file UUID
|
||||
const auto j = nlohmann::json::parse(body, nullptr, false, true);
|
||||
if (j.is_discarded()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on token response: " << body;
|
||||
error_fn(_L("Unknown error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (j.find("uuid") == j.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "SimplyPrint: Invalid or no JSON data on token response: " << body;
|
||||
error_fn(_L("Unknown error"));
|
||||
return false;
|
||||
}
|
||||
const std::string uuid = j["uuid"];
|
||||
|
||||
// Launch external browser for file importing after uploading
|
||||
const auto url = "https://simplyprint.io/panel?" + url_encode({{"import", "tmp:" + uuid}, {"filename", filename}});
|
||||
wxLaunchDefaultBrowser(url);
|
||||
|
||||
return true;
|
||||
},
|
||||
[this, &error_fn](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("SimplyPrint: Error uploading file : %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
45
src/slic3r/Utils/SimplyPrint.hpp
Normal file
45
src/slic3r/Utils/SimplyPrint.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef slic3r_SimplyPrint_hpp_
|
||||
#define slic3r_SimplyPrint_hpp_
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "slic3r/GUI/Jobs/OAuthJob.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
class SimplyPrint : public PrintHost
|
||||
{
|
||||
std::string cred_file;
|
||||
std::map<std::string, std::string> cred;
|
||||
|
||||
void load_oauth_credential();
|
||||
|
||||
bool do_api_call(std::function<Http(bool /*is_retry*/)> build_request,
|
||||
std::function<bool(std::string /* body */, unsigned /* http_status */)> on_complete,
|
||||
std::function<bool(std::string /* body */, std::string /* error */, unsigned /* http_status */)> on_error) const;
|
||||
|
||||
public:
|
||||
SimplyPrint(DynamicPrintConfig* config);
|
||||
~SimplyPrint() override = default;
|
||||
|
||||
const char* get_name() const override { return "SimplyPrint"; }
|
||||
bool can_test() const override { return true; }
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool is_cloud() const override { return true; }
|
||||
std::string get_host() const override { return "https://simplyprint.io"; }
|
||||
|
||||
GUI::OAuthParams get_oauth_params() const;
|
||||
void save_oauth_credential(const GUI::OAuthResult& cred) const;
|
||||
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString& msg) const override;
|
||||
bool test(wxString& curl_msg) const override;
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::QueuePrint; }
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool is_logged_in() const override { return !cred.empty(); }
|
||||
void log_out() const override;
|
||||
};
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue