diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index cc7f448534..fac7870707 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "Platform.hpp" #include "Time.hpp" @@ -941,8 +942,25 @@ unsigned get_current_pid() { #ifdef WIN32 return GetCurrentProcessId(); -#else +#elif __APPLE__ return ::getpid(); +#else + // On flatpak getpid() might return same number for each concurent instances. + static std::atomic instance_uuid{0}; + if (instance_uuid == 0) { + unsigned generated_value; + { + // Use a thread-local random engine + thread_local std::random_device rd; + thread_local std::mt19937 generator(rd()); + std::uniform_int_distribution distribution; + generated_value = distribution(generator); + } + unsigned expected = 0; + // Atomically initialize the instance_uuid if it has not been set + instance_uuid.compare_exchange_strong(expected, generated_value); + } + return instance_uuid.load(); #endif } diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 882e2e3259..aec1db8223 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -5,6 +5,7 @@ #include "GUI_App.hpp" #include "InstanceCheck.hpp" #include "Plater.hpp" +#include "format.hpp" #ifdef _WIN32 #include "MainFrame.hpp" @@ -16,12 +17,15 @@ #include "boost/nowide/convert.hpp" #include #include +#include +#include #include #include #include #include #include #include +#include #ifdef _WIN32 #include @@ -66,9 +70,7 @@ namespace instance_check_internal static CommandLineAnalysis process_command_line(int argc, char** argv) { CommandLineAnalysis ret; - //if (argc < 2) - // return ret; - std::vector arguments { argv[0] }; + std::vector arguments { argv[0] }; bool send_if_url = false; bool has_url = false; for (int i = 1; i < argc; ++i) { @@ -90,14 +92,22 @@ namespace instance_check_internal if (send_if_url && has_url) { ret.should_send = true; } - ret.cl_string = escape_strings_cstyle(arguments); + // We do now want escape_strings_cstyle that quotes strings + // It would not be possible to use inside json + for (const std::string& arg : arguments) { + ret.cl_string += escape_string_cstyle(arg); + ret.cl_string += ";"; + } BOOST_LOG_TRIVIAL(info) << "single instance: " << (ret.should_send.has_value() ? (*ret.should_send ? "true" : "false") : "undefined") << ". other params: " << ret.cl_string; return ret; } - + std::string compose_message_json(const std::string& type, const std::string& data) + { + return GUI::format("{ \"type\" : \"%1%\" , \"data\" : \"%2%\"}", type, data); + } #ifdef _WIN32 @@ -168,6 +178,67 @@ namespace instance_check_internal return false; } + static BOOL CALLBACK enum_windows_process_multicast(_In_ HWND hwnd, _In_ LPARAM lParam) + { + if (hwnd == GUI::wxGetApp().mainframe->GetHandle()) { + return true; + } + + TCHAR wndText[1000]; + TCHAR className[1000]; + int err; + err = GetClassName(hwnd, className, 1000); + if (err == 0) + return true; + err = GetWindowText(hwnd, wndText, 1000); + if (err == 0) + return true; + std::wstring classNameString(className); + std::wstring wndTextString(wndText); + if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") { + //check if other instances has same instance hash + //if not it is not same version(binary) as this version + HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor"); + uint64_t other_instance_hash = PtrToUint(handle); + uint64_t other_instance_hash_major; + uint64_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int(); + handle = GetProp(hwnd, L"Instance_Hash_Major"); + other_instance_hash_major = PtrToUint(handle); + other_instance_hash_major = other_instance_hash_major << 32; + other_instance_hash += other_instance_hash_major; + handle = GetProp(hwnd, L"Instance_Is_Maximized"); + const bool maximized = PtrToUint(handle) == 1; + + if (my_instance_hash == other_instance_hash) + { + BOOST_LOG_TRIVIAL(debug) << "win multicast enum - found instance " << hwnd; + std::wstring multicast_message = *reinterpret_cast(lParam); + std::unique_ptr message = std::make_unique(const_cast(multicast_message.c_str())); + + //Create a COPYDATASTRUCT to send the information + //cbData represents the size of the information we want to send. + //lpData represents the information we want to send. + //dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA). + COPYDATASTRUCT data_to_send = { 0 }; + data_to_send.dwData = 1; + data_to_send.cbData = sizeof(TCHAR) * (wcslen(*message.get()) + 1); + data_to_send.lpData = *message.get(); + SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send); + + return true; + } + BOOST_LOG_TRIVIAL(trace) << "win enum - found wrong instance"; + } + return true; + } + + static void multicast_message_inner(const std::string& message) + { + // multicast_message must live until EnumWindows is done, it is passed as pointer parameter. + std::wstring multicast_message = boost::nowide::widen(message); + EnumWindows(enum_windows_process_multicast, reinterpret_cast(&multicast_message)); + } + #else static bool get_lock(const std::string& name, const std::string& path) @@ -228,6 +299,11 @@ namespace instance_check_internal #endif //WIN32 #if defined(__APPLE__) + static void multicast_message_inner(const std::string &message_text) + { + multicast_message_mac(message_text); + } + static bool send_message(const std::string &message_text, const std::string &version) { //std::string v(version); @@ -242,6 +318,163 @@ namespace instance_check_internal #elif defined(__linux__) + static void list_matching_objects(const std::string& pattern, std::vector& result) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + DBusConnection* connection; + DBusError error; + + // Initialize the D-Bus error. + dbus_error_init(&error); + + // Connect to the session bus. + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection) { + BOOST_LOG_TRIVIAL(error) << "Failed to connect to the D-Bus session bus: " << error.message; + dbus_error_free(&error); + return; + } + + // Request a list of all bus names. + DBusMessage* message = dbus_message_new_method_call( + "org.freedesktop.DBus", // Destination (the D-Bus daemon) + "/org/freedesktop/DBus", // Object path + "org.freedesktop.DBus", // Interface + "ListNames" // Method + ); + + if (!message) { + BOOST_LOG_TRIVIAL(error) << "Failed to create D-Bus message."; + return; + } + + DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, message, -1, &error); + dbus_message_unref(message); + + if (!reply) { + BOOST_LOG_TRIVIAL(error) << "Failed to send message: " << error.message; + dbus_error_free(&error); + return; + } + + // Parse the reply. + DBusMessageIter args; + if (!dbus_message_iter_init(reply, &args)) { + BOOST_LOG_TRIVIAL(error) << "Reply does not contain arguments."; + dbus_message_unref(reply); + return; + } + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) { + BOOST_LOG_TRIVIAL(error) << "Unexpected argument type in reply."; + dbus_message_unref(reply); + return; + } + + DBusMessageIter array_iter; + dbus_message_iter_recurse(&args, &array_iter); + + std::regex instance_regex(pattern); + + while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) { + const char* name; + dbus_message_iter_get_basic(&array_iter, &name); + if (std::regex_match(name, instance_regex)) { + result.push_back(name); + BOOST_LOG_TRIVIAL(debug) << "Matching object found: " << name; + } + dbus_message_iter_next(&array_iter); + } + + dbus_message_unref(reply); + dbus_error_free(&error); + } + + + static bool multicast_one_message(const std::string &message_text, const std::string &interface_name) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " to " << interface_name; + DBusMessage* msg; + DBusConnection* conn; + DBusError err; + dbus_uint32_t serial = 0; + const char* sigval = message_text.c_str(); + std::string method_name = "Message"; + //std::string interface_name = "com.prusa3d.prusaslicer.MulticastListener.Object" + reciever_id; + //std::string object_name = "/com/prusa3d/prusaslicer/MulticastListener/Object" + reciever_id; + std::string object_name = "/" + interface_name; + std::replace(object_name.begin(), object_name.end(), '.', '/'); + + // initialise the error value + dbus_error_init(&err); + // connect to bus, and check for errors (use SESSION bus everywhere!) + conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send."; + BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: " << err.message; + dbus_error_free(&err); + return false; + } + if (NULL == conn) { + BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send."; + return false; + } + //some sources do request interface ownership before constructing msg but i think its wrong. + //create new method call message + msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str()); + if (NULL == msg) { + BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send."; + dbus_connection_unref(conn); + return false; + } + //the Message method is not sending reply. + dbus_message_set_no_reply(msg, TRUE); + //append arguments to message + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) { + BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send."; + dbus_message_unref(msg); + dbus_connection_unref(conn); + return false; + } + // send the message and flush the connection + if (!dbus_connection_send(conn, msg, &serial)) { + BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message."; + dbus_message_unref(msg); + dbus_connection_unref(conn); + return false; + } + dbus_connection_flush(conn); + BOOST_LOG_TRIVIAL(trace) << "DBus message sent."; + // free the message and close the connection + dbus_message_unref(msg); + dbus_connection_unref(conn); + return true; + } + + static void multicast_message_inner(const std::string &message_text) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + std::string pattern = R"(com\.prusa3d\.prusaslicer\.MulticastListener\.Object\d+)"; + std::vector instances; + std::string my_pid = std::to_string(get_current_pid()); + + list_matching_objects(pattern, instances); + for (const std::string& instance : instances) { + // regex object pid to not send message to myself. + std::regex objectRegex("Object(\\d+)"); + std::smatch match; + if (std::regex_search(instance, match, objectRegex) && match[1] != my_pid) { + if (!multicast_one_message(message_text, instance)) { + BOOST_LOG_TRIVIAL(error) << "Failed send DBUS message to " << instance; + } else { + BOOST_LOG_TRIVIAL(debug) << "Successfully sent DBUS message to " << instance; + } + } + } + + } + + static bool send_message(const std::string &message_text, const std::string &version) { /*std::string v(version); @@ -372,7 +605,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance) // get_lock() creates the lockfile therefore *cla.should_send is checked after if (instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/") && *cla.should_send) { #endif - instance_check_internal::send_message(cla.cl_string, lock_name); + instance_check_internal::send_message(instance_check_internal::compose_message_json("CLI", cla.cl_string), lock_name); BOOST_LOG_TRIVIAL(error) << "Instance check: Another instance found. This instance will terminate. Lock file of current running instance is located at " << data_dir() << #ifdef _WIN32 "\\cache\\" @@ -394,6 +627,7 @@ wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent); wxDEFINE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent); wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); +wxDEFINE_EVENT(EVT_STORE_READ_REQUEST, SimpleEvent); void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) { @@ -410,7 +644,8 @@ void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) #endif //__APPLE__ #ifdef BACKGROUND_MESSAGE_LISTENER - m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this))); + m_instance_check_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen_instance_check, this))); + m_multicast_listener_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen_multicast, this))); #endif //BACKGROUND_MESSAGE_LISTENER } void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame) @@ -432,17 +667,30 @@ void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame) this->unregister_for_messages(); #endif //__APPLE__ #ifdef BACKGROUND_MESSAGE_LISTENER - if (m_thread.joinable()) { + if (m_instance_check_thread.joinable()) { // Stop the worker thread, if running. { // Notify the worker thread to cancel wait on detection polling. - std::lock_guard lck(m_thread_stop_mutex); - m_stop = true; + std::lock_guard lck(m_instance_check_thread_stop_mutex); + m_instance_check_thread_stop = true; } - m_thread_stop_condition.notify_all(); + m_instance_check_thread_stop_condition.notify_all(); // Wait for the worker thread to stop. - m_thread.join(); - m_stop = false; + m_instance_check_thread.join(); + m_instance_check_thread_stop = false; + } + + if (m_multicast_listener_thread.joinable()) { + // Stop the worker thread, if running. + { + // Notify the worker thread to cancel wait on detection polling. + std::lock_guard lck(m_multicast_listener_thread_stop_mutex); + m_multicast_listener_thread_stop = true; + } + m_multicast_listener_thread_stop_condition.notify_all(); + // Wait for the worker thread to stop. + m_multicast_listener_thread.join(); + m_multicast_listener_thread_stop = false; } #endif //BACKGROUND_MESSAGE_LISTENER m_callback_evt_handler = nullptr; @@ -525,15 +773,20 @@ namespace MessageHandlerInternal } } //namespace MessageHandlerInternal -void OtherInstanceMessageHandler::handle_message(const std::string& message) +void OtherInstanceMessageHandler::multicast_message(const std::string& message_type, const std::string& message_data) { - BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_type; + instance_check_internal::multicast_message_inner(instance_check_internal::compose_message_json(message_type, message_data)); +} - std::vector args; - bool parsed = unescape_strings_cstyle(message, args); + +void OtherInstanceMessageHandler::handle_message_type_cli(const std::string& data) +{ + std::vector args; + bool parsed = unescape_strings_cstyle(data, args); assert(parsed); if (! parsed) { - BOOST_LOG_TRIVIAL(error) << "message from other instance is incorrectly formatted: " << message; + BOOST_LOG_TRIVIAL(error) << "message from other instance is incorrectly formatted: " << data; return; } @@ -542,10 +795,10 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message) // Skip the first argument, it is the path to the slicer executable. auto it = args.begin(); for (++ it; it != args.end(); ++ it) { + BOOST_LOG_TRIVIAL(debug) << *it; boost::filesystem::path p = MessageHandlerInternal::get_path(*it); if (! p.string().empty()) paths.emplace_back(p); -// TODO: There is a misterious slash appearing in recieved msg on windows #ifdef _WIN32 else if (it->rfind("prusaslicer://open/?file=", 0) == 0) #else @@ -557,16 +810,45 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message) } } if (! paths.empty()) { - //wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here? - //if (evt_handler) { - wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector(std::move(paths)))); - //} + wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector(std::move(paths)))); } - if (!downloads.empty()) - { + if (!downloads.empty()) { wxPostEvent(m_callback_evt_handler, StartDownloadOtherInstanceEvent(GUI::EVT_START_DOWNLOAD_OTHER_INSTANCE, std::vector(std::move(downloads)))); } } +void OtherInstanceMessageHandler::handle_message_type_store_read(const std::string& data) +{ + wxPostEvent(m_callback_evt_handler, SimpleEvent(GUI::EVT_STORE_READ_REQUEST)); +} + +void OtherInstanceMessageHandler::handle_message(const std::string& message) +{ + BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message; + // message in format { "type" : "TYPE", "data" : "data" } + // types: CLI, STORE_READ + std::string type; + std::string data; + try { + std::stringstream ss(message); + boost::property_tree::ptree ptree; + boost::property_tree::read_json(ss, ptree); + if (const auto action = ptree.get_optional("type"); action) { + type = *action; + } + if (const auto data_opt = ptree.get_optional("data"); data_opt) { + data = *data_opt; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse other instance message: " << e.what(); + return; + } + assert(!type.empty()); + assert(m_message_handlers.find(type) != m_message_handlers.end()); // this assert means there is an message type that has no handling. + if (m_message_handlers.find(type) != m_message_handlers.end()) { + m_message_handlers[type](data); + } +} #ifdef __APPLE__ void OtherInstanceMessageHandler::handle_message_other_closed() @@ -577,7 +859,7 @@ void OtherInstanceMessageHandler::handle_message_other_closed() #ifdef BACKGROUND_MESSAGE_LISTENER -namespace MessageHandlerDBusInternal +namespace InstanceCheckMessageHandlerDBusInternal { //reply to introspect makes our DBus object visible for other programs like D-Feet static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) @@ -638,26 +920,27 @@ namespace MessageHandlerDBusInternal std::string our_interface = "com.prusa3d.prusaslicer.InstanceCheck.Object" + wxGetApp().get_instance_hash_string(); BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name; if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) { - respond_to_introspect(connection, message); + InstanceCheckMessageHandlerDBusInternal::respond_to_introspect(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstance", member_name)) { - handle_method_another_instance(connection, message); + InstanceCheckMessageHandlerDBusInternal::handle_method_another_instance(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Introspect", member_name)) { - respond_to_introspect(connection, message); + InstanceCheckMessageHandlerDBusInternal::respond_to_introspect(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -} //namespace MessageHandlerDBusInternal +} //namespace InstanceCheckMessageHandlerDBusInternal -void OtherInstanceMessageHandler::listen() +void OtherInstanceMessageHandler::listen_instance_check() { DBusConnection* conn; DBusError err; int name_req_val; DBusObjectPathVTable vtable; std::string instance_hash = wxGetApp().get_instance_hash_string(); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << instance_hash; std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck.Object" + instance_hash; std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck/Object" + instance_hash; @@ -694,7 +977,7 @@ void OtherInstanceMessageHandler::listen() } // Set callbacks. Unregister function should not be nessary. - vtable.message_function = MessageHandlerDBusInternal::handle_dbus_object_message; + vtable.message_function = InstanceCheckMessageHandlerDBusInternal::handle_dbus_object_message; vtable.unregister_function = NULL; // register new object - this is our access to DBus @@ -707,19 +990,170 @@ void OtherInstanceMessageHandler::listen() return; } - BOOST_LOG_TRIVIAL(trace) << "Dbus object "<< object_name <<" registered. Starting listening for messages."; + BOOST_LOG_TRIVIAL(debug) << "Dbus object "<< object_name <<" registered. Starting listening for messages."; for (;;) { // Wait for 1 second // Cancellable. { - std::unique_lock lck(m_thread_stop_mutex); - m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; }); + std::unique_lock lck(m_instance_check_thread_stop_mutex); + m_instance_check_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_instance_check_thread_stop; }); } - if (m_stop) + if (m_instance_check_thread_stop) { // Stop the worker thread. - break; + } + //dispatch should do all the work with incoming messages + //second parameter is blocking time that funciton waits for new messages + //that is handled here with our own event loop above + dbus_connection_read_write_dispatch(conn, 0); + } + + dbus_connection_unref(conn); +} + + +namespace MulticastMessageHandlerDBusInternal +{ + //reply to introspect makes our DBus object visible for other programs like D-Feet + static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + DBusMessage *reply; + const char *introspection_data = + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " "; + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_data, DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } + + //method Message receives message from another PrusaSlicer instance + static void handle_method_message(DBusConnection *connection, DBusMessage *request) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + DBusError err; + char* text = nullptr; + wxEvtHandler* evt_handler; + + dbus_error_init(&err); + dbus_message_get_args(request, &err, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID); + if (dbus_error_is_set(&err)) { + BOOST_LOG_TRIVIAL(debug) << "Dbus method Message received with wrong arguments."; + dbus_error_free(&err); + return; + } + wxGetApp().other_instance_message_handler()->handle_message(text); + } + + //every dbus message received comes here + static DBusHandlerResult handle_dbus_object_message(DBusConnection *connection, DBusMessage *message, void *user_data) + { + const char* interface_name = dbus_message_get_interface(message); + const char* member_name = dbus_message_get_member(message); + std::string our_interface = "com.prusa3d.prusaslicer.MulticastListener.Object" + std::to_string(get_current_pid()); + BOOST_LOG_TRIVIAL(debug) << "DBus message received: interface: " << interface_name << ", member: " << member_name; + if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) { + MulticastMessageHandlerDBusInternal::respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Message", member_name)) { + MulticastMessageHandlerDBusInternal::handle_method_message(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Introspect", member_name)) { + MulticastMessageHandlerDBusInternal::respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } +} //namespace MulticastMessageHandlerDBusInternal + +void OtherInstanceMessageHandler::listen_multicast() +{ + DBusConnection* conn; + DBusError err; + int name_req_val; + DBusObjectPathVTable vtable; + std::string pid = std::to_string(get_current_pid()); + std::string interface_name = "com.prusa3d.prusaslicer.MulticastListener.Object" + pid; + std::string object_name = "/com/prusa3d/prusaslicer/MulticastListener/Object" + pid; + + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << interface_name; + dbus_error_init(&err); + + // connect to the bus and check for errors (use SESSION bus everywhere!) + conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + BOOST_LOG_TRIVIAL(error) << "listen_multicast: DBus Connection Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "listen_multicast: Dbus Messages listening terminating."; + dbus_error_free(&err); + return; + } + if (NULL == conn) { + BOOST_LOG_TRIVIAL(error) << "listen_multicast: DBus Connection is NULL. Dbus Messages listening terminating."; + return; + } + + // request our name on the bus and check for errors + name_req_val = dbus_bus_request_name(conn, interface_name.c_str(), DBUS_NAME_FLAG_REPLACE_EXISTING , &err); + if (dbus_error_is_set(&err)) { + BOOST_LOG_TRIVIAL(error) << "listen_multicast: DBus Request name Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "listen_multicast: Dbus Messages listening terminating."; + dbus_error_free(&err); + dbus_connection_unref(conn); + return; + } + if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != name_req_val) { + BOOST_LOG_TRIVIAL(error) << "listen_multicast: Not primary owner of DBus name - probably another PrusaSlicer instance is running."; + BOOST_LOG_TRIVIAL(error) << "listen_multicast: Dbus Messages listening terminating."; + dbus_connection_unref(conn); + return; + } + + // Set callbacks. Unregister function should not be nessary. + vtable.message_function = MulticastMessageHandlerDBusInternal::handle_dbus_object_message; + vtable.unregister_function = NULL; + + // register new object - this is our access to DBus + dbus_connection_try_register_object_path(conn, object_name.c_str(), &vtable, NULL, &err); + if ( dbus_error_is_set(&err) ) { + BOOST_LOG_TRIVIAL(error) << "listen_multicast: DBus Register object Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "listen_multicast: Dbus Messages listening terminating."; + dbus_connection_unref(conn); + dbus_error_free(&err); + return; + } + + BOOST_LOG_TRIVIAL(debug) << "listen_multicast: Dbus object "<< object_name <<" registered. Starting listening for messages."; + + for (;;) { + // Wait for 1 second + // Cancellable. + { + std::unique_lock lck(m_multicast_listener_thread_stop_mutex); + m_multicast_listener_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_multicast_listener_thread_stop; }); + } + if (m_multicast_listener_thread_stop) { + // Stop the worker thread. + break; + } //dispatch should do all the work with incoming messages //second parameter is blocking time that funciton waits for new messages //that is handled here with our own event loop above diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 26cf6bd14b..7185e6bfd1 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -12,6 +12,7 @@ #endif //_WIN32 #include +#include #include @@ -32,6 +33,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance); // apple implementation of inner functions of instance_check // in InstanceCheckMac.mm void send_message_mac(const std::string& msg, const std::string& version); +void multicast_message_mac(const std::string &msg); void send_message_mac_closing(const std::string& msg, const std::string& version); @@ -54,11 +56,16 @@ wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEve wxDECLARE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent); using InstanceGoToFrontEvent = SimpleEvent; wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); +wxDECLARE_EVENT(EVT_STORE_READ_REQUEST, SimpleEvent); class OtherInstanceMessageHandler { public: - OtherInstanceMessageHandler() = default; + OtherInstanceMessageHandler() + { + m_message_handlers["CLI"] = std::bind(&OtherInstanceMessageHandler::handle_message_type_cli, this, std::placeholders::_1); + m_message_handlers["STORE_READ"] = std::bind(&OtherInstanceMessageHandler::handle_message_type_store_read, this, std::placeholders::_1); + } OtherInstanceMessageHandler(OtherInstanceMessageHandler const&) = delete; void operator=(OtherInstanceMessageHandler const&) = delete; ~OtherInstanceMessageHandler() { assert(!m_initialized); } @@ -68,13 +75,10 @@ public: // stops listening, on linux stops the background thread void shutdown(MainFrame* main_frame); - //finds paths to models in message(= command line arguments, first should be prusaSlicer executable) - //and sends them to plater via LoadFromOtherInstanceEvent - //security of messages: from message all existing paths are proccesed to load model - // win32 - anybody who has hwnd can send message. - // mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating" - // linux - instrospectable on dbus - void handle_message(const std::string& message); + // message in format { "type" : "TYPE", "data" : "data" } + void handle_message(const std::string& message); + + void multicast_message(const std::string& message_type, const std::string& message_data = std::string()); #ifdef __APPLE__ // Messege form other instance, that it deleted its lockfile - first instance to get it will create its own. void handle_message_other_closed(); @@ -84,19 +88,37 @@ public: void update_windows_properties(MainFrame* main_frame); #endif //WIN32 private: + //finds paths to models in message(= command line arguments, first should be prusaSlicer executable) + //and sends them to plater via LoadFromOtherInstanceEvent + //security of messages: from message all existing paths are proccesed to load model + // win32 - anybody who has hwnd can send message. + // mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating" + // linux - instrospectable on dbus + void handle_message_type_cli(const std::string& data); + // Passes information to UI to perform store read + void handle_message_type_store_read(const std::string& data); + std::map> m_message_handlers; + bool m_initialized { false }; wxEvtHandler* m_callback_evt_handler { nullptr }; #ifdef BACKGROUND_MESSAGE_LISTENER - //worker thread to listen incoming dbus communication - boost::thread m_thread; - std::condition_variable m_thread_stop_condition; - mutable std::mutex m_thread_stop_mutex; - bool m_stop{ false }; - bool m_start{ true }; - - // background thread method - void listen(); + // instance check worker thread to listen incoming dbus communication + // Only one instance has registered dbus object at time + boost::thread m_instance_check_thread; + std::condition_variable m_instance_check_thread_stop_condition; + mutable std::mutex m_instance_check_thread_stop_mutex; + bool m_instance_check_thread_stop{ false }; + //bool m_instance_check_thread_start{ true }; + void listen_instance_check(); + + // "multicast" worker thread to listen incoming dbus communication + // every instance has registered its own object + boost::thread m_multicast_listener_thread; + std::condition_variable m_multicast_listener_thread_stop_condition; + mutable std::mutex m_multicast_listener_thread_stop_mutex; + bool m_multicast_listener_thread_stop{ false }; + void listen_multicast(); #endif //BACKGROUND_MESSAGE_LISTENER #if __APPLE__ diff --git a/src/slic3r/GUI/InstanceCheckMac.h b/src/slic3r/GUI/InstanceCheckMac.h index 1f047ec88a..6977569702 100644 --- a/src/slic3r/GUI/InstanceCheckMac.h +++ b/src/slic3r/GUI/InstanceCheckMac.h @@ -9,6 +9,7 @@ -(instancetype) init; -(void) add_observer:(NSString *)version; -(void) message_update:(NSNotification *)note; +-(void) message_multicast_update:(NSNotification *)note; -(void) closing_update:(NSNotification *)note; -(void) bring_forward; @end diff --git a/src/slic3r/GUI/InstanceCheckMac.mm b/src/slic3r/GUI/InstanceCheckMac.mm index b43e898b05..641d3efeb5 100644 --- a/src/slic3r/GUI/InstanceCheckMac.mm +++ b/src/slic3r/GUI/InstanceCheckMac.mm @@ -11,12 +11,14 @@ } -(void)add_observer:(NSString *)version_hash { - //NSLog(@"adding observer"); + //NSLog(@"adding observers"); //NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + version_hash; NSString *nsver = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceMessage", version_hash]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:nsver object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; NSString *nsver2 = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceClosing", version_hash]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(closing_update:) name:nsver2 object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; + NSString *nsnover = [NSString stringWithFormat: @"%@", @"OtherPrusaSlicerMulticastMessage"]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_multicast_update:) name:nsnover object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; } -(void)message_update:(NSNotification *)msg @@ -26,6 +28,13 @@ Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String])); } +-(void)message_multicast_update:(NSNotification *)msg +{ + //NSLog(@"message_multicast_update"); + //pass message + Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String])); +} + -(void)closing_update:(NSNotification *)msg { //[self bring_forward]; @@ -51,6 +60,14 @@ namespace Slic3r { +void multicast_message_mac(const std::string &msg) +{ + NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]]; + //NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + [NSString stringWithCString:version.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *notifname = [NSString stringWithFormat: @"%@", @"OtherPrusaSlicerMulticastMessage"]; + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notifname object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES]; +} + void send_message_mac(const std::string &msg, const std::string &version) { NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]]; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d677808180..aa19984274 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -876,6 +876,7 @@ void MainFrame::show_connect_tab(const wxString& url) if (!m_connect_webview_added) { return; } + m_connect_webview->prohibit_after_show_func_once(); m_tabpanel->SetSelection(m_tabpanel->FindPage(m_connect_webview)); m_connect_webview->set_load_default_url_on_next_error(true); m_connect_webview->load_url(url); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 5f352b814c..215aae7b5e 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -144,6 +144,8 @@ enum class NotificationType WipeTowerNozzleDiameterDiffer, // Notification about using supports with different nozzle diameters. SupportNozzleDiameterDiffer, + // Transient error on Prusa Account communication - user is informed and has option to cancel (logout) + AccountTransientRetry, }; class NotificationManager diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ef3d164865..202c35989e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -896,6 +896,11 @@ void Plater::priv::init() BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event."; user_account->on_login_code_recieved(evt.data); }); + this->q->Bind(EVT_STORE_READ_REQUEST, [this](SimpleEvent& evt) { + BOOST_LOG_TRIVIAL(debug) << "Received store read request from other instance event."; + user_account->on_store_read_request(); + }); + this->q->Bind(EVT_LOGIN_VIA_WIZARD, [this](Event &evt) { BOOST_LOG_TRIVIAL(trace) << "Received login from wizard."; user_account->on_login_code_recieved(evt.data); @@ -952,7 +957,7 @@ void Plater::priv::init() this->show_action_buttons(this->ready_to_slice); }); - this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) { + auto on_id_user_success = [this](UserAccountSuccessEvent& evt, bool after_token_success) { if (login_dialog != nullptr) { login_dialog->EndModal(wxID_CANCEL); } @@ -960,7 +965,7 @@ void Plater::priv::init() evt.Skip(); std::string who = user_account->get_username(); std::string username; - if (user_account->on_user_id_success(evt.data, username)) { + if (user_account->on_user_id_success(evt.data, username, after_token_success)) { if (who != username) { // show notification only on login (not refresh). std::string text = format(_u8L("Logged to Prusa Account as %1%."), username); @@ -970,6 +975,9 @@ void Plater::priv::init() this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); this->main_frame->on_account_login(user_account->get_access_token()); + + // notify other instances + Slic3r::GUI::wxGetApp().other_instance_message_handler()->multicast_message("STORE_READ"); } else { // refresh do different operations than on_account_login this->main_frame->on_account_did_refresh(user_account->get_access_token()); @@ -991,9 +999,16 @@ void Plater::priv::init() this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); - } - + } + }; + + this->q->Bind(EVT_UA_ID_USER_SUCCESS, [on_id_user_success](UserAccountSuccessEvent& evt) { + on_id_user_success(evt, false); }); + this->q->Bind(EVT_UA_ID_USER_SUCCESS_AFTER_TOKEN_SUCCESS, [on_id_user_success](UserAccountSuccessEvent& evt) { + on_id_user_success(evt, true); + }); + this->q->Bind(EVT_UA_RESET, [this](UserAccountFailEvent& evt) { BOOST_LOG_TRIVIAL(error) << "Reseting Prusa Account communication. Error message: " << evt.data; user_account->clear(); @@ -1009,6 +1024,11 @@ void Plater::priv::init() BOOST_LOG_TRIVIAL(error) << "Failed communication with Prusa Account: " << evt.data; user_account->on_communication_fail(); }); + this->q->Bind(EVT_UA_RACE_LOST, [this](UserAccountFailEvent& evt) { + BOOST_LOG_TRIVIAL(debug) << "Renew token race lost: " << evt.data; + user_account->on_race_lost(); + }); + this->q->Bind(EVT_UA_PRUSACONNECT_STATUS_SUCCESS, [this](UserAccountSuccessEvent& evt) { std::string text; bool printers_changed = false; @@ -1073,6 +1093,22 @@ void Plater::priv::init() } this->q->printables_to_connect_gcode(into_u8(evt.GetString())); }); + + this->q->Bind(EVT_UA_RETRY_NOTIFY, [this](UserAccountFailEvent& evt) { + this->notification_manager->close_notification_of_type(NotificationType::AccountTransientRetry); + this->notification_manager->push_notification(NotificationType::AccountTransientRetry + , NotificationManager::NotificationLevel::RegularNotificationLevel + , evt.data + , _u8L("Stop now.") + , [this](wxEvtHandler* ) { + this->user_account->do_logout(); + return true; + }); + + }); + this->q->Bind(EVT_UA_CLOSE_RETRY_NOTIFICATION, [this](SimpleEvent& evt) { + this->notification_manager->close_notification_of_type(NotificationType::AccountTransientRetry); + }); } wxGetApp().other_instance_message_handler()->init(this->q); diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp index 7192ec6a0f..009628d7e7 100644 --- a/src/slic3r/GUI/UserAccount.cpp +++ b/src/slic3r/GUI/UserAccount.cpp @@ -14,6 +14,9 @@ #include #include +#include "InstanceCheck.hpp" +#include "GUI_App.hpp" + #include namespace pt = boost::property_tree; @@ -30,10 +33,10 @@ UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config, const UserAccount::~UserAccount() {} -void UserAccount::set_username(const std::string& username) +void UserAccount::set_username(const std::string& username, bool store) { m_username = username; - m_communication->set_username(username); + m_communication->set_username(username, store); } void UserAccount::clear() @@ -67,7 +70,9 @@ void UserAccount::do_login() } void UserAccount::do_logout() { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; m_communication->do_logout(); + Slic3r::GUI::wxGetApp().other_instance_message_handler()->multicast_message("STORE_READ"); } std::string UserAccount::get_access_token() @@ -121,7 +126,7 @@ bool UserAccount::on_login_code_recieved(const std::string& url_message) return true; } -bool UserAccount::on_user_id_success(const std::string data, std::string& out_username) +bool UserAccount::on_user_id_success(const std::string data, std::string& out_username, bool after_token_success) { boost::property_tree::ptree ptree; try { @@ -136,7 +141,7 @@ bool UserAccount::on_user_id_success(const std::string data, std::string& out_us for (const auto& section : ptree) { const auto opt = ptree.get_optional(section.first); if (opt) { - BOOST_LOG_TRIVIAL(debug) << static_cast(section.first) << " " << *opt; + //BOOST_LOG_TRIVIAL(debug) << static_cast(section.first) << " " << *opt; m_account_user_data[section.first] = *opt; } @@ -146,7 +151,7 @@ bool UserAccount::on_user_id_success(const std::string data, std::string& out_us return false; } std::string public_username = m_account_user_data["public_username"]; - set_username(public_username); + set_username(public_username, after_token_success); out_username = public_username; // enqueue GET with avatar url @@ -181,11 +186,14 @@ void UserAccount::on_communication_fail() } } - +void UserAccount::on_race_lost() +{ + m_communication->on_race_lost(); +} bool UserAccount::on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed) { - BOOST_LOG_TRIVIAL(debug) << "Prusa Connect printers message: " << data; + BOOST_LOG_TRIVIAL(trace) << "Prusa Connect printers message: " << data; pt::ptree ptree; try { std::stringstream ss(data); diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index 6a1730735a..65362eb812 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -56,9 +56,10 @@ public: // 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); + bool on_user_id_success(const std::string data, std::string& out_username, bool after_token_success); // Called on EVT_UA_FAIL, triggers test after several calls void on_communication_fail(); + void on_race_lost(); bool on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed); bool on_connect_uiid_map_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed); @@ -78,8 +79,10 @@ public: void set_current_printer_data(const std::string& data) { m_current_printer_data_json_from_connect = data; } void set_refresh_time(int seconds) { m_communication->set_refresh_time(seconds); } + + void on_store_read_request() { m_communication->on_store_read_request(); } private: - void set_username(const std::string& username); + void set_username(const std::string& username, bool store); std::string m_instance_hash; // used in avatar path @@ -105,7 +108,7 @@ private: {"READY" , ConnectPrinterState::CONNECT_PRINTER_READY}, {"ATTENTION", ConnectPrinterState::CONNECT_PRINTER_ATTENTION}, {"BUSY" , ConnectPrinterState::CONNECT_PRINTER_BUSY}, - {"ERROR" , ConnectPrinterState::CONNECT_PRINTER_ERROR}, + {"ERROR" , ConnectPrinterState::CONNECT_PRINTER_ERROR}, }; }; }} // namespace slic3r::GUI diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index cc0cdf6318..0af26a86fc 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -42,6 +42,8 @@ #include #include #include + +#include #endif // __linux__ @@ -139,7 +141,7 @@ bool load_secret(const std::string& opt, std::string& usr, std::string& psswd) } #ifdef __linux__ -void load_refresh_token_linux(std::string& refresh_token) +void load_tokens_linux(UserAccountCommunication::StoreData& result) { // Load refresh token from UserAccount.dat boost::filesystem::path source(boost::filesystem::path(Slic3r::data_dir()) / "UserAccount.dat") ; @@ -157,18 +159,80 @@ void load_refresh_token_linux(std::string& refresh_token) } boost::nowide::ifstream stream(source.generic_string(), std::ios::in | std::ios::binary); if (!stream) { - BOOST_LOG_TRIVIAL(error) << "UserAccount: Failed to read token from " << source; + BOOST_LOG_TRIVIAL(error) << "UserAccount: Failed to read tokens from " << source; return; } - std::getline(stream, refresh_token); + std::string token_data; + std::getline(stream, token_data); stream.close(); if (delete_after_read) { ec.clear(); if (!boost::filesystem::remove(source, ec) || ec) { BOOST_LOG_TRIVIAL(error) << "UserAccount: Failed to remove file " << source; } - } + + // read data + std::vector token_list; + boost::split(token_list, token_data, boost::is_any_of("|"), boost::token_compress_off); + assert(token_list.empty() || token_list.size() == 5); + if (token_list.size() < 5) { + BOOST_LOG_TRIVIAL(error) << "Size of read secrets is only: " << token_list.size() << " (expected 5). Data: " << token_data; + } + result.access_token = token_list.size() > 0 ? token_list[0] : std::string(); + result.refresh_token = token_list.size() > 1 ? token_list[1] : std::string(); + result.next_timeout = token_list.size() > 2 ? token_list[2] : std::string(); + result.master_pid = token_list.size() > 3 ? token_list[3] : std::string(); + result.shared_session_key = token_list.size() > 4 ? token_list[4] : std::string(); +} +bool concurrent_write_file(const std::string& secret_to_store, const boost::filesystem::path& filename) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + // Open the file + int fd = open(filename.string().c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + if (fd == -1) { + BOOST_LOG_TRIVIAL(error) << "Unable to open store file " << filename << ": " << strerror(errno); + return false; + } + // Close the file when the guard dies + Slic3r::ScopeGuard sg_fd([fd]() { + close(fd); + BOOST_LOG_TRIVIAL(debug) << "Closed file."; + }); + + // Configure the lock + struct flock lock; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; // Write lock + lock.l_whence = SEEK_SET; // Lock from the start of the file + lock.l_start = 0; + lock.l_len = 0; // 0 means lock the entire file + + // Try to acquire the lock + BOOST_LOG_TRIVIAL(debug) << "Waiting to acquire lock on file: " << filename; + if (fcntl(fd, F_SETLKW, &lock) == -1) { + BOOST_LOG_TRIVIAL(error) << "Unable to acquire lock: " << strerror(errno); + return false; + } + BOOST_LOG_TRIVIAL(debug) << "Lock acquired on file: " << filename; + + Slic3r::ScopeGuard sg_lock([&lock, fd, filename]() { + // Release the lock when guard dies. + lock.l_type = F_UNLCK; // Unlock the file + if (fcntl(fd, F_SETLK, &lock) == -1) { + BOOST_LOG_TRIVIAL(error) << "Unable to release lock ("<< filename <<"): " << strerror(errno); + } else { + BOOST_LOG_TRIVIAL(debug) << "Lock released on file: " << filename; + } + }); + + // Write content to the file + if (write(fd, secret_to_store.c_str(), strlen(secret_to_store.c_str())) == -1) { + BOOST_LOG_TRIVIAL(error) << "Unable to write to file: " << strerror(errno); + return false; + } + BOOST_LOG_TRIVIAL(debug) << "Content written to file."; + return true; } #endif //__linux__ } @@ -179,42 +243,31 @@ UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, Ap , m_app_config(app_config) , m_polling_timer(std::make_unique(this)) , m_token_timer(std::make_unique(this)) + , m_slave_read_timer(std::make_unique(this)) + , m_after_race_lost_timer(std::make_unique(this)) { Bind(wxEVT_TIMER, &UserAccountCommunication::on_token_timer, this, m_token_timer->GetId()); Bind(wxEVT_TIMER, &UserAccountCommunication::on_polling_timer, this, m_polling_timer->GetId()); + Bind(wxEVT_TIMER, &UserAccountCommunication::on_slave_read_timer, this, m_slave_read_timer->GetId()); + Bind(wxEVT_TIMER, &UserAccountCommunication::on_after_race_lost_timer, this, m_after_race_lost_timer->GetId()); - std::string access_token, refresh_token, shared_session_key, next_timeout; - if (is_secret_store_ok()) { - std::string key0, key1, key2, tokens; - if (load_secret("tokens", key0, tokens)) { - std::vector token_list; - boost::split(token_list, tokens, boost::is_any_of("|"), boost::token_compress_off); - assert(token_list.empty() || token_list.size() == 3); - access_token = token_list.size() > 0 ? token_list[0] : std::string(); - refresh_token = token_list.size() > 1 ? token_list[1] : std::string(); - next_timeout = token_list.size() > 2 ? token_list[2] : std::string(); - } else { - load_secret("access_token", key0, access_token); - load_secret("refresh_token", key1, refresh_token); - load_secret("access_token_timeout", key2, next_timeout); - assert(key0 == key1); - } - shared_session_key = key0; + StoreData stored_data; + read_stored_data(stored_data); - } else { -#ifdef __linux__ - load_refresh_token_linux(refresh_token); -#endif - } - long long next = next_timeout.empty() ? 0 : std::stoll(next_timeout); - long long remain_time = next - std::time(nullptr); + long long next_timeout_long = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout); + long long remain_time = next_timeout_long - std::time(nullptr); if (remain_time <= 0) { - access_token.clear(); + stored_data.access_token.clear(); } else { set_refresh_time((int)remain_time); } - bool has_token = !refresh_token.empty(); - m_session = std::make_unique(evt_handler, access_token, refresh_token, shared_session_key, m_app_config->get_bool("connect_polling")); + if (!stored_data.access_token.empty()) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token: " << stored_data.access_token.substr(0,5) << "..." << stored_data.access_token.substr(stored_data.access_token.size()-5); + } else { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token empty!"; + } + bool has_token = !stored_data.refresh_token.empty(); + m_session = std::make_unique(evt_handler, stored_data.access_token, stored_data.refresh_token, stored_data.shared_session_key, next_timeout_long, m_app_config->get_bool("connect_polling")); init_session_thread(); // perform login at the start, but only with tokens if (has_token) { @@ -240,24 +293,42 @@ UserAccountCommunication::~UserAccountCommunication() } } -void UserAccountCommunication::set_username(const std::string& username) +void UserAccountCommunication::set_username(const std::string& username, bool store) { m_username = username; + if (!store && !username.empty()) { + return; + } { + //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " empty: " << username.empty(); + std::string at = m_session->get_access_token(); + if (!at.empty()) + at = at.substr(0,5) + "..." + at.substr(at.size()-5); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token: " << (username.empty() ? "" : at); if (is_secret_store_ok()) { - std::string tokens; - if (m_remember_session) { + std::string tokens = "|||"; + if (m_remember_session && !username.empty()) { tokens = m_session->get_access_token() + - "|" + m_session->get_refresh_token() + - "|" + std::to_string(m_session->get_next_token_timeout()); + "|" + m_session->get_refresh_token() + + "|" + std::to_string(m_session->get_next_token_timeout()) + + "|" + std::to_string(get_current_pid()); } - save_secret("tokens", m_session->get_shared_session_key(), tokens); - } - else { + if (!save_secret("tokens", m_session->get_shared_session_key(), tokens)) { + BOOST_LOG_TRIVIAL(error) << "Failed to write tokens to the secret store."; + } + } else { #ifdef __linux__ // If we can't store the tokens in secret store, store them in file with chmod 600 boost::filesystem::path target(boost::filesystem::path(Slic3r::data_dir()) / "UserAccount.dat") ; - std::string data = m_session->get_refresh_token(); + std::string data = "||||"; + if (m_remember_session && !username.empty()) { + data = m_session->get_access_token() + + "|" + m_session->get_refresh_token() + + "|" + std::to_string(m_session->get_next_token_timeout()) + + "|" + std::to_string(get_current_pid()) + + "|" + m_session->get_shared_session_key(); + } + FILE* file; static const auto perms = boost::filesystem::owner_read | boost::filesystem::owner_write; // aka 600 @@ -267,17 +338,15 @@ void UserAccountCommunication::set_username(const std::string& username) BOOST_LOG_TRIVIAL(debug) << "UserAccount: boost::filesystem::permisions before write error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); - file = boost::nowide::fopen(target.generic_string().c_str(), "wb"); - if (file == NULL) { - BOOST_LOG_TRIVIAL(error) << "UserAccount: Failed to open file to store token: " << target; - return; + if (!concurrent_write_file(data, target)) { + BOOST_LOG_TRIVIAL(error) << "Failed to store secret."; } - fwrite(data.c_str(), 1, data.size(), file); - fclose(file); boost::filesystem::permissions(target, perms, ec); if (ec) BOOST_LOG_TRIVIAL(debug) << "UserAccount: boost::filesystem::permisions after write error message (this could be irrelevant message based on file system): " << ec.message(); +#else + BOOST_LOG_TRIVIAL(error) << "Failed to write tokens to the secret store: Store is not ok."; #endif } } @@ -285,9 +354,10 @@ void UserAccountCommunication::set_username(const std::string& username) void UserAccountCommunication::set_remember_session(bool b) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; m_remember_session = b; // tokens needs to be stored or deleted - set_username(m_username); + set_username(m_username, true); } std::string UserAccountCommunication::get_access_token() @@ -327,7 +397,7 @@ wxString UserAccountCommunication::generate_login_redirect_url() const std::string REDIRECT_URI = "prusaslicer://login"; CodeChalengeGenerator ccg; m_code_verifier = ccg.generate_verifier(); - std::string code_challenge = ccg.generate_chalenge(m_code_verifier); + std::string code_challenge = ccg.generate_challenge(m_code_verifier); wxString language = GUI::wxGetApp().current_language_code(); language = language.SubString(0, 1); BOOST_LOG_TRIVIAL(info) << "code verifier: " << m_code_verifier; @@ -343,7 +413,7 @@ wxString UserAccountCommunication::get_login_redirect_url(const std::string& ser const std::string CLIENT_ID = client_id(); const std::string REDIRECT_URI = "prusaslicer://login"; CodeChalengeGenerator ccg; - std::string code_challenge = ccg.generate_chalenge(m_code_verifier); + std::string code_challenge = ccg.generate_challenge(m_code_verifier); wxString language = GUI::wxGetApp().current_language_code(); language = language.SubString(0, 1); @@ -380,9 +450,13 @@ void UserAccountCommunication::do_logout() void UserAccountCommunication::do_clear() { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; m_session->clear(); - set_username({}); + set_username({}, true); m_token_timer->Stop(); + m_slave_read_timer->Stop(); + m_after_race_lost_timer->Stop(); + m_next_token_refresh_at = 0; } void UserAccountCommunication::on_login_code_recieved(const std::string& url_message) @@ -414,7 +488,7 @@ void UserAccountCommunication::enqueue_connect_status_action() void UserAccountCommunication::enqueue_test_connection() { if (!m_session->is_initialized()) { - BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; + BOOST_LOG_TRIVIAL(error) << "Connect test endpoint connection failed - Not Logged in."; return; } m_session->enqueue_test_with_refresh(); @@ -424,7 +498,7 @@ void UserAccountCommunication::enqueue_test_connection() void UserAccountCommunication::enqueue_avatar_old_action(const std::string& url) { if (!m_session->is_initialized()) { - BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; + BOOST_LOG_TRIVIAL(error) << "Connect avatar endpoint connection failed - Not Logged in."; return; } m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR_OLD, nullptr, nullptr, url); @@ -441,6 +515,16 @@ void UserAccountCommunication::enqueue_avatar_new_action(const std::string& url) wakeup_session_thread(); } +void UserAccountCommunication::enqueue_id_action() +{ + if (!m_session->is_initialized()) { + BOOST_LOG_TRIVIAL(error) << "Connect id endpoint connection failed - Not Logged in."; + return; + } + m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID, nullptr, nullptr, {}); + wakeup_session_thread(); +} + void UserAccountCommunication::enqueue_printer_data_action(const std::string& uuid) { if (!m_session->is_initialized()) { @@ -451,12 +535,6 @@ void UserAccountCommunication::enqueue_printer_data_action(const std::string& uu wakeup_session_thread(); } -void UserAccountCommunication::request_refresh() -{ - m_token_timer->Stop(); - enqueue_refresh(); -} - void UserAccountCommunication::enqueue_refresh() { if (!m_session->is_initialized()) { @@ -501,7 +579,7 @@ void UserAccountCommunication::on_activate_app(bool active) m_window_is_active = active; } auto now = std::time(nullptr); - BOOST_LOG_TRIVIAL(info) << "UserAccountCommunication activate: active " << active; + //BOOST_LOG_TRIVIAL(info) << "UserAccountCommunication activate: active " << active; #ifndef _NDEBUG // constexpr auto refresh_threshold = 110 * 60; constexpr auto refresh_threshold = 60; @@ -509,13 +587,22 @@ void UserAccountCommunication::on_activate_app(bool active) constexpr auto refresh_threshold = 60; #endif if (active && m_next_token_refresh_at > 0 && m_next_token_refresh_at - now < refresh_threshold) { - BOOST_LOG_TRIVIAL(info) << "Enqueue access token refresh on activation"; + // Commented during implementation of sharing access token among instances - TODO + BOOST_LOG_TRIVIAL(debug) << " Requesting refresh when app was activated: next token refresh is at " << m_next_token_refresh_at - now; request_refresh(); + return; + } + // When no token timers are running but we have token -> refresh it. + if (active && m_next_token_refresh_at > 0 && m_token_timer->IsRunning() && m_slave_read_timer->IsRunning() && m_after_race_lost_timer->IsRunning()) { + BOOST_LOG_TRIVIAL(debug) << " Requesting refresh when app was activated when no timers are running, next token refresh is at " << m_next_token_refresh_at - now; + request_refresh(); + return; } } void UserAccountCommunication::wakeup_session_thread() { + //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; { std::lock_guard lck(m_thread_stop_mutex); m_thread_wakeup = true; @@ -527,6 +614,7 @@ void UserAccountCommunication::set_refresh_time(int seconds) { assert(m_token_timer); m_token_timer->Stop(); + m_last_token_duration_seconds = seconds; const auto prior_expiration_secs = std::max(seconds / 24, 10); int milliseconds = std::max((seconds - prior_expiration_secs) * 1000, 1000); m_next_token_refresh_at = std::time(nullptr) + milliseconds / 1000; @@ -534,21 +622,260 @@ void UserAccountCommunication::set_refresh_time(int seconds) m_token_timer->StartOnce(milliseconds); } +void UserAccountCommunication::read_stored_data(UserAccountCommunication::StoreData& result) +{ + if (is_secret_store_ok()) { + std::string key0, tokens; + if (load_secret("tokens", key0, tokens)) { + std::vector token_list; + boost::split(token_list, tokens, boost::is_any_of("|"), boost::token_compress_off); + assert(token_list.empty() || token_list.size() == 4); + if (token_list.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Size of read secrets is only: " << token_list.size() << " (expected 4). Data: " << tokens; + } + result.access_token = token_list.size() > 0 ? token_list[0] : std::string(); + result.refresh_token = token_list.size() > 1 ? token_list[1] : std::string(); + result.next_timeout = token_list.size() > 2 ? token_list[2] : std::string(); + result.master_pid = token_list.size() > 3 ? token_list[3] : std::string(); + } + result.shared_session_key = key0; + } else { +#ifdef __linux__ + load_tokens_linux(result); +#endif + } +} + +void UserAccountCommunication::request_refresh() +{ + // This function is called when Printables requests new token - same token as we have now wont do. + // Or from UserAccountCommunication::on_activate_app(true) when current token has too small refresh or is dead + // See if there is different token stored, if not - proceed to T3 (there might be more than 1 app doing this). + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + if (m_token_timer->IsRunning()) { + m_token_timer->Stop(); + } + if (m_slave_read_timer->IsRunning()) { + m_slave_read_timer->Stop(); + } + if (m_after_race_lost_timer->IsRunning()) { + m_after_race_lost_timer->Stop(); + } + + std::string current_access_token = m_session->get_access_token(); + StoreData stored_data; + read_stored_data(stored_data); + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + // Here we need to count with situation when token was renewed in m_session but was not yet stored. + // Then store token is not valid - it should has erlier expiration + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + BOOST_LOG_TRIVIAL(error) << "Compare " << expires_in_second << " vs " << m_next_token_refresh_at - std::time(nullptr) << (stored_data.access_token != current_access_token ? " not same" : " same"); + if (stored_data.access_token != current_access_token && expires_in_second > 0 && expires_in_second > m_next_token_refresh_at - std::time(nullptr)) { + BOOST_LOG_TRIVIAL(debug) << "Found usable token. Expires in " << expires_in_second; + set_tokens(stored_data); + } else { + BOOST_LOG_TRIVIAL(debug) << "No new token"; + enqueue_refresh_race(stored_data.refresh_token); + } +} void UserAccountCommunication::on_token_timer(wxTimerEvent& evt) { - BOOST_LOG_TRIVIAL(info) << "UserAccountCommunication: Token refresh timer fired"; - enqueue_refresh(); + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " T1"; + // Read PID from current stored token and decide if master / slave + + std::string my_pid = std::to_string(get_current_pid()); + StoreData stored_data; + read_stored_data(stored_data); + + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + if (my_pid == stored_data.master_pid) { + enqueue_refresh(); + return; + } + // token could be either already new -> we want to start using it now + const auto prior_expiration_secs = std::max(m_last_token_duration_seconds / 24, 10); + if (expires_in_second >= 0 && expires_in_second > prior_expiration_secs) { + BOOST_LOG_TRIVIAL(debug) << "Current token has different PID - expiration is " << expires_in_second << " while longest expected was " << prior_expiration_secs << ". Using this token."; + set_tokens(stored_data); + return; + } + // or yet to be renewed -> we should wait to give time to master to renew it + if (expires_in_second >= 0) { + BOOST_LOG_TRIVIAL(debug) << "Current token has different PID - waiting " << expires_in_second / 2; + m_slave_read_timer->StartOnce((expires_in_second / 2) * 1000); + return; + } + // or expired -> renew now. + BOOST_LOG_TRIVIAL(debug) << "Current token has different PID and is expired."; + enqueue_refresh_race(stored_data.refresh_token); } +void UserAccountCommunication::on_slave_read_timer(wxTimerEvent& evt) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " T2"; + std::string current_access_token = m_session->get_access_token(); + StoreData stored_data; + read_stored_data(stored_data); + + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + if (stored_data.access_token != current_access_token) { + // consider stored_data as renewed token from master + BOOST_LOG_TRIVIAL(debug) << "Token in store seems to be new - using it."; + set_tokens(stored_data); + return; + } + if (stored_data.access_token != current_access_token) { + // token is expired + BOOST_LOG_TRIVIAL(debug) << "Token in store seems to be new but expired - refreshing now."; + enqueue_refresh_race(stored_data.refresh_token); + return; + } + BOOST_LOG_TRIVIAL(debug) <<"No new token, enqueueing refresh (race expected)."; + enqueue_refresh_race(); +} + +void UserAccountCommunication::enqueue_refresh_race(const std::string refresh_token_from_store/* = std::string()*/) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " T3"; + if (!m_session->is_initialized()) { + BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; + return; + } + if (refresh_token_from_store.empty() && m_session->is_enqueued(UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN)) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Token refresh already enqueued, skipping..."; + return; + } + // At this point, last master did not renew the tokens, behave like master + m_session->enqueue_refresh_race(); + wakeup_session_thread(); +} + +void UserAccountCommunication::on_race_lost() +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " T4"; + // race from on_slave_read_timer has been lost + // other instance was faster to renew tokens so refresh token from this app was denied (invalid grant) + // we should read the other token now. + std::string current_access_token = m_session->get_access_token(); + StoreData stored_data; + read_stored_data(stored_data); + + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + const auto prior_expiration_secs = std::max(m_last_token_duration_seconds / 24, 10); + if (expires_in_second > 0 && expires_in_second > prior_expiration_secs) { + BOOST_LOG_TRIVIAL(debug) << "Token is alive - using it."; + set_tokens(stored_data); + return; + } + BOOST_LOG_TRIVIAL(debug) << "No suitable token found waiting " << std::max((expires_in_second / 2), (long long)2); + m_after_race_lost_timer->StartOnce(std::max((expires_in_second / 2) * 1000, (long long)2000)); +} + +void UserAccountCommunication::on_after_race_lost_timer(wxTimerEvent& evt) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " T5"; + + std::string current_access_token = m_session->get_access_token(); + StoreData stored_data; + read_stored_data(stored_data); + + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + const auto prior_expiration_secs = std::max(m_last_token_duration_seconds / 24, 10); + if (expires_in_second > 0 && expires_in_second > prior_expiration_secs) { + BOOST_LOG_TRIVIAL(debug) << "Token is alive - using it."; + set_tokens(stored_data); + return; + } + BOOST_LOG_TRIVIAL(warning) << "No new token is stored - This is error state. Logging out."; + do_logout(); +} + +void UserAccountCommunication::set_tokens(const StoreData store_data) +{ + if (m_token_timer->IsRunning()) { + m_token_timer->Stop(); + } + if (m_slave_read_timer->IsRunning()) { + m_slave_read_timer->Stop(); + } + if (m_after_race_lost_timer->IsRunning()) { + m_after_race_lost_timer->Stop(); + } + + long long next = store_data.next_timeout.empty() ? 0 : std::stoll(store_data.next_timeout); + m_session->set_tokens(store_data.access_token, store_data.refresh_token, store_data.shared_session_key, next); + enqueue_id_action(); +} + +void UserAccountCommunication::on_store_read_request() +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + StoreData stored_data; + read_stored_data(stored_data); + + if (stored_data.refresh_token.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out."; + do_logout(); + return; + } + + std::string currrent_access_token = m_session->get_access_token(); + if (currrent_access_token == stored_data.access_token) + { + BOOST_LOG_TRIVIAL(debug) << "Current token is up to date."; + return; + } + + long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr); + const auto prior_expiration_secs = std::max(m_last_token_duration_seconds / 24, 10); + if (expires_in_second > 0 /*&& expires_in_second > prior_expiration_secs*/) { + BOOST_LOG_TRIVIAL(debug) << "Token is alive - using it."; + set_tokens(stored_data); + return; + } else { + BOOST_LOG_TRIVIAL(warning) << "Token read from store is expired!"; + } +} + void UserAccountCommunication::on_polling_timer(wxTimerEvent& evt) { + if (!m_window_is_active) { return; } wakeup_session_thread(); } -std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier) +std::string CodeChalengeGenerator::generate_challenge(const std::string& verifier) { std::string code_challenge; try @@ -558,7 +885,7 @@ std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Code Chalenge Generator failed: " << e.what(); + BOOST_LOG_TRIVIAL(error) << "Code Challenge Generator failed: " << e.what(); } assert(!code_challenge.empty()); return code_challenge; diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index e8d1e42aa0..458857255e 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -22,7 +22,7 @@ class CodeChalengeGenerator public: CodeChalengeGenerator() {} ~CodeChalengeGenerator() {} - std::string generate_chalenge(const std::string& verifier); + std::string generate_challenge(const std::string& verifier); std::string generate_verifier(); private: std::string generate_code_verifier(size_t length); @@ -32,6 +32,14 @@ private: class UserAccountCommunication : public wxEvtHandler { + public: + struct StoreData { + std::string access_token; + std::string refresh_token; + std::string shared_session_key; + std::string next_timeout; + std::string master_pid; + }; public: UserAccountCommunication(wxEvtHandler* evt_handler, AppConfig* app_config); ~UserAccountCommunication(); @@ -51,6 +59,7 @@ public: void enqueue_connect_printer_models_action(); void enqueue_avatar_old_action(const std::string& url); void enqueue_avatar_new_action(const std::string& url); + void enqueue_id_action(); void enqueue_test_connection(); void enqueue_printer_data_action(const std::string& uuid); void enqueue_refresh(); @@ -64,7 +73,7 @@ public: void on_activate_app(bool active); - void set_username(const std::string& username); + void set_username(const std::string& username, bool store); void set_remember_session(bool b); bool get_remember_session() const {return m_remember_session; } @@ -79,6 +88,10 @@ public: void set_refresh_time(int seconds); void on_token_timer(wxTimerEvent& evt); void on_polling_timer(wxTimerEvent& evt); + void set_tokens(const StoreData store_data); + + void on_race_lost(); // T4 + void on_store_read_request(); private: std::unique_ptr m_session; std::thread m_thread; @@ -104,8 +117,15 @@ private: void login_redirect(); std::string client_id() const { return Utils::ServiceConfig::instance().account_client_id(); } + // master / slave logic + std::unique_ptr m_slave_read_timer; // T2 timer + std::unique_ptr m_after_race_lost_timer; // T5 timer + int m_last_token_duration_seconds {0}; - + void on_slave_read_timer(wxTimerEvent& evt); // T2 + void read_stored_data(StoreData& result); + void enqueue_refresh_race(const std::string refresh_token_from_store = std::string()); // T3 + void on_after_race_lost_timer(wxTimerEvent& evt); // T4 }; } } diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 62b7bb00c4..114fd5258e 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -21,6 +21,7 @@ namespace GUI { wxDEFINE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent); wxDEFINE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent); +wxDEFINE_EVENT(EVT_UA_ID_USER_SUCCESS_AFTER_TOKEN_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_STATUS_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_MODELS_SUCCESS, UserAccountSuccessEvent); @@ -28,33 +29,45 @@ wxDEFINE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, UserAccountSuccessEvent); wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent); +wxDEFINE_EVENT(EVT_UA_RACE_LOST, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); wxDEFINE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent); +wxDEFINE_EVENT(EVT_UA_RETRY_NOTIFY, UserAccountFailEvent); +wxDEFINE_EVENT(EVT_UA_CLOSE_RETRY_NOTIFICATION, SimpleEvent); void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const { std::string url = m_url; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << url; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " " << url; auto http = Http::post(std::move(url)); if (!input.empty()) http.set_post_body(input); http.header("Content-type", "application/x-www-form-urlencoded"); http.on_error([fail_callback](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(trace) << "UserActionPost::perform on_error"; if (fail_callback) fail_callback(body); }); http.on_complete([success_callback](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(trace) << "UserActionPost::perform on_complete"; if (success_callback) success_callback(body); }); + http.on_retry([&](int attempt, unsigned delay) { + BOOST_LOG_TRIVIAL(trace) << "UserActionPost::perform on_retry " << attempt; + if (attempt > 1) { + wxQueueEvent(evt_handler, new UserAccountFailEvent(EVT_UA_RETRY_NOTIFY, GUI::format(_u8L("Communication with Prusa Account is taking longer than expected. Retrying. Attempt %1%."), std::to_string(attempt)))); + } + return true; + }); http.perform_sync(HttpRetryOpt::default_retry()); } void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const { std::string url = m_url + input; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << url; + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " " << url; auto http = Http::get(std::move(url)); if (!access_token.empty()) { http.header("Authorization", "Bearer " + access_token); @@ -68,6 +81,7 @@ void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::strin #endif } http.on_error([evt_handler, fail_callback, action_name = &m_action_name, fail_evt_type = m_fail_evt_type](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(trace) << "UserActionGetWithEvent::perform on_error"; if (fail_callback) fail_callback(body); std::string message = GUI::format("%1% action failed (%2%): %3%", action_name, std::to_string(status), body); @@ -75,12 +89,19 @@ void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::strin wxQueueEvent(evt_handler, new UserAccountFailEvent(fail_evt_type, std::move(message))); }); http.on_complete([evt_handler, success_callback, succ_evt_type = m_succ_evt_type](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(trace) << "UserActionGetWithEvent::perform on_complete"; if (success_callback) success_callback(body); if (succ_evt_type != wxEVT_NULL) wxQueueEvent(evt_handler, new UserAccountSuccessEvent(succ_evt_type, body)); }); - + http.on_retry([&](int attempt, unsigned delay) { + BOOST_LOG_TRIVIAL(trace) << "UserActionGetWithEvent::perform on_retry " << attempt; + if (attempt > 1) { + wxQueueEvent(evt_handler, new UserAccountFailEvent(EVT_UA_RETRY_NOTIFY, GUI::format(_u8L("Communication with Prusa Account is taking longer than expected. Retrying. Attempt %1%."), std::to_string(attempt)))); + } + return true; + }); http.perform_sync(HttpRetryOpt::default_retry()); } @@ -101,6 +122,7 @@ void UserAccountSession::process_action_queue() std::lock_guard lock(m_session_mutex); if (!m_proccessing_enabled) return; + BOOST_LOG_TRIVIAL(trace) << "action queue: " << m_priority_action_queue.size() << " " << m_action_queue.size(); if (m_priority_action_queue.empty() && m_action_queue.empty()) { // update printers periodically enqueue_action_inner(m_polling_action, nullptr, nullptr, {}); @@ -173,11 +195,38 @@ void UserAccountSession::init_with_code(const std::string& code, const std::stri } } +void UserAccountSession::remove_from_queue(UserAccountActionID action_id) +{ + { + std::lock_guard lock(m_session_mutex); + + auto it = std::find_if( + std::begin(m_priority_action_queue), std::end(m_priority_action_queue), + [action_id](const ActionQueueData& item) { return item.action_id == action_id; } + ); + while (it != m_priority_action_queue.end()) + { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + m_priority_action_queue.erase(it); + it = std::find_if( + std::begin(m_priority_action_queue), std::end(m_priority_action_queue), + [action_id](const ActionQueueData& item) { return item.action_id == action_id; } + ); + } + } + +} + void UserAccountSession::token_success_callback(const std::string& body) { // No need to use lock m_session_mutex here - BOOST_LOG_TRIVIAL(debug) << "Access token refreshed"; + // This is here to prevent performing refresh again until USER_ACCOUNT_ACTION_USER_ID_AFTER_TOKEN_SUCCESS is performed. + // If refresh with stored token was enqueued during performing one we are in its success_callback, + // It would fail and prevent USER_ID to write this tokens to store. + remove_from_queue(UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN); + + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " Access token refreshed"; // Data we need std::string access_token, refresh_token, shared_session_key; try { @@ -209,14 +258,21 @@ void UserAccountSession::token_success_callback(const std::string& body) m_access_token = std::string(); m_refresh_token = std::string(); m_shared_session_key = std::string(); + m_next_token_timeout = 0; } wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(msg))); return; } - //BOOST_LOG_TRIVIAL(info) << "access_token: " << access_token; + if (!access_token.empty()) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token: " << access_token.substr(0,5) << "..." << access_token.substr(access_token.size()-5); + } else { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token empty!"; + } + //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ <<" access_token: " << access_token; //BOOST_LOG_TRIVIAL(info) << "refresh_token: " << refresh_token; //BOOST_LOG_TRIVIAL(info) << "shared_session_key: " << shared_session_key; + //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ <<" expires_in: " << std::time(nullptr) + expires_in; { std::lock_guard lock(m_credentials_mutex); m_access_token = access_token; @@ -224,10 +280,29 @@ void UserAccountSession::token_success_callback(const std::string& body) m_shared_session_key = shared_session_key; m_next_token_timeout = std::time(nullptr) + expires_in; } - enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID, nullptr, nullptr, {}); + enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID_AFTER_TOKEN_SUCCESS, nullptr, nullptr, {}); wxQueueEvent(p_evt_handler, new UserAccountTimeEvent(EVT_UA_REFRESH_TIME, expires_in)); } +void UserAccountSession::set_tokens(const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key, long long expires_in) +{ + if (!access_token.empty()) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token: " << access_token.substr(0,5) << "..." << access_token.substr(access_token.size()-5); + } else { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ <<" access_token empty!"; + } + + { + std::lock_guard lock(m_credentials_mutex); + m_access_token = access_token; + m_refresh_token = refresh_token; + m_shared_session_key = shared_session_key; + m_next_token_timeout = /*std::time(nullptr) +*/ expires_in; + } + long long exp = expires_in - std::time(nullptr); + wxQueueEvent(p_evt_handler, new UserAccountTimeEvent(EVT_UA_REFRESH_TIME, exp)); +} + void UserAccountSession::code_exchange_fail_callback(const std::string& body) { @@ -240,6 +315,7 @@ void UserAccountSession::code_exchange_fail_callback(const std::string& body) void UserAccountSession::enqueue_test_with_refresh() { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; { std::lock_guard lock(m_session_mutex); // on test fail - try refresh @@ -250,6 +326,7 @@ void UserAccountSession::enqueue_test_with_refresh() void UserAccountSession::enqueue_refresh(const std::string& body) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; wxQueueEvent(p_evt_handler, new SimpleEvent(EVT_UA_ENQUEUED_REFRESH)); std::string post_fields; { @@ -278,6 +355,33 @@ void UserAccountSession::refresh_fail_callback(const std::string& body) wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, body)); } +void UserAccountSession::enqueue_refresh_race(const std::string refresh_token_from_store/* = std::string()*/) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + wxQueueEvent(p_evt_handler, new SimpleEvent(EVT_UA_ENQUEUED_REFRESH)); + std::string post_fields; + { + std::lock_guard lock(m_credentials_mutex); + assert(!m_refresh_token.empty()); + post_fields = "grant_type=refresh_token" + "&client_id=" + client_id() + + "&refresh_token=" + (refresh_token_from_store.empty() ? m_refresh_token :refresh_token_from_store); + } + { + std::lock_guard lock(m_session_mutex); + m_priority_action_queue.push_back({ UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN + , std::bind(&UserAccountSession::token_success_callback, this, std::placeholders::_1) + , std::bind(&UserAccountSession::refresh_fail_soft_callback, this, std::placeholders::_1) + , post_fields }); + } +} + +void UserAccountSession::refresh_fail_soft_callback(const std::string& body) +{ + cancel_queue(); + wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RACE_LOST, body)); +} + void UserAccountSession::cancel_queue() { { diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index 23ae62263a..b3f468c7e6 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -21,6 +21,7 @@ using UserAccountTimeEvent = Event; wxDECLARE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent); wxDECLARE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent); +wxDECLARE_EVENT(EVT_UA_ID_USER_SUCCESS_AFTER_TOKEN_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_STATUS_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_MODELS_SUCCESS, UserAccountSuccessEvent); @@ -28,9 +29,13 @@ wxDECLARE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent); wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_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 +wxDECLARE_EVENT(EVT_UA_RACE_LOST, UserAccountFailEvent); // Hard fail - clears all wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat wxDECLARE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); wxDECLARE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent); +wxDECLARE_EVENT(EVT_UA_RETRY_NOTIFY, UserAccountFailEvent); // Not fail yet, just retry attempt. string is message to ui. +wxDECLARE_EVENT(EVT_UA_CLOSE_RETRY_NOTIFICATION, SimpleEvent); + typedef std::function UserActionSuccessFn; typedef std::function UserActionFailFn; @@ -41,6 +46,7 @@ enum class UserAccountActionID { USER_ACCOUNT_ACTION_REFRESH_TOKEN, USER_ACCOUNT_ACTION_CODE_FOR_TOKEN, USER_ACCOUNT_ACTION_USER_ID, + USER_ACCOUNT_ACTION_USER_ID_AFTER_TOKEN_SUCCESS, USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN, USER_ACCOUNT_ACTION_TEST_CONNECTION, USER_ACCOUNT_ACTION_CONNECT_STATUS, // status of all printers by UUID @@ -104,11 +110,12 @@ struct ActionQueueData class UserAccountSession { public: - UserAccountSession(wxEvtHandler* evt_handler, const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key, bool polling_enabled) + UserAccountSession(wxEvtHandler* evt_handler, const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key, long long next_token_timeout, bool polling_enabled) : p_evt_handler(evt_handler) , m_access_token(access_token) , m_refresh_token(refresh_token) , m_shared_session_key(shared_session_key) + , m_next_token_timeout(next_token_timeout) , m_polling_action(polling_enabled ? UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS : UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY) { @@ -119,6 +126,7 @@ public: m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN] = std::make_unique("EXCHANGE_TOKENS", sc.account_token_url()); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN] = std::make_unique("EXCHANGE_TOKENS", sc.account_token_url()); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID] = std::make_unique("USER_ID", sc.account_me_url(), EVT_UA_ID_USER_SUCCESS, EVT_UA_RESET); + m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID_AFTER_TOKEN_SUCCESS] = std::make_unique("USER_ID_AFTER_TOKEN_SUCCESS", sc.account_me_url(), EVT_UA_ID_USER_SUCCESS_AFTER_TOKEN_SUCCESS, EVT_UA_RESET); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN] = std::make_unique("TEST_ACCESS_TOKEN", sc.account_me_url(), EVT_UA_ID_USER_SUCCESS, EVT_UA_FAIL); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION] = std::make_unique("TEST_CONNECTION", sc.account_me_url(), wxEVT_NULL, EVT_UA_RESET); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique("CONNECT_STATUS", sc.connect_status_url(), EVT_UA_PRUSACONNECT_STATUS_SUCCESS, EVT_UA_FAIL); @@ -159,6 +167,7 @@ public: // Special enques, that sets callbacks. void enqueue_test_with_refresh(); void enqueue_refresh(const std::string& body); + void enqueue_refresh_race(const std::string refresh_token_from_store = std::string()); void process_action_queue(); bool is_initialized() const { @@ -183,19 +192,23 @@ public: return m_next_token_timeout; } - //void set_polling_enabled(bool enabled) {m_polling_action = enabled ? UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS : UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY; } + void set_tokens(const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key, long long expires_in); + void set_polling_action(UserAccountActionID action) { std::lock_guard lock(m_session_mutex); m_polling_action = action; } private: void refresh_fail_callback(const std::string& body); + void refresh_fail_soft_callback(const std::string& body); void cancel_queue(); void code_exchange_fail_callback(const std::string& body); void token_success_callback(const std::string& body); std::string client_id() const { return Utils::ServiceConfig::instance().account_client_id(); } void process_action_queue_inner(); + void remove_from_queue(UserAccountActionID action_id); + // called from m_session_mutex protected code only void enqueue_action_inner(UserAccountActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input); diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 15fb080d40..19b6e18652 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -232,6 +232,10 @@ void WebViewPanel::on_show(wxShowEvent& evt) return; } + if (m_after_show_func_prohibited_once) { + m_after_show_func_prohibited_once = false; + return; + } after_on_show(evt); } @@ -422,7 +426,7 @@ void WebViewPanel::run_script(const wxString& javascript) // Remember the script we run in any case, so the next time the user opens // the "Run Script" dialog box, it is shown there for convenient updating. m_javascript = javascript; - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + BOOST_LOG_TRIVIAL(trace) << "RunScript " << javascript << "\n"; m_browser->RunScriptAsync(javascript); } @@ -615,6 +619,7 @@ ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) auto* plater = wxGetApp().plater(); plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); + plater->Bind(EVT_UA_ID_USER_SUCCESS_AFTER_TOKEN_SUCCESS, &ConnectWebViewPanel::on_user_token, this); m_actions["appQuit"] = std::bind(&WebViewPanel::on_app_quit_event, this, std::placeholders::_1); m_actions["appMinimize"] = std::bind(&WebViewPanel::on_app_minimize_event, this, std::placeholders::_1); @@ -641,6 +646,7 @@ void ConnectWebViewPanel::late_create() void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; e.Skip(); if (!m_browser) { return; @@ -660,6 +666,7 @@ ConnectWebViewPanel::~ConnectWebViewPanel() wxString ConnectWebViewPanel::get_login_script(bool refresh) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; Plater* plater = wxGetApp().plater(); const std::string& access_token = plater->get_user_account()->get_access_token(); assert(!access_token.empty()); @@ -810,6 +817,7 @@ void ConnectWebViewPanel::on_page_will_load() if (!m_browser) { return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; auto javascript = get_login_script(false); BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; m_browser->AddUserScript(javascript); @@ -859,6 +867,7 @@ void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) void ConnectWebViewPanel::on_connect_action_error(const std::string &message_data) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; ConnectRequestHandler::on_connect_action_error(message_data); // TODO: make this more user friendly (and make sure only once opened if multiple errors happen) // MessageDialog dialog( @@ -873,6 +882,7 @@ void ConnectWebViewPanel::on_connect_action_error(const std::string &message_dat void ConnectWebViewPanel::on_reload_event(const std::string& message_data) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; // Event from our error page button or keyboard shortcut m_styles_defined = false; try { @@ -891,6 +901,12 @@ void ConnectWebViewPanel::on_reload_event(const std::string& message_data) } } +void ConnectWebViewPanel::after_on_show(wxShowEvent& evt) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + run_script("window.location.reload();"); +} + void ConnectWebViewPanel::logout() { if (!m_browser || m_do_late_webview_create) { @@ -1377,6 +1393,7 @@ void PrintablesWebViewPanel::send_refreshed_token(const std::string& access_toke if (m_load_default_url) { return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; hide_loading_overlay(); wxString script = GUI::format_wxstr("window.postMessage(JSON.stringify({" "event: 'accessTokenChange'," @@ -1390,6 +1407,7 @@ void PrintablesWebViewPanel::send_will_refresh() if (m_load_default_url) { return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; wxString script = "window.postMessage(JSON.stringify({ event: 'accessTokenWillChange' }))"; run_script(script); } @@ -1410,7 +1428,7 @@ void PrintablesWebViewPanel::sys_color_changed() void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std::string& message_data) { - // { "event": "accessTokenExpired:) + // { "event": "accessTokenExpired") // There seems to be a situation where we get accessTokenExpired when there is active token from Slicer POW // We need get new token and freeze webview until its not refreshed if (m_refreshing_token) { @@ -1420,6 +1438,7 @@ void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std: BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; m_refreshing_token = true; show_loading_overlay(); + wxGetApp().plater()->get_user_account()->request_refresh(); } diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index 17d0a41710..ddf43d7a0e 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -76,6 +76,7 @@ public: void on_app_quit_event(const std::string& message_data); void on_app_minimize_event(const std::string& message_data); + void prohibit_after_show_func_once() {m_after_show_func_prohibited_once = true; } protected: virtual void late_create(); virtual void define_css(); @@ -112,6 +113,7 @@ protected: wxString m_response_js; wxString m_default_url; bool m_reached_default_url {false}; + bool m_after_show_func_prohibited_once {false}; std::string m_loading_html; std::string m_error_html; @@ -148,6 +150,7 @@ protected: void on_connect_action_close_dialog(const std::string& message_data) override {assert(false);} void on_user_token(UserAccountSuccessEvent& e); void define_css() override; + void after_on_show(wxShowEvent& evt) override; private: static wxString get_login_script(bool refresh); static wxString get_logout_script(); diff --git a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp index a03a573965..0f5f697f04 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp @@ -238,7 +238,7 @@ void RequestHeadersToLog(ICoreWebView2HttpRequestHeaders* requestHeaders) wchar_t* value = nullptr; iterator->GetCurrentHeader(&name, &value); - BOOST_LOG_TRIVIAL(debug) <<"name: " << name << L", value: " << value; + BOOST_LOG_TRIVIAL(trace) <<"name: " << name << L", value: " << value; if (name) { CoTaskMemFree(name); }