mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 04:15:52 +08:00
Merge branch 'dk_token_connect'
This commit is contained in:
commit
d16b08812b
@ -12,6 +12,7 @@
|
||||
#include <ctime>
|
||||
#include <cstdarg>
|
||||
#include <stdio.h>
|
||||
#include <random>
|
||||
|
||||
#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<unsigned> 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<unsigned> 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
|
||||
}
|
||||
|
||||
|
@ -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 <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <strsafe.h>
|
||||
@ -66,8 +70,6 @@ namespace instance_check_internal
|
||||
static CommandLineAnalysis process_command_line(int argc, char** argv)
|
||||
{
|
||||
CommandLineAnalysis ret;
|
||||
//if (argc < 2)
|
||||
// return ret;
|
||||
std::vector<std::string> arguments { argv[0] };
|
||||
bool send_if_url = false;
|
||||
bool has_url = false;
|
||||
@ -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<std::wstring*>(lParam);
|
||||
std::unique_ptr<LPWSTR> message = std::make_unique<LPWSTR>(const_cast<LPWSTR>(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<LPARAM>(&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<std::string>& 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<std::string> 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<std::mutex> lck(m_thread_stop_mutex);
|
||||
m_stop = true;
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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));
|
||||
}
|
||||
|
||||
|
||||
void OtherInstanceMessageHandler::handle_message_type_cli(const std::string& data)
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
bool parsed = unescape_strings_cstyle(message, 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<boost::filesystem::path>(std::move(paths))));
|
||||
//}
|
||||
}
|
||||
if (!downloads.empty())
|
||||
{
|
||||
if (!downloads.empty()) {
|
||||
wxPostEvent(m_callback_evt_handler, StartDownloadOtherInstanceEvent(GUI::EVT_START_DOWNLOAD_OTHER_INSTANCE, std::vector<std::string>(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<std::string>("type"); action) {
|
||||
type = *action;
|
||||
}
|
||||
if (const auto data_opt = ptree.get_optional<std::string>("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<std::mutex> lck(m_thread_stop_mutex);
|
||||
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; });
|
||||
std::unique_lock<std::mutex> 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 =
|
||||
" <!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
|
||||
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
|
||||
" <!-- dbus-sharp 0.8.1 -->"
|
||||
" <node>"
|
||||
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
|
||||
" <method name=\"Introspect\">"
|
||||
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
" <interface name=\"com.prusa3d.prusaslicer.MulticastListener\">"
|
||||
" <method name=\"Message\">"
|
||||
" <arg name=\"data\" direction=\"in\" type=\"s\" />"
|
||||
" </method>"
|
||||
" <method name=\"Introspect\">"
|
||||
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
" </node>";
|
||||
|
||||
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<std::mutex> 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
|
||||
|
@ -12,6 +12,7 @@
|
||||
#endif //_WIN32
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@ -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
|
||||
// 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<std::string, std::function<void(const std::string&)>> 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 };
|
||||
// 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();
|
||||
|
||||
// background thread method
|
||||
void listen();
|
||||
// "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__
|
||||
|
@ -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
|
||||
|
@ -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]];
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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<std::string> &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());
|
||||
@ -992,8 +1000,15 @@ void Plater::priv::init()
|
||||
// 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);
|
||||
|
@ -14,6 +14,9 @@
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "InstanceCheck.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
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<std::string>(section.first);
|
||||
if (opt) {
|
||||
BOOST_LOG_TRIVIAL(debug) << static_cast<std::string>(section.first) << " " << *opt;
|
||||
//BOOST_LOG_TRIVIAL(debug) << static_cast<std::string>(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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#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<std::string> 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<wxTimer>(this))
|
||||
, m_token_timer(std::make_unique<wxTimer>(this))
|
||||
, m_slave_read_timer(std::make_unique<wxTimer>(this))
|
||||
, m_after_race_lost_timer(std::make_unique<wxTimer>(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<std::string> 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<UserAccountSession>(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<UserAccountSession>(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());
|
||||
"|" + 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);
|
||||
if (!save_secret("tokens", m_session->get_shared_session_key(), tokens)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write tokens to the secret store.";
|
||||
}
|
||||
else {
|
||||
} 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<std::mutex> 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<std::string> 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;
|
||||
|
@ -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<UserAccountSession> 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<wxTimer> m_slave_read_timer; // T2 timer
|
||||
std::unique_ptr<wxTimer> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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()
|
||||
{
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ using UserAccountTimeEvent = Event<int>;
|
||||
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<void(const std::string& body)> UserActionSuccessFn;
|
||||
typedef std::function<void(const std::string& body)> 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<UserActionPost>("EXCHANGE_TOKENS", sc.account_token_url());
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", sc.account_token_url());
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID] = std::make_unique<UserActionGetWithEvent>("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<UserActionGetWithEvent>("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<UserActionGetWithEvent>("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<UserActionGetWithEvent>("TEST_CONNECTION", sc.account_me_url(), wxEVT_NULL, EVT_UA_RESET);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique<UserActionGetWithEvent>("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<std::mutex> 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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user