diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e47f898082..8f57c70f80 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -102,7 +102,9 @@ static const t_config_enum_values s_keys_map_PrintHostType { { "flashair", htFlashAir }, { "astrobox", htAstroBox }, { "repetier", htRepetier }, - { "mks", htMKS } + { "mks", htMKS }, + { "prusaconnectnew", htPrusaConnectNew }, + }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c14230d4f4..c48a8d6bb9 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -65,7 +65,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htPrusaLink, htPrusaConnect, htOctoPrint, htMoonraker, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htPrusaLink, htPrusaConnect, htOctoPrint, htMoonraker, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htPrusaConnectNew }; enum AuthorizationType { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7b56776032..479144dab9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -351,6 +351,8 @@ set(SLIC3R_GUI_SOURCES Utils/WifiScanner.cpp Utils/Secrets.hpp Utils/Secrets.cpp + Utils/PrusaConnect.hpp + Utils/PrusaConnect.cpp ) find_package(NanoSVG REQUIRED) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7a1191c972..8a460fe440 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -852,8 +852,9 @@ void MainFrame::create_preset_tabs() void MainFrame::add_connect_webview_tab() { - assert(!m_connect_webview_added); - // parameters of InsertPage (to prevent ambigous overloaded function) + if (m_connect_webview_added) { + return; + } // parameters of InsertPage (to prevent ambigous overloaded function) // insert to positon 4, if physical printer is already added, it moves to 5 // order of tabs: Plater - Print Settings - Filaments - Printers - Prusa Connect - Prusa Link size_t n = 4; @@ -867,7 +868,9 @@ void MainFrame::add_connect_webview_tab() } void MainFrame::remove_connect_webview_tab() { - assert(m_connect_webview_added); + if (!m_connect_webview_added) { + return; + } // connect tab should always be at position 4 if (m_tabpanel->GetSelection() == 4) m_tabpanel->SetSelection(0); @@ -877,7 +880,10 @@ void MainFrame::remove_connect_webview_tab() void MainFrame::add_printer_webview_tab(const wxString& url) { - assert(!m_printer_webview_added); + if (m_printer_webview_added) { + set_printer_webview_tab_url(url); + return; + } m_printer_webview_added = true; // add as the last (rightmost) panel dynamic_cast(m_tabpanel)->AddPage(m_printer_webview, L"Physical Printer", "", false); @@ -886,7 +892,9 @@ void MainFrame::add_printer_webview_tab(const wxString& url) } void MainFrame::remove_printer_webview_tab() { - assert(m_printer_webview_added); + if (!m_printer_webview_added) { + return; + } m_printer_webview_added = false; m_printer_webview->Hide(); // always remove the last tab @@ -894,7 +902,10 @@ void MainFrame::remove_printer_webview_tab() } void MainFrame::set_printer_webview_tab_url(const wxString& url) { - assert(m_printer_webview_added); + if (!m_printer_webview_added) { + add_printer_webview_tab(url); + return; + } m_printer_webview->clear(); m_printer_webview->set_default_url(url); if (m_tabpanel->GetSelection() == m_tabpanel->GetPageCount() - 1) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2f7ed80616..d95c34c631 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -892,8 +892,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); - this->main_frame->refresh_account_menu(); wxGetApp().update_login_dialog(); + this->show_action_buttons(this->ready_to_slice); }); this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) { @@ -908,6 +908,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // Update User name in TopBar this->main_frame->refresh_account_menu(); wxGetApp().update_login_dialog(); + this->show_action_buttons(this->ready_to_slice); } else { // data were corrupt and username was not retrieved // procced as if EVT_UA_RESET was recieved @@ -917,7 +918,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); this->main_frame->remove_connect_webview_tab(); // Update User name in TopBar - this->main_frame->refresh_account_menu(); + this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); } @@ -930,7 +931,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); this->main_frame->remove_connect_webview_tab(); // Update User name in TopBar - this->main_frame->refresh_account_menu(); + this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); }); @@ -944,6 +945,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, evt.data); }); + this->q->Bind(EVT_UA_CONNECT_USER_DATA_SUCCESS, [this](UserAccountSuccessEvent& evt) { + BOOST_LOG_TRIVIAL(error) << evt.data; + user_account->on_connect_user_data_success(evt.data); + }); #endif // 0 this->q->Bind(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, [this](UserAccountSuccessEvent& evt) { std::string text; @@ -965,7 +970,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) fclose(file); this->main_frame->refresh_account_menu(true); wxGetApp().update_login_dialog(); - }); + }); wxGetApp().other_instance_message_handler()->init(this->q); @@ -5852,15 +5857,17 @@ void Plater::connect_gcode() return; } if (dialog_msg.empty()) { + show_error(this, _L("Failed to select a printer. PrusaConnect did not return a value.")); return; } - BOOST_LOG_TRIVIAL(error) << dialog_msg; + BOOST_LOG_TRIVIAL(debug) << "Message from Printer pick webview: " << dialog_msg; std::string model_name = p->user_account->get_model_from_json(dialog_msg); std::string nozzle = p->user_account->get_nozzle_from_json(dialog_msg); assert(!model_name.empty()); - PresetBundle* preset_bundle = wxGetApp().preset_bundle; + PresetBundle* preset_bundle = wxGetApp().preset_bundle; const Preset* preset = preset_bundle->printers.find_system_preset_by_model_and_variant(model_name, nozzle); + // TODO: preset check // if selected (in connect) preset is not visible, make it visible and selected if (!preset->is_visible) { @@ -5875,8 +5882,7 @@ void Plater::connect_gcode() } // if selected (in connect) preset is not selected in slicer, select it - if (preset_bundle->printers.get_selected_preset_name() != preset->name) - { + if (preset_bundle->printers.get_selected_preset_name() != preset->name) { size_t preset_id = preset_bundle->printers.get_preset_idx_by_name(preset->name); assert(preset_id != size_t(-1)); preset_bundle->printers.select_preset(preset_id); @@ -5887,21 +5893,45 @@ void Plater::connect_gcode() return; } - // TODO: break if printer not ready - - // get api key from dialog_msg and upload the file - std::string api_key = p->user_account->get_apikey_from_json(dialog_msg); - if (api_key.empty()) - { + const std::string connect_state = p->user_account->get_keyword_from_json(dialog_msg, "connect_state"); + const std::string printer_state = p->user_account->get_keyword_from_json(dialog_msg, "printer_state"); + const std::map& printer_state_table = p->user_account->get_printer_state_table(); + const auto state = printer_state_table.find(connect_state); + assert(state != printer_state_table.end()); + // TODO: all states that does not allow to upload + if (state->second == ConnectPrinterState::CONNECT_PRINTER_OFFLINE) { + show_error(this, _L("Failed to select a printer. Chosen printer is in offline state.")); return; } - std::string connect_address = p->user_account->get_connect_address(); + + const std::string uuid = p->user_account->get_keyword_from_json(dialog_msg, "uuid"); + const std::string team_id = p->user_account->get_keyword_from_json(dialog_msg, "team_id"); + if (uuid.empty() || team_id.empty()) { + show_error(this, _L("Failed to select a printer. Missing data (uuid and team id) for chosen printer.")); + return; + } PhysicalPrinter ph_printer("connect_temp_printer", wxGetApp().preset_bundle->physical_printers.default_config(), *preset); - ph_printer.config.opt_string("printhost_apikey") = api_key; - ph_printer.config.opt_string("print_host") = connect_address; - ph_printer.config.set_key_value("host_type", new ConfigOptionEnum(htPrusaConnect)); + ph_printer.config.set_key_value("host_type", new ConfigOptionEnum(htPrusaConnectNew)); + // use existing structures to pass data + ph_printer.config.opt_string("printhost_apikey") = team_id; + ph_printer.config.opt_string("print_host") = uuid; DynamicPrintConfig* physical_printer_config = &ph_printer.config; + + + // Old PrusaConnect - requires prusaconnect_api_key in + //std::string api_key = p->user_account->get_keyword_from_json(dialog_msg, "prusaconnect_api_key"); + //if (api_key.empty()) { + // // TODO error dialog + // return; + //} + //const std::string connect_address = "https://dev.connect.prusa3d.com"; + + //PhysicalPrinter ph_printer("connect_temp_printer", wxGetApp().preset_bundle->physical_printers.default_config(), *preset); + //ph_printer.config.opt_string("printhost_apikey") = api_key; + //ph_printer.config.opt_string("print_host") = connect_address; + //ph_printer.config.set_key_value("host_type", new ConfigOptionEnum(htPrusaConnect)); + //DynamicPrintConfig* physical_printer_config = &ph_printer.config; send_gcode_inner(physical_printer_config); } diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp index f3f3b47721..af04a5fc87 100644 --- a/src/slic3r/GUI/UserAccount.cpp +++ b/src/slic3r/GUI/UserAccount.cpp @@ -31,7 +31,7 @@ void UserAccount::set_username(const std::string& username) void UserAccount::clear() { m_username = {}; - m_user_data.clear(); + m_account_user_data.clear(); m_printer_map.clear(); m_communication->do_clear(); } @@ -86,6 +86,10 @@ void UserAccount::enqueue_connect_dummy_action() { m_communication->enqueue_connect_dummy_action(); } +void UserAccount::enqueue_connect_user_data_action() +{ + m_communication->enqueue_connect_user_data_action(); +} #endif void UserAccount::enqueue_connect_printers_action() @@ -94,7 +98,7 @@ void UserAccount::enqueue_connect_printers_action() } void UserAccount::enqueue_avatar_action() { - m_communication->enqueue_avatar_action(m_user_data["avatar"]); + m_communication->enqueue_avatar_action(m_account_user_data["avatar"]); } bool UserAccount::on_login_code_recieved(const std::string& url_message) @@ -114,24 +118,24 @@ bool UserAccount::on_user_id_success(const std::string data, std::string& out_us BOOST_LOG_TRIVIAL(error) << "UserIDUserAction Could not parse server response."; return false; } - m_user_data.clear(); + m_account_user_data.clear(); for (const auto& section : ptree) { const auto opt = ptree.get_optional(section.first); if (opt) { BOOST_LOG_TRIVIAL(debug) << static_cast(section.first) << " " << *opt; - m_user_data[section.first] = *opt; + m_account_user_data[section.first] = *opt; } } - if (m_user_data.find("public_username") == m_user_data.end()) { + if (m_account_user_data.find("public_username") == m_account_user_data.end()) { BOOST_LOG_TRIVIAL(error) << "User ID message from PrusaAuth did not contain public_username. Login failed. Message data: " << data; return false; } - std::string public_username = m_user_data["public_username"]; + std::string public_username = m_account_user_data["public_username"]; set_username(public_username); out_username = public_username; // equeue GET with avatar url - if (m_user_data.find("avatar") != m_user_data.end()) { + if (m_account_user_data.find("avatar") != m_account_user_data.end()) { enqueue_avatar_action(); } else { @@ -168,7 +172,7 @@ namespace { } } -bool UserAccount::on_connect_printers_success(const std::string data, AppConfig* app_config, bool& out_printers_changed) +bool UserAccount::on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed) { BOOST_LOG_TRIVIAL(debug) << "PrusaConnect printers message: " << data; pt::ptree ptree; @@ -280,15 +284,15 @@ std::string UserAccount::get_nozzle_from_json(const std::string& message) const return out; } -std::string UserAccount::get_apikey_from_json(const std::string& message) const +std::string UserAccount::get_keyword_from_json(const std::string& json, const std::string& keyword) const { std::string out; try { - std::stringstream ss(message); + std::stringstream ss(json); pt::ptree ptree; pt::read_json(ss, ptree); - out = parse_tree_for_param(ptree, "prusaconnect_api_key"); + out = parse_tree_for_param(ptree, keyword); //assert(!out.empty()); } catch (const std::exception& e) { diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index c73b861b6f..982cf9ff02 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -18,6 +18,7 @@ enum class ConnectPrinterState { CONNECT_PRINTER_IDLE, CONNECT_PRINTER_FINISHED, CONNECT_PRINTER_READY, //? + CONNECT_PRINTER_ATTENTION, CONNECT_PRINTER_STATE_COUNT }; @@ -40,31 +41,34 @@ public: #if 0 void enqueue_user_id_action(); void enqueue_connect_dummy_action(); + void enqueue_connect_user_data_action(); #endif void enqueue_connect_printers_action(); void enqueue_avatar_action(); + // Clears all data and connections, called on logout or EVT_UA_RESET + void clear(); + // Functions called from UI where events emmited from UserAccountSession are binded // Returns bool if data were correctly proccessed bool on_login_code_recieved(const std::string& url_message); bool on_user_id_success(const std::string data, std::string& out_username); // Called on EVT_UA_FAIL, triggers test after several calls void on_communication_fail(); - // Clears all data and connections, called on logout or EVT_UA_RESET - void clear(); - bool on_connect_printers_success(const std::string data, AppConfig* app_config, bool& out_printers_changed); + bool on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed); std::string get_username() const { return m_username; } std::string get_access_token(); const ConnectPrinterStateMap& get_printer_state_map() const { return m_printer_map; } - const std::map get_user_data() const { return m_user_data; } - std::string get_connect_address() const { return "https://dev.connect.prusa3d.com"; } boost::filesystem::path get_avatar_path(bool logged) const; // standalone utility methods std::string get_model_from_json(const std::string& message) const; std::string get_nozzle_from_json(const std::string& message) const; - std::string get_apikey_from_json(const std::string& message) const; + //std::string get_apikey_from_json(const std::string& message) const; + std::string get_keyword_from_json(const std::string& json, const std::string& keyword) const; + + const std::map& get_printer_state_table() const { return printer_state_table; } private: void set_username(const std::string& username); @@ -73,7 +77,7 @@ private: std::unique_ptr m_communication; ConnectPrinterStateMap m_printer_map; - std::map m_user_data; + std::map m_account_user_data; std::string m_username; size_t m_fail_counter { 0 }; @@ -116,6 +120,7 @@ private: {"IDLE" , ConnectPrinterState::CONNECT_PRINTER_IDLE}, {"FINISHED" , ConnectPrinterState::CONNECT_PRINTER_FINISHED}, {"READY" , ConnectPrinterState::CONNECT_PRINTER_READY}, + {"ATTENTION", ConnectPrinterState::CONNECT_PRINTER_ATTENTION}, }; }; }} // namespace slic3r::GUI diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index 0f44828361..eb0f90241b 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -276,7 +276,6 @@ void UserAccountCommunication::enqueue_user_id_action() } wakeup_session_thread(); } - void UserAccountCommunication::enqueue_connect_dummy_action() { { @@ -289,6 +288,18 @@ void UserAccountCommunication::enqueue_connect_dummy_action() } wakeup_session_thread(); } +void UserAccountCommunication::enqueue_connect_user_data_action() +{ + { + std::lock_guard lock(m_session_mutex); + if (!m_session->is_initialized()) { + BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; + return; + } + m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA, nullptr, nullptr, {}); + } + wakeup_session_thread(); +} #endif 0 void UserAccountCommunication::enqueue_connect_printers_action() diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index 553973c41c..d89a051a1a 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -41,6 +41,7 @@ public: #if 0 void enqueue_user_id_action(); void enqueue_connect_dummy_action(); + void enqueue_connect_user_data_action(); #endif void enqueue_connect_printers_action(); void enqueue_avatar_action(const std::string url); diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 546d1656b6..416e8585f2 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -23,9 +23,12 @@ wxDEFINE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, UserAccountSuccessEvent); -wxDEFINE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent); +wxDEFINE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent); +#if 0 +wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); +#endif // 0 void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index f6e7f5dcc1..3336f6a618 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -24,6 +24,10 @@ wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only after some number of fails wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all +#if 0 +wxDECLARE_EVENT(EVT_UA_CONNECT_USER_DATA_SUCCESS, UserAccountSuccessEvent); +#endif // 0 + typedef std::function UserActionSuccessFn; typedef std::function UserActionFailFn; @@ -39,6 +43,7 @@ enum class UserAccountActionID { USER_ACCOUNT_ACTION_CONNECT_PRINTERS, USER_ACCOUNT_ACTION_AVATAR, #if 0 + USER_ACCOUNT_ACTION_CONNECT_USER_DATA, USER_ACCOUNT_ACTION_CONNECT_DUMMY, #endif // 0 @@ -115,6 +120,7 @@ public: m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTERS] = std::make_unique("CONNECT_PRINTERS", "https://dev.connect.prusa3d.com/slicer/printers", EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, EVT_UA_FAIL); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR] = std::make_unique("AVATAR", "https://test-media.printables.com/media/", EVT_UA_AVATAR_SUCCESS, EVT_UA_FAIL); #if 0 + m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA] = std::make_unique("CONNECT_USER_DATA", "https://dev.connect.prusa3d.com/app/login", EVT_UA_CONNECT_USER_DATA_SUCCESS, EVT_UA_FAIL); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DUMMY] = std::make_unique("CONNECT_DUMMY", "https://dev.connect.prusa3d.com/slicer/dummy", EVT_UA_SUCCESS, EVT_UA_FAIL); #endif // 0 @@ -130,6 +136,7 @@ public: m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTERS].reset(nullptr); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR].reset(nullptr); #if 0 + m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA].reset(nullptr); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DUMMY].reset(nullptr); #endif // 0 diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 70ccc50d7e..4e4faa1b7c 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -442,9 +442,11 @@ SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) : ConnectRequestHandler::ConnectRequestHandler() { m_actions["REQUEST_ACCESS_TOKEN"] = std::bind(&ConnectRequestHandler::on_request_access_token, this); + m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_request_config, this); m_actions["REQUEST_LANGUAGE"] = std::bind(&ConnectRequestHandler::on_request_language_action, this); m_actions["REQUEST_SESSION_ID"] = std::bind(&ConnectRequestHandler::on_request_session_id_action, this); m_actions["UPDATE_SELECTED_PRINTER"] = std::bind(&ConnectRequestHandler::on_request_update_selected_printer_action, this); + } ConnectRequestHandler::~ConnectRequestHandler() { @@ -489,8 +491,23 @@ void ConnectRequestHandler::on_request_access_token() wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token); run_script_bridge(script); } + +void ConnectRequestHandler::on_request_config() +{ + /* + accessToken?: string; + clientVersion?: string; + language?: ConnectLanguage; + sessionId?: string; + */ + const std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); + const std::string init_options = GUI::format("{\"accessToken\": \"%1%\" }", token); + wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options); + run_script_bridge(script); +} void ConnectRequestHandler::on_request_language_action() { + assert(true); // TODO: //std::string lang = "en"; //wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'en\')", lang); @@ -498,6 +515,7 @@ void ConnectRequestHandler::on_request_language_action() } void ConnectRequestHandler::on_request_session_id_action() { + assert(true); /* std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token); @@ -506,7 +524,7 @@ void ConnectRequestHandler::on_request_session_id_action() } ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/printer-list") + : WebViewPanel(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/") { } diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 9dd148a5fe..6a07fc70f2 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -115,6 +115,7 @@ public: protected: // action callbacs stored in m_actions virtual void on_request_access_token(); + virtual void on_request_config(); virtual void on_request_language_action(); virtual void on_request_session_id_action(); virtual void on_request_update_selected_printer_action() = 0; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index fc36a60d4e..2fde630960 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -29,6 +29,7 @@ #include "Repetier.hpp" #include "MKS.hpp" #include "Moonraker.hpp" +#include "PrusaConnect.hpp" #include "../GUI/PrintHostDialogs.hpp" namespace fs = boost::filesystem; @@ -63,6 +64,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htRepetier: return new Repetier(config); case htPrusaLink: return new PrusaLink(config); case htPrusaConnect: return new PrusaConnect(config); + case htPrusaConnectNew: return new PrusaConnectNew(config); case htMKS: return new MKS(config); case htMoonraker: return new Moonraker(config); default: return nullptr; diff --git a/src/slic3r/Utils/PrusaConnect.cpp b/src/slic3r/Utils/PrusaConnect.cpp new file mode 100644 index 0000000000..cf8ba300f2 --- /dev/null +++ b/src/slic3r/Utils/PrusaConnect.cpp @@ -0,0 +1,328 @@ +#include "PrusaConnect.hpp" + +#include "Http.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/UserAccount.hpp" + +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + + +namespace Slic3r { +namespace +{ +std::string escape_string(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size()); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +std::string escape_path_by_element(const boost::filesystem::path& path) +{ + std::string ret_val = escape_string(path.filename().string()); + boost::filesystem::path parent(path.parent_path()); + while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path. + { + ret_val = escape_string(parent.filename().string()) + "/" + ret_val; + parent = parent.parent_path(); + } + return ret_val; +} +} + +PrusaConnectNew::PrusaConnectNew(DynamicPrintConfig *config) + : m_uuid(config->opt_string("print_host")) + , m_team_id(config->opt_string("printhost_apikey")) +{} + +const char* PrusaConnectNew::get_name() const { return "PrusaConnectNew"; } + + +bool PrusaConnectNew::test(wxString& curl_msg) const +{ + // Test is not used by upload and gets list of files on a device. + const std::string name = get_name(); + std::string url = GUI::format("https://dev.connect.prusa3d.com/app/teams/%1%/files?printer_uuid=%2%", m_team_id, m_uuid); + const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); + BOOST_LOG_TRIVIAL(info) << GUI::format("%1%: Get files/raw at: %2%", name, url); + bool res = true; + + auto http = Http::get(std::move(url)); + http.header("Authorization", "Bearer " + access_token); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + res = false; + curl_msg = format_error(body, error, status); + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Got files/raw: %2%") % name % body; + }) + .perform_sync(); + + return res; + +} + +bool PrusaConnectNew::init_upload(PrintHostUpload upload_data, std::string& out) const +{ + // Register upload. Then upload must be performed immediately with returned "id" + bool res = true; + boost::system::error_code ec; + boost::uintmax_t size = boost::filesystem::file_size(upload_data.source_path, ec); + const std::string name = get_name(); + const std::string file_size = std::to_string(size); + const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); + const std::string escaped_upload_path = escape_path_by_element(upload_data.upload_path); + const std::string escaped_upload_filename = escape_path_by_element(upload_data.upload_path.filename()); + std::string url = GUI::format("%1%/app/users/teams/%2%/uploads", get_host(), m_team_id); + const std::string request_body_json = GUI::format( + "{" + "\"filename\": \"%1%\", " + "\"size\": %2%, " + "\"path\": \"%3%\", " + "\"force\": true, " + "\"printer_uuid\": \"%4%\"" + "}" + , escaped_upload_filename + , file_size + , upload_data.storage + "/" + escaped_upload_path + , m_uuid + ); + + BOOST_LOG_TRIVIAL(info) << "Register upload to "<< name<<". Url: " << url << "\nBody: " << request_body_json; + Http http = Http::post(std::move(url)); + http.header("Authorization", "Bearer " + access_token) + .header("Content-Type", "application/json") + .set_post_body(request_body_json) + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: File upload registered: HTTP %2%: %3%") % name % status % body; + out = body; + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << body; + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error registering file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + res = false; + out = GUI::into_u8(format_error(body, error, status)); + }) + .perform_sync(); + return res; +} + +bool PrusaConnectNew::upload(PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const +{ + std::string init_out; + if (!init_upload(upload_data, init_out)) + { + error_fn(std::move(GUI::from_u8(init_out))); + return false; + } + + // init reply format: {"id": 1234, "team_id": 12345, "name": "filename.gcode", "size": 123, "hash": "QhE0LD76vihC-F11Jfx9rEqGsk4.", "state": "INITIATED", "source": "CONNECT_USER", "path": "/usb/filename.bgcode"} + std::string upload_id; + try + { + std::stringstream ss(init_out); + pt::ptree ptree; + pt::read_json(ss, ptree); + const auto id_opt = ptree.get_optional("id"); + if (!id_opt) { + error_fn(std::move(_L("Failed to extract upload id from server reply."))); + return false; + } + upload_id = *id_opt; + } + catch (const std::exception&) + { + error_fn(std::move(_L("Failed to extract upload id from server reply."))); + return false; + } + const std::string name = get_name(); + const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); + const std::string escaped_upload_path = escape_string(upload_data.storage + "/" + upload_data.upload_path.string()); + const std::string to_print = upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"; + const std::string to_queue = upload_data.post_action == PrintHostPostUploadAction::QueuePrint ? "true" : "false"; + std::string url = GUI::format("%1%/app/teams/%2%/files/raw?upload_id=%3%&force=true&printer_uuid=%4%&path=%5%&to_print=%6%&to_queue=%7%", get_host(), m_team_id, upload_id, m_uuid, escaped_upload_path, to_print, to_queue); + bool res = true; + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%") + % name + % upload_data.source_path + % url + % upload_data.upload_path.filename().string() + % upload_data.upload_path.parent_path().string() + % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); + + Http http = Http::put(std::move(url)); + http.set_put_body(upload_data.source_path) + .header("Content-Type", "text/x.gcode") + .header("Authorization", "Bearer " + access_token) + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_progress([&](Http::Progress progress, bool& cancel) { + progress_fn(std::move(progress), cancel); + if (cancel) { + // Upload was canceled + BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled"; + res = false; + } + }) + .perform_sync(); + + return res; +} + +bool PrusaConnectNew::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const +{ + const char* name = get_name(); + bool res = true; + std::string url = GUI::format("%1%/app/printers/%2%/storages", get_host(), m_uuid); + const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token(); + wxString error_msg; + + struct StorageInfo { + wxString path; + wxString name; + bool read_only = false; + long long free_space = -1; + }; + std::vector storage; + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url; + + wxString wlang = GUI::wxGetApp().current_language_code(); + std::string lang = GUI::format(wlang.SubString(0, 1)); + + auto http = Http::get(std::move(url)); + http.header("Authorization", "Bearer " + access_token) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_msg = L"\n\n" + boost::nowide::widen(error); + res = false; + // If status is 0, the communication with the printer has failed completely (most likely a timeout), if the status is <= 400, it is an error returned by the pritner. + // If 0, we can show error to the user now, as we know the communication has failed. (res = true will do the trick.) + // if not 0, we must not show error, as not all printers support api/v1/storage endpoint. + // So we must be extra careful here, or we might be showing errors on perfectly fine communication. + if (status == 0) + res = true; + + }) + .on_complete([&](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body; + // {"storages": [{"mountpoint": "/usb", "name": "usb", "free_space": 16340844544, "type": "USB", "is_sfn": true, "read_only": false, "file_count": 1}]} + try + { + std::stringstream ss(body); + pt::ptree ptree; + pt::read_json(ss, ptree); + + // what if there is more structure added in the future? Enumerate all elements? + if (ptree.front().first != "storages") { + res = false; + return; + } + // each storage has own subtree of storage_list + for (const auto& section : ptree.front().second) { + const auto name = section.second.get_optional("name"); + const auto path = section.second.get_optional("mountpoint"); + const auto space = section.second.get_optional("free_space"); + const auto read_only = section.second.get_optional("read_only"); + const auto ro = section.second.get_optional("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro". + const auto available = section.second.get_optional("available"); + if (path && (!available || *available)) { + StorageInfo si; + si.path = boost::nowide::widen(*path); + si.name = name ? boost::nowide::widen(*name) : wxString(); + // If read_only is missing, assume it is NOT read only. + // si.read_only = read_only ? *read_only : false; // version without "ro" + si.read_only = (read_only ? *read_only : (ro ? *ro : false)); + si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space. + storage.emplace_back(std::move(si)); + } + } + } + catch (const std::exception&) + { + res = false; + } + }) + .perform_sync(); + + for (const auto& si : storage) { + if (!si.read_only && si.free_space > 0) { + storage_path.push_back(si.path); + storage_name.push_back(si.name); + } + } + + if (res && storage_path.empty()) { + if (!storage.empty()) { // otherwise error_msg is already filled + error_msg = L"\n\n" + _L("Storages found") + L": \n"; + for (const auto& si : storage) { + error_msg += GUI::format_wxstr(si.read_only ? + // TRN %1% = storage path + _L("%1% : read only") : + // TRN %1% = storage path + _L("%1% : no free space"), si.path) + L"\n"; + } + } + // TRN %1% = host + std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%. "), get_host()) + GUI::into_u8(error_msg); + BOOST_LOG_TRIVIAL(error) << message; + throw Slic3r::IOError(message); + } + + return res; +} + +wxString PrusaConnectNew::get_test_ok_msg() const +{ + return _L("Test OK."); +} +wxString PrusaConnectNew::get_test_failed_msg(wxString& msg) const +{ + return _L("Test NOK."); +} + +std::string PrusaConnectNew::get_team_id(const std::string& data) const +{ + boost::property_tree::ptree ptree; + try { + std::stringstream ss(data); + boost::property_tree::read_json(ss, ptree); + } + catch (const std::exception&) { + return {}; + } + + const auto team_id = ptree.get_optional("team_id"); + if (team_id) + { + return *team_id; + } + return {}; +} + +} diff --git a/src/slic3r/Utils/PrusaConnect.hpp b/src/slic3r/Utils/PrusaConnect.hpp new file mode 100644 index 0000000000..c569f0d566 --- /dev/null +++ b/src/slic3r/Utils/PrusaConnect.hpp @@ -0,0 +1,49 @@ +#ifndef slic3r_PrusaConnect_hpp_ +#define slic3r_PrusaConnect_hpp_ + +#include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" +/* +#include +#include +#include +#include + + +*/ + +namespace Slic3r { + +class DynamicPrintConfig; +class Http; + +class PrusaConnectNew : public PrintHost +{ +public: + PrusaConnectNew(DynamicPrintConfig *config); + ~PrusaConnectNew() override = default; + + const char* get_name() const override; + + virtual bool test(wxString &curl_msg) const override; + wxString get_test_ok_msg () const override; + wxString get_test_failed_msg (wxString &msg) const override; + bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; + bool has_auto_discovery() const override { return true; } + bool can_test() const override { return true; } + PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; } + std::string get_host() const override { return "https://dev.connect.prusa3d.com"; } + bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override; + //const std::string& get_apikey() const { return m_apikey; } + //const std::string& get_cafile() const { return m_cafile; } + +private: + std::string m_uuid; + std::string m_team_id; + + bool init_upload(PrintHostUpload upload_data, std::string& out) const; + std::string get_team_id(const std::string& data) const; +}; + +} +#endif