From e99a53079e5f98473e0789395b456eeaf25ac067 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 13 Jan 2025 17:08:34 +0100 Subject: [PATCH] Multicast for login and logout. Implemented on same base as single instance messages. Linux adds new Dbus object for this. Message format for instance messaging changed to json. --- src/slic3r/GUI/InstanceCheck.cpp | 506 ++++++++++++++++++-- src/slic3r/GUI/InstanceCheck.hpp | 56 ++- src/slic3r/GUI/InstanceCheckMac.h | 1 + src/slic3r/GUI/InstanceCheckMac.mm | 19 +- src/slic3r/GUI/Plater.cpp | 8 + src/slic3r/GUI/UserAccount.cpp | 5 + src/slic3r/GUI/UserAccount.hpp | 2 + src/slic3r/GUI/UserAccountCommunication.cpp | 44 ++ src/slic3r/GUI/UserAccountCommunication.hpp | 1 + 9 files changed, 587 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 882e2e3259..94735c81f4 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,162 @@ 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); + } + 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 +604,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 +626,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 +643,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 +666,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 +772,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 +794,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 +809,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 +858,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 +919,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 +976,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 +989,169 @@ 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; + + 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) << "DBus Connection Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating."; + dbus_error_free(&err); + return; + } + if (NULL == conn) { + BOOST_LOG_TRIVIAL(error) << "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) << "DBus Request name Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "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) << "Not primary owner of DBus name - probably another PrusaSlicer instance is running."; + BOOST_LOG_TRIVIAL(error) << "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) << "DBus Register object Error: "<< err.message; + BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating."; + dbus_connection_unref(conn); + dbus_error_free(&err); + return; + } + + BOOST_LOG_TRIVIAL(debug) << "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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1a99e84dcf..7ba1a3aa10 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); @@ -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()); diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp index a1c52aa153..09556b1b97 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; @@ -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() diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index 8049233ec3..e9657e86af 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -79,6 +79,8 @@ 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); diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index 382ad50c53..fde8b2c051 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -452,6 +452,10 @@ void UserAccountCommunication::do_clear() m_session->clear(); set_username({}); m_token_timer->Stop(); + m_slave_read_timer->Stop(); + m_after_race_lost_timer->Stop(); + m_next_token_refresh_at = 0; + m_behave_as_master = true; } void UserAccountCommunication::on_login_code_recieved(const std::string& url_message) @@ -821,11 +825,51 @@ void UserAccountCommunication::on_after_race_lost_timer(wxTimerEvent& evt) 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) { diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index 3d5ce0abf3..931c1dd57c 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -91,6 +91,7 @@ public: void set_tokens(const StoreData store_data); void on_race_lost(); // T5 + void on_store_read_request(); private: std::unique_ptr m_session; std::thread m_thread;