mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 23:17:35 -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",
|
"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",
|
"best_object_pos","head_wrap_detect_zone",
|
||||||
//SoftFever
|
//SoftFever
|
||||||
"host_type", "print_host", "printhost_apikey",
|
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost",
|
||||||
"print_host_webui",
|
"print_host_webui",
|
||||||
"printhost_cafile","printhost_port","printhost_authorization_type",
|
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||||
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
|
"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_name", // temporary option to compatibility with older Slicer
|
||||||
"preset_names",
|
"preset_names",
|
||||||
"printer_technology",
|
"printer_technology",
|
||||||
|
"bbl_use_printhost",
|
||||||
"host_type",
|
"host_type",
|
||||||
"print_host",
|
"print_host",
|
||||||
"print_host_webui",
|
"print_host_webui",
|
||||||
|
|
|
@ -344,6 +344,13 @@ VendorType PresetBundle::get_current_vendor_type()
|
||||||
return t;
|
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
|
//BBS: load project embedded presets
|
||||||
PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule)
|
PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||||
{
|
{
|
||||||
|
|
|
@ -97,6 +97,7 @@ public:
|
||||||
VendorType get_current_vendor_type();
|
VendorType get_current_vendor_type();
|
||||||
// Vendor related handy functions
|
// Vendor related handy functions
|
||||||
bool is_bbl_vendor() { return get_current_vendor_type() == VendorType::Marlin_BBL; }
|
bool is_bbl_vendor() { return get_current_vendor_type() == VendorType::Marlin_BBL; }
|
||||||
|
bool use_bbl_network();
|
||||||
|
|
||||||
//BBS: project embedded preset logic
|
//BBS: project embedded preset logic
|
||||||
PresetsConfigSubstitutions load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule);
|
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 },
|
{ "repetier", htRepetier },
|
||||||
{ "mks", htMKS },
|
{ "mks", htMKS },
|
||||||
{ "obico", htObico },
|
{ "obico", htObico },
|
||||||
{ "flashforge", htFlashforge}
|
{ "flashforge", htFlashforge },
|
||||||
|
{ "simplyprint", htSimplyPrint },
|
||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
|
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
|
||||||
|
|
||||||
|
@ -542,6 +543,13 @@ void PrintConfigDef::init_common_params()
|
||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionStrings());
|
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 = this->add("print_host", coString);
|
||||||
def->label = L("Hostname, IP or URL");
|
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 "
|
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("mks");
|
||||||
def->enum_values.push_back("obico");
|
def->enum_values.push_back("obico");
|
||||||
def->enum_values.push_back("flashforge");
|
def->enum_values.push_back("flashforge");
|
||||||
|
def->enum_values.push_back("simplyprint");
|
||||||
def->enum_labels.push_back("PrusaLink");
|
def->enum_labels.push_back("PrusaLink");
|
||||||
def->enum_labels.push_back("PrusaConnect");
|
def->enum_labels.push_back("PrusaConnect");
|
||||||
def->enum_labels.push_back("Octo/Klipper");
|
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("MKS");
|
||||||
def->enum_labels.push_back("Obico");
|
def->enum_labels.push_back("Obico");
|
||||||
def->enum_labels.push_back("Flashforge");
|
def->enum_labels.push_back("Flashforge");
|
||||||
|
def->enum_labels.push_back("SimplyPrint");
|
||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->cli = ConfigOptionDef::nocli;
|
def->cli = ConfigOptionDef::nocli;
|
||||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||||
|
|
|
@ -59,7 +59,7 @@ enum class FuzzySkinType {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PrintHostType {
|
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 {
|
enum AuthorizationType {
|
||||||
|
|
|
@ -532,6 +532,13 @@ set(SLIC3R_GUI_SOURCES
|
||||||
Utils/Obico.hpp
|
Utils/Obico.hpp
|
||||||
Utils/Flashforge.cpp
|
Utils/Flashforge.cpp
|
||||||
Utils/Flashforge.hpp
|
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)
|
if (WIN32)
|
||||||
|
|
|
@ -7,35 +7,179 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
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);
|
size_t start = url.find(key);
|
||||||
if (start < 0) return "";
|
if (start == std::string::npos) return "";
|
||||||
size_t eq = url.find('=', start);
|
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);
|
std::string key_str = url.substr(start, eq - start);
|
||||||
if (key_str != key)
|
if (key_str != key)
|
||||||
return "";
|
return "";
|
||||||
start += key.size() + 1;
|
start += key.size() + 1;
|
||||||
size_t end = url.find('&', start);
|
size_t end = url.find('&', start);
|
||||||
if (end < 0)
|
if (end == std::string::npos) end = url.length(); // Last param
|
||||||
return "";
|
|
||||||
std::string result = url.substr(start, end - start);
|
std::string result = url.substr(start, end - start);
|
||||||
return result;
|
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";
|
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
|
||||||
std::stringstream ssOut;
|
|
||||||
std::string url_str = Http::url_decode(url);
|
if (boost::contains(url, "access_token")) {
|
||||||
if (boost::contains(url_str, "access_token")) {
|
std::string redirect_url = url_get_param(url, "redirect_url");
|
||||||
std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
|
std::string access_token = url_get_param(url, "access_token");
|
||||||
std::string redirect_url = parse_params(url_str, "redirect_url");
|
std::string refresh_token = url_get_param(url, "refresh_token");
|
||||||
std::string access_token = parse_params(url_str, "access_token");
|
std::string expires_in_str = url_get_param(url, "expires_in");
|
||||||
std::string refresh_token = parse_params(url_str, "refresh_token");
|
std::string refresh_expires_in_str = url_get_param(url, "refresh_expires_in");
|
||||||
std::string expires_in_str = parse_params(url_str, "expires_in");
|
|
||||||
std::string refresh_expires_in_str = parse_params(url_str, "refresh_expires_in");
|
|
||||||
NetworkAgent* agent = wxGetApp().getAgent();
|
NetworkAgent* agent = wxGetApp().getAgent();
|
||||||
|
|
||||||
unsigned int http_code;
|
unsigned int http_code;
|
||||||
|
@ -72,79 +216,38 @@ std::string http_headers::get_response()
|
||||||
if (agent->is_user_login()) {
|
if (agent->is_user_login()) {
|
||||||
wxGetApp().request_user_login(1);
|
wxGetApp().request_user_login(1);
|
||||||
}
|
}
|
||||||
GUI::wxGetApp().CallAfter([this] {
|
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
|
||||||
wxGetApp().ShowUserLogin(false);
|
std::string location_str = (boost::format("%1%?result=success") % redirect_url).str();
|
||||||
});
|
return std::make_shared<ResponseRedirect>(location_str);
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
std::string error_str = "get_user_profile_error_" + std::to_string(result);
|
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();
|
std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str();
|
||||||
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
return std::make_shared<ResponseRedirect>(location_str);
|
||||||
ssOut << location_str << std::endl;
|
|
||||||
ssOut << "content-type: text/html" << std::endl;
|
|
||||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
|
||||||
ssOut << std::endl;
|
|
||||||
ssOut << sHTML;
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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 << "HTTP/1.1 404 Not Found" << std::endl;
|
||||||
ssOut << "content-type: text/html" << std::endl;
|
ssOut << "content-type: text/html" << std::endl;
|
||||||
ssOut << "content-length: " << sHTML.length() << std::endl;
|
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||||
ssOut << std::endl;
|
ssOut << std::endl;
|
||||||
ssOut << sHTML;
|
ssOut << sHTML;
|
||||||
}
|
}
|
||||||
return ssOut.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
|
||||||
void accept_and_run(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& io_service)
|
|
||||||
{
|
{
|
||||||
std::shared_ptr<session> sesh = std::make_shared<session>(io_service);
|
const std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
|
||||||
acceptor.async_accept(sesh->socket,
|
ssOut << "HTTP/1.1 302 Found" << std::endl;
|
||||||
[sesh, &acceptor, &io_service](const boost::beast::error_code& accept_error)
|
ssOut << "Location: " << location_str << std::endl;
|
||||||
{
|
ssOut << "content-type: text/html" << std::endl;
|
||||||
accept_and_run(acceptor, io_service);
|
ssOut << "content-length: " << sHTML.length() << std::endl;
|
||||||
if (!accept_error)
|
ssOut << std::endl;
|
||||||
{
|
ssOut << sHTML;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // GUI
|
} // GUI
|
||||||
|
|
|
@ -13,14 +13,12 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace boost::system;
|
|
||||||
using namespace boost::asio;
|
|
||||||
|
|
||||||
#define LOCALHOST_PORT 13618
|
#define LOCALHOST_PORT 13618
|
||||||
#define LOCALHOST_URL "http://localhost:"
|
#define LOCALHOST_URL "http://localhost:"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r { namespace GUI {
|
||||||
namespace GUI {
|
|
||||||
|
class session;
|
||||||
|
|
||||||
class http_headers
|
class http_headers
|
||||||
{
|
{
|
||||||
|
@ -31,14 +29,12 @@ class http_headers
|
||||||
std::map<std::string, std::string> headers;
|
std::map<std::string, std::string> headers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
std::string get_url() { return url; }
|
||||||
std::string get_response();
|
|
||||||
|
|
||||||
int content_length()
|
int content_length()
|
||||||
{
|
{
|
||||||
auto request = headers.find("content-length");
|
auto request = headers.find("content-length");
|
||||||
if (request != headers.end())
|
if (request != headers.end()) {
|
||||||
{
|
|
||||||
std::stringstream ssLength(request->second);
|
std::stringstream ssLength(request->second);
|
||||||
int content_length;
|
int content_length;
|
||||||
ssLength >> content_length;
|
ssLength >> content_length;
|
||||||
|
@ -71,82 +67,36 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class session
|
class HttpServer
|
||||||
{
|
{
|
||||||
boost::asio::streambuf buff;
|
boost::asio::ip::port_type port;
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
boost::asio::ip::tcp::socket socket;
|
class Response
|
||||||
|
|
||||||
session(io_service& io_service)
|
|
||||||
:socket(io_service)
|
|
||||||
{
|
{
|
||||||
}
|
public:
|
||||||
|
virtual ~Response() = default;
|
||||||
static void interact(std::shared_ptr<session> pThis)
|
virtual void write_response(std::stringstream& ssOut) = 0;
|
||||||
{
|
|
||||||
read_first_line(pThis);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpServer {
|
class ResponseNotFound : public Response
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
HttpServer();
|
~ResponseNotFound() override = default;
|
||||||
|
void write_response(std::stringstream& ssOut) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
boost::thread m_http_server_thread;
|
||||||
bool start_http_server = false;
|
bool start_http_server = false;
|
||||||
|
@ -154,9 +104,55 @@ public:
|
||||||
bool is_started() { return start_http_server; }
|
bool is_started() { return start_http_server; }
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
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
|
#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_enable = get_enable_print_status();
|
||||||
m_print_btn->Enable(m_print_enable);
|
m_print_btn->Enable(m_print_enable);
|
||||||
if (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));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
|
||||||
else
|
else
|
||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
||||||
|
@ -1600,7 +1600,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||||
SidePopup* p = new SidePopup(this);
|
SidePopup* p = new SidePopup(this);
|
||||||
|
|
||||||
if (wxGetApp().preset_bundle
|
if (wxGetApp().preset_bundle
|
||||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()) {
|
&& !wxGetApp().preset_bundle->use_bbl_network()) {
|
||||||
// ThirdParty Buttons
|
// ThirdParty Buttons
|
||||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||||
export_gcode_btn->SetCornerRadius(0);
|
export_gcode_btn->SetCornerRadius(0);
|
||||||
|
@ -3584,7 +3584,7 @@ void MainFrame::load_printer_url(wxString url, wxString apikey)
|
||||||
void MainFrame::load_printer_url()
|
void MainFrame::load_printer_url()
|
||||||
{
|
{
|
||||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||||
if (preset_bundle.is_bbl_vendor())
|
if (preset_bundle.use_bbl_network())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
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)
|
void OptionsGroup::set_name(const wxString& new_name)
|
||||||
{
|
{
|
||||||
stb->SetLabel(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 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 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);
|
void set_name(const wxString& new_name);
|
||||||
|
|
||||||
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
|
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
#include "BitmapCache.hpp"
|
#include "BitmapCache.hpp"
|
||||||
#include "BonjourDialog.hpp"
|
#include "BonjourDialog.hpp"
|
||||||
#include "MsgDialog.hpp"
|
#include "MsgDialog.hpp"
|
||||||
|
#include "OAuthDialog.hpp"
|
||||||
|
#include "SimplyPrint.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
@ -174,20 +176,24 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||||
result = host->test(msg);
|
result = host->test(msg);
|
||||||
|
|
||||||
if (!result && host->is_cloud()) {
|
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());
|
PrinterCloudAuthDialog dlg(this->GetParent(), host.get());
|
||||||
dlg.ShowModal();
|
dlg.ShowModal();
|
||||||
|
|
||||||
auto api_key = dlg.GetApiKey();
|
const auto api_key = dlg.GetApiKey();
|
||||||
m_config->opt_string("printhost_apikey") = api_key;
|
m_config->opt_string("printhost_apikey") = api_key;
|
||||||
result = !api_key.empty();
|
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!"));
|
show_info(this, host->get_test_ok_msg(), _L("Success!"));
|
||||||
else
|
else
|
||||||
show_error(this, host->get_test_failed_msg(msg));
|
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;
|
return sizer;
|
||||||
|
@ -215,6 +246,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||||
Line host_line = m_optgroup->create_single_option_line(option);
|
Line host_line = m_optgroup->create_single_option_line(option);
|
||||||
host_line.append_widget(printhost_browse);
|
host_line.append_widget(printhost_browse);
|
||||||
host_line.append_widget(print_host_test);
|
host_line.append_widget(print_host_test);
|
||||||
|
host_line.append_widget(print_host_logout);
|
||||||
m_optgroup->append_line(host_line);
|
m_optgroup->append_line(host_line);
|
||||||
|
|
||||||
option = m_optgroup->get_option("print_host_webui");
|
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));
|
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||||
if (host) {
|
if (host) {
|
||||||
m_printhost_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
|
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");
|
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||||
m_optgroup->show_field("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 (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());
|
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());
|
temp->SetValue(wxString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,25 +540,42 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||||
m_optgroup->show_field("printhost_apikey", true);
|
m_optgroup->show_field("printhost_apikey", true);
|
||||||
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
|
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
|
||||||
m_optgroup->hide_field(opt_key);
|
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 (opt->value == htPrusaConnect) { // automatically show default prusaconnect address
|
||||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
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");
|
temp->SetValue(L"https://connect.prusa3d.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (opt->value == htObico) { // automatically show default obico address
|
||||||
}
|
|
||||||
|
|
||||||
if (opt->value == htObico) {
|
|
||||||
supports_multiple_printers = true;
|
|
||||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
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 = dynamic_cast<TextCtrl*>(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) {
|
||||||
if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp && temp->GetValue().IsEmpty()) {
|
|
||||||
temp->SetValue(L"https://app.obico.io");
|
temp->SetValue(L"https://app.obico.io");
|
||||||
m_config->opt_string("print_host") = "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_browse_btn->msw_rescale();
|
||||||
m_printhost_test_btn->msw_rescale();
|
m_printhost_test_btn->msw_rescale();
|
||||||
|
m_printhost_logout_btn->msw_rescale();
|
||||||
if (m_printhost_cafile_browse_btn)
|
if (m_printhost_cafile_browse_btn)
|
||||||
m_printhost_cafile_browse_btn->msw_rescale();
|
m_printhost_cafile_browse_btn->msw_rescale();
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class PhysicalPrinterDialog : public DPIDialog
|
||||||
|
|
||||||
ScalableButton* m_printhost_browse_btn {nullptr};
|
ScalableButton* m_printhost_browse_btn {nullptr};
|
||||||
ScalableButton* m_printhost_test_btn {nullptr};
|
ScalableButton* m_printhost_test_btn {nullptr};
|
||||||
|
ScalableButton* m_printhost_logout_btn {nullptr};
|
||||||
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
|
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
|
||||||
ScalableButton* m_printhost_client_cert_browse_btn {nullptr};
|
ScalableButton* m_printhost_client_cert_browse_btn {nullptr};
|
||||||
ScalableButton* m_printhost_port_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();
|
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
|
||||||
|
|
||||||
bool is_bbl_vendor = preset_bundle.is_bbl_vendor();
|
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
|
// Orca:: show device tab based on vendor type
|
||||||
auto p_mainframe = wxGetApp().mainframe;
|
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;
|
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
|
//only show connection button for not-BBL printer
|
||||||
connection_btn->Hide();
|
connection_btn->Hide();
|
||||||
//only show sync-ams button for BBL printer
|
//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();
|
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);
|
wxGetApp().mainframe->show_calibration_button(!isBBL);
|
||||||
|
|
||||||
if (auto printer_structure_opt = printer_preset.config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
|
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;
|
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
|
||||||
update_sidebar();
|
update_sidebar();
|
||||||
int old_sel = e.GetOldSelection();
|
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()) {
|
if (!wxGetApp().getAgent()) {
|
||||||
e.Veto();
|
e.Veto();
|
||||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
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 {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
PrinterCloudAuthDialog::PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host)
|
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);
|
SetBackgroundColour(*wxWHITE);
|
||||||
// Url
|
// Url
|
||||||
|
@ -91,7 +91,6 @@ void PrinterCloudAuthDialog::OnScriptMessage(wxWebViewEvent& evt)
|
||||||
wxString strCmd = j["command"];
|
wxString strCmd = j["command"];
|
||||||
if (strCmd == "login_token") {
|
if (strCmd == "login_token") {
|
||||||
auto token = j["data"]["token"];
|
auto token = j["data"]["token"];
|
||||||
m_host->set_api_key(token);
|
|
||||||
m_apikey = token;
|
m_apikey = token;
|
||||||
}
|
}
|
||||||
Close();
|
Close();
|
||||||
|
|
|
@ -29,7 +29,6 @@ protected:
|
||||||
|
|
||||||
wxString m_javascript;
|
wxString m_javascript;
|
||||||
wxString m_response_js;
|
wxString m_response_js;
|
||||||
PrintHost* m_host;
|
|
||||||
std::string m_apikey;
|
std::string m_apikey;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -3546,6 +3546,7 @@ void TabPrinter::build_fff()
|
||||||
optgroup = page->new_optgroup(L("Advanced"), L"param_advanced");
|
optgroup = page->new_optgroup(L("Advanced"), L"param_advanced");
|
||||||
optgroup->append_single_option_line("printer_structure");
|
optgroup->append_single_option_line("printer_structure");
|
||||||
optgroup->append_single_option_line("gcode_flavor");
|
optgroup->append_single_option_line("gcode_flavor");
|
||||||
|
optgroup->append_single_option_line("bbl_use_printhost");
|
||||||
optgroup->append_single_option_line("disable_m73");
|
optgroup->append_single_option_line("disable_m73");
|
||||||
option = optgroup->get_option("thumbnails");
|
option = optgroup->get_option("thumbnails");
|
||||||
option.opt.full_width = true;
|
option.opt.full_width = true;
|
||||||
|
@ -4188,7 +4189,7 @@ void TabPrinter::toggle_options()
|
||||||
|
|
||||||
// SoftFever: hide BBL specific settings
|
// SoftFever: hide BBL specific settings
|
||||||
for (auto el :
|
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);
|
toggle_line(el, is_BBL_printer);
|
||||||
|
|
||||||
// SoftFever: hide non-BBL settings
|
// SoftFever: hide non-BBL settings
|
||||||
|
|
|
@ -42,8 +42,6 @@ Obico::Obico(DynamicPrintConfig* config) :
|
||||||
|
|
||||||
const char* Obico::get_name() const { return "Obico"; }
|
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 {
|
std::string Obico::get_host() const {
|
||||||
return m_host;
|
return m_host;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ public:
|
||||||
bool has_auto_discovery() const override { return false; }
|
bool has_auto_discovery() const override { return false; }
|
||||||
bool is_cloud() const override { return true; }
|
bool is_cloud() const override { return true; }
|
||||||
bool get_login_url(wxString& auth_url) const override;
|
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;
|
std::string get_host() const override;
|
||||||
|
|
||||||
wxString get_test_ok_msg() const override;
|
wxString get_test_ok_msg() const override;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "../GUI/PrintHostDialogs.hpp"
|
#include "../GUI/PrintHostDialogs.hpp"
|
||||||
#include "Obico.hpp"
|
#include "Obico.hpp"
|
||||||
#include "Flashforge.hpp"
|
#include "Flashforge.hpp"
|
||||||
|
#include "SimplyPrint.hpp"
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
|
@ -58,6 +59,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||||
case htMKS: return new MKS(config);
|
case htMKS: return new MKS(config);
|
||||||
case htObico: return new Obico(config);
|
case htObico: return new Obico(config);
|
||||||
case htFlashforge: return new Flashforge(config);
|
case htFlashforge: return new Flashforge(config);
|
||||||
|
case htSimplyPrint: return new SimplyPrint(config);
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -72,8 +72,9 @@ public:
|
||||||
|
|
||||||
//Support for cloud webui login
|
//Support for cloud webui login
|
||||||
virtual bool is_cloud() const { return false; }
|
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 bool get_login_url(wxString& auth_url) const { return false; }
|
||||||
virtual void set_api_key(const std::string auth_api_key) {}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const;
|
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