From 185f70182afc19ee8bd0fc53fc9dcbe85108686e Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 22 Apr 2020 20:01:29 +0200 Subject: [PATCH] Improved updater UI --- native/updater/CMakeLists.txt | 6 +- native/updater/main.cpp | 20 +- native/updater/test/dummy_config.json | 8 +- native/updater/test/lock_file.txt | 0 native/updater/test/update.log | 265 ++++++++++++ native/updater/ui.h | 17 + native/updater/win32/Resource.rc | 1 + native/updater/win32/logo.ico | Bin 0 -> 118476 bytes native/updater/win32/retry_ui.cpp | 555 ++++++++++++++++++++++++++ 9 files changed, 861 insertions(+), 11 deletions(-) create mode 100644 native/updater/test/lock_file.txt create mode 100644 native/updater/ui.h create mode 100644 native/updater/win32/Resource.rc create mode 100644 native/updater/win32/logo.ico create mode 100644 native/updater/win32/retry_ui.cpp diff --git a/native/updater/CMakeLists.txt b/native/updater/CMakeLists.txt index e0565cc..aa12006 100644 --- a/native/updater/CMakeLists.txt +++ b/native/updater/CMakeLists.txt @@ -9,10 +9,14 @@ set(SOURCE_FILES file.cpp ) +if (WIN32) + list(APPEND SOURCE_FILES win32/retry_ui.cpp win32/Resource.rc) +endif () + add_executable(update_installer ${SOURCE_FILES}) if(WIN32) - target_link_libraries(update_installer kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;Shlwapi.lib) + target_link_libraries(update_installer kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;Shlwapi.lib;Rstrtmgr.lib) add_custom_command(TARGET update_installer POST_BUILD COMMAND ${CMAKE_COMMAND} -E diff --git a/native/updater/main.cpp b/native/updater/main.cpp index fc183b8..f249186 100644 --- a/native/updater/main.cpp +++ b/native/updater/main.cpp @@ -3,10 +3,12 @@ #include #include -#include "logger.h" -#include "config.h" -#include "util.h" -#include "file.h" +#include "./logger.h" +#include "./config.h" +#include "./util.h" +#include "./file.h" + +#include "./ui.h" using namespace std; using namespace log_helper; @@ -116,7 +118,7 @@ int main(int argc, char** argv) { { auto admin = is_administrator(); logger::info("App executed as admin: %s", admin ? "yes" : "no"); - if(!admin) { + if(!admin && false) { logger::info("Requesting administrator rights"); if(!request_administrator(argc, argv)) { execute_callback_fail_exit("permissions", "failed to get administrator permissions"); @@ -137,20 +139,24 @@ int main(int argc, char** argv) { { logger::info("Awaiting the unlocking of all files"); + await_unlock: auto begin = chrono::system_clock::now(); while(true) { bool locked = false; for(shared_ptr& file : config::locking_files) { if(file::file_locked(file->filename)) { locked = true; - if(chrono::system_clock::now() - chrono::milliseconds(file->timeout) > begin) { + if(chrono::system_clock::now() - std::chrono::milliseconds{1000} > begin) { /* we don't use the lock timeout here because we've a new system */ + auto result = ui::open_file_blocked(file->filename); + if(result == ui::FileBlockedResult::PROCESSES_CLOSED) + goto await_unlock; logger::fatal( "Failed to lock file %s. Timeout: %d, Time tried: %d", file->filename.c_str(), file->timeout, chrono::duration_cast(chrono::system_clock::now() - begin).count() ); - execute_callback_fail_exit(file->error_id, "lock timeout (" + to_string(file->timeout) + ")"); + execute_callback_fail_exit(file->error_id, "failed to lock file"); } } } diff --git a/native/updater/test/dummy_config.json b/native/updater/test/dummy_config.json index f9dce5b..fc7b9b6 100644 --- a/native/updater/test/dummy_config.json +++ b/native/updater/test/dummy_config.json @@ -5,9 +5,11 @@ "callback_file": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "callback_argument_fail": "-EncodedCommand \"QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA==\"", "callback_argument_success": "-EncodedCommand \"QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAcwB1AGMAYwBlAGUAZABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA==\"", - "locks": [ - - ], + "locks": [{ + "filename": "D:\\Program Files\\IDA 7.0\\ida.exe", + "timeout": 5000, + "error-id": "Hello World error" + }], "moves": [ { "source": "source/file_new.txt", diff --git a/native/updater/test/lock_file.txt b/native/updater/test/lock_file.txt new file mode 100644 index 0000000..e69de29 diff --git a/native/updater/test/update.log b/native/updater/test/update.log index 9410ef9..876e4b7 100644 --- a/native/updater/test/update.log +++ b/native/updater/test/update.log @@ -355,3 +355,268 @@ [2] Update unpacking successfully! [2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with success with command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAcwB1AGMAYwBlAGUAZABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA==" bG9nX2ZpbGU6ZFhCa1lYUmxMbXh2Wnc9PQ== [2] ----------- log ended at 2019-04-16.16:46:25 ----------- +[523F][2] ----------- log started at 2020-04-22.19:51:40 ----------- +[523F][2] App executed as admin: no +[523F][2] Requesting administrator rights +[523F][2] Admin right granted. New updater instance executes the update now. +[523F][2] ----------- log ended at 2020-04-22.19:51:43 ----------- +[3DC4][2] ----------- log started at 2020-04-22.19:51:43 ----------- +[3DC4][2] App executed as admin: yes +[3DC4][2] loading config from file updater\test\dummy_config.json +[3DC4][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.305] cannot use operator[] with a string argument with string +[3DC4][2] ----------- log ended at 2020-04-22.19:51:43 ----------- +[39EE][2] ----------- log started at 2020-04-22.19:52:35 ----------- +[39EE][2] App executed as admin: no +[39EE][2] Requesting administrator rights +[39EE][2] Admin right granted. New updater instance executes the update now. +[39EE][2] ----------- log ended at 2020-04-22.19:52:38 ----------- +[52B2][2] ----------- log started at 2020-04-22.19:52:38 ----------- +[52B2][2] App executed as admin: yes +[52B2][2] loading config from file updater\test\dummy_config.json +[52B2][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.305] cannot use operator[] with a string argument with string +[52B2][2] ----------- log ended at 2020-04-22.19:52:38 ----------- +[35F3][2] ----------- log started at 2020-04-22.19:52:47 ----------- +[35F3][2] App executed as admin: no +[35F3][2] Requesting administrator rights +[35F3][2] Admin right granted. New updater instance executes the update now. +[35F3][2] ----------- log ended at 2020-04-22.19:52:49 ----------- +[64D5][2] ----------- log started at 2020-04-22.19:52:49 ----------- +[64D5][2] App executed as admin: yes +[64D5][2] loading config from file updater\test\dummy_config.json +[64D5][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.305] cannot use operator[] with a string argument with string +[64D5][2] ----------- log ended at 2020-04-22.19:52:49 ----------- +[65B6][2] ----------- log started at 2020-04-22.19:52:51 ----------- +[65B6][2] App executed as admin: no +[65B6][2] Requesting administrator rights +[65B6][5] callback file () is not executable! Ignoring fail callback +[65B6][2] ----------- log ended at 2020-04-22.19:52:53 ----------- +[59A3][2] ----------- log started at 2020-04-22.19:53:00 ----------- +[59A3][2] App executed as admin: no +[59A3][2] loading config from file updater\test\dummy_config.json +[59A3][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.305] cannot use operator[] with a string argument with string +[59A3][2] ----------- log ended at 2020-04-22.19:53:00 ----------- +[370F][2] ----------- log started at 2020-04-22.19:53:43 ----------- +[370F][2] App executed as admin: no +[370F][2] loading config from file updater\test\dummy_config.json +[370F][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.302] type must be string, but is null +[370F][2] ----------- log ended at 2020-04-22.19:53:43 ----------- +[65DD][2] ----------- log started at 2020-04-22.19:53:45 ----------- +[65DD][2] App executed as admin: no +[65DD][2] loading config from file updater\test\dummy_config.json +[65DD][5] failed to load config: failed to get key error-id. error: [json.exception.type_error.302] type must be string, but is null +[65DD][2] ----------- log ended at 2020-04-22.19:53:45 ----------- +[4225][2] ----------- log started at 2020-04-22.19:55:34 ----------- +[4225][2] App executed as admin: no +[4225][2] loading config from file updater\test\dummy_config.json +[4225][1] Loaded 1 locking actions and 1 moving actions +[4225][2] Awaiting the unlocking of all files +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][2] Failed to open file! (The system cannot find the file specified.) (2) +[4225][5] Failed to lock file lock_file.txt. Timeout: 5000, Time tried: 7941 +[4225][2] Rollbacking 0 moved files +[4225][2] Rollbacking 0 deleted files +[4225][2] Rollback done +[4225][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6U0dWc2JHOGdWMjl5YkdRZ1pYSnliM0k9O2Vycm9yX21lc3NhZ2U6Ykc5amF5QjBhVzFsYjNWMElDZzFNREF3S1E9PQ== +[4225][2] ----------- log ended at 2020-04-22.19:55:50 ----------- +[3BB5][2] ----------- log started at 2020-04-22.19:56:36 ----------- +[3BB5][2] App executed as admin: no +[3BB5][2] loading config from file updater\test\dummy_config.json +[3BB5][1] Loaded 1 locking actions and 1 moving actions +[3BB5][2] Awaiting the unlocking of all files +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][2] Failed to open file! (The system cannot find the file specified.) (2) +[3BB5][5] Failed to lock file lock_file.txt. Timeout: 5000, Time tried: 8660 +[3BB5][2] Rollbacking 0 moved files +[3BB5][2] Rollbacking 0 deleted files +[3BB5][2] Rollback done +[3BB5][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6U0dWc2JHOGdWMjl5YkdRZ1pYSnliM0k9O2Vycm9yX21lc3NhZ2U6Ykc5amF5QjBhVzFsYjNWMElDZzFNREF3S1E9PQ== +[3BB5][2] ----------- log ended at 2020-04-22.19:56:44 ----------- +[6DF9][2] ----------- log started at 2020-04-22.19:57:03 ----------- +[6DF9][2] App executed as admin: no +[6DF9][2] loading config from file updater\test\dummy_config.json +[6DF9][1] Loaded 1 locking actions and 1 moving actions +[6DF9][2] Awaiting the unlocking of all files +[6DF9][2] All files have been unlocked (0ms required) +[6DF9][2] Moving file from source/file_new.txt to target/file.txt +[6DF9][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[6DF9][2] Rollbacking 0 moved files +[6DF9][2] Rollbacking 0 deleted files +[6DF9][2] Rollback done +[6DF9][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[6DF9][2] ----------- log ended at 2020-04-22.19:57:03 ----------- +[2B0D][2] ----------- log started at 2020-04-22.19:57:18 ----------- +[2B0D][2] App executed as admin: no +[2B0D][2] loading config from file updater\test\dummy_config.json +[2B0D][1] Loaded 1 locking actions and 1 moving actions +[2B0D][2] Awaiting the unlocking of all files +[2B0D][2] All files have been unlocked (0ms required) +[2B0D][2] Moving file from source/file_new.txt to target/file.txt +[2B0D][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[2B0D][2] Rollbacking 0 moved files +[2B0D][2] Rollbacking 0 deleted files +[2B0D][2] Rollback done +[2B0D][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[2B0D][2] ----------- log ended at 2020-04-22.19:57:18 ----------- +[79AD][2] ----------- log started at 2020-04-22.19:57:34 ----------- +[79AD][2] App executed as admin: no +[79AD][2] loading config from file updater\test\dummy_config.json +[79AD][1] Loaded 1 locking actions and 1 moving actions +[79AD][2] Awaiting the unlocking of all files +[79AD][2] All files have been unlocked (0ms required) +[79AD][2] Moving file from source/file_new.txt to target/file.txt +[79AD][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[79AD][2] Rollbacking 0 moved files +[79AD][2] Rollbacking 0 deleted files +[79AD][2] Rollback done +[79AD][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[79AD][2] ----------- log ended at 2020-04-22.19:57:34 ----------- +[5665][2] ----------- log started at 2020-04-22.19:57:35 ----------- +[5665][2] App executed as admin: no +[5665][2] loading config from file updater\test\dummy_config.json +[5665][1] Loaded 1 locking actions and 1 moving actions +[5665][2] Awaiting the unlocking of all files +[5665][2] All files have been unlocked (0ms required) +[5665][2] Moving file from source/file_new.txt to target/file.txt +[5665][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[5665][2] Rollbacking 0 moved files +[5665][2] Rollbacking 0 deleted files +[5665][2] Rollback done +[5665][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[5665][2] ----------- log ended at 2020-04-22.19:57:35 ----------- +[0A74][2] ----------- log started at 2020-04-22.19:57:43 ----------- +[0A74][2] App executed as admin: no +[0A74][2] loading config from file updater\test\dummy_config.json +[0A74][1] Loaded 1 locking actions and 1 moving actions +[0A74][2] Awaiting the unlocking of all files +[0A74][2] All files have been unlocked (0ms required) +[0A74][2] Moving file from source/file_new.txt to target/file.txt +[0A74][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[0A74][2] Rollbacking 0 moved files +[0A74][2] Rollbacking 0 deleted files +[0A74][2] Rollback done +[0A74][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[0A74][2] ----------- log ended at 2020-04-22.19:57:43 ----------- +[4337][2] ----------- log started at 2020-04-22.19:57:44 ----------- +[4337][2] App executed as admin: no +[4337][2] loading config from file updater\test\dummy_config.json +[4337][1] Loaded 1 locking actions and 1 moving actions +[4337][2] Awaiting the unlocking of all files +[4337][2] All files have been unlocked (0ms required) +[4337][2] Moving file from source/file_new.txt to target/file.txt +[4337][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[4337][2] Rollbacking 0 moved files +[4337][2] Rollbacking 0 deleted files +[4337][2] Rollback done +[4337][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[4337][2] ----------- log ended at 2020-04-22.19:57:44 ----------- +[5F3A][2] ----------- log started at 2020-04-22.19:59:41 ----------- +[5F3A][2] App executed as admin: no +[5F3A][2] loading config from file updater\test\dummy_config.json +[5F3A][1] Loaded 1 locking actions and 1 moving actions +[5F3A][2] Awaiting the unlocking of all files +[5F3A][2] All files have been unlocked (0ms required) +[5F3A][2] Moving file from source/file_new.txt to target/file.txt +[5F3A][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[5F3A][2] Rollbacking 0 moved files +[5F3A][2] Rollbacking 0 deleted files +[5F3A][2] Rollback done +[5F3A][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[5F3A][2] ----------- log ended at 2020-04-22.19:59:41 ----------- +[3179][2] ----------- log started at 2020-04-22.19:59:52 ----------- +[3179][2] App executed as admin: no +[3179][2] loading config from file updater\test\dummy_config.json +[3179][1] Loaded 1 locking actions and 1 moving actions +[3179][2] Awaiting the unlocking of all files +[3179][2] All files have been unlocked (0ms required) +[3179][2] Moving file from source/file_new.txt to target/file.txt +[3179][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[3179][2] Rollbacking 0 moved files +[3179][2] Rollbacking 0 deleted files +[3179][2] Rollback done +[3179][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[3179][2] ----------- log ended at 2020-04-22.20:00:03 ----------- +[5E98][2] ----------- log started at 2020-04-22.20:00:53 ----------- +[5E98][2] App executed as admin: no +[5E98][2] loading config from file updater\test\dummy_config.json +[5E98][1] Loaded 1 locking actions and 1 moving actions +[5E98][2] Awaiting the unlocking of all files +[5E98][2] All files have been unlocked (0ms required) +[5E98][2] Moving file from source/file_new.txt to target/file.txt +[5E98][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[5E98][2] Rollbacking 0 moved files +[5E98][2] Rollbacking 0 deleted files +[5E98][2] Rollback done +[5E98][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[5E98][2] ----------- log ended at 2020-04-22.20:00:54 ----------- +[59B2][2] ----------- log started at 2020-04-22.20:00:59 ----------- +[59B2][2] App executed as admin: no +[59B2][2] loading config from file updater\test\dummy_config.json +[59B2][1] Loaded 1 locking actions and 1 moving actions +[59B2][2] Awaiting the unlocking of all files +[59B2][2] All files have been unlocked (0ms required) +[59B2][2] Moving file from source/file_new.txt to target/file.txt +[59B2][5] failed to move file source/file_new.txt to target/file.txt (source file does not exists) +[59B2][2] Rollbacking 0 moved files +[59B2][2] Rollbacking 0 deleted files +[59B2][2] Rollback done +[59B2][2] executing callback file C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe with fail command line C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -EncodedCommand "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7ACAAWwBTAHkAcwB0AGUAbQAuAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBNAGUAcwBzAGEAZwBlAEIAbwB4AF0AOgA6AFMAaABvAHcAKAAiAFUAcABkAGEAdABlACAAZgBhAGkAbABlAGQAIgAsACIAVQBwAGQAYQB0AGUAcgAiACwAMAApAA=="bG9nX2ZpbGU6ZFhCa1lYUmxjbHgwWlhOMFhIVndaR0YwWlM1c2IyYz07ZXJyb3JfaWQ6TURBd01RPT07ZXJyb3JfbWVzc2FnZTpjMjkxY21ObElHWnBiR1VnWkc5bGN5QnViM1FnWlhocGMzUno= +[59B2][2] ----------- log ended at 2020-04-22.20:01:11 ----------- diff --git a/native/updater/ui.h b/native/updater/ui.h new file mode 100644 index 0000000..3e2e184 --- /dev/null +++ b/native/updater/ui.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace ui { + enum struct FileBlockedResult { + UNSET, + PROCESSES_CLOSED, + CANCELED, + INTERNAL_ERROR + }; + +#ifdef WIN32 + extern void init_win32(); +#endif + extern FileBlockedResult open_file_blocked(const std::string&); +} \ No newline at end of file diff --git a/native/updater/win32/Resource.rc b/native/updater/win32/Resource.rc new file mode 100644 index 0000000..175cacf --- /dev/null +++ b/native/updater/win32/Resource.rc @@ -0,0 +1 @@ +101 ICON "logo.ico" \ No newline at end of file diff --git a/native/updater/win32/logo.ico b/native/updater/win32/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..04be6c1c2c19be1976df1ce3e51951cba9a98769 GIT binary patch literal 118476 zcmeEP1$>mp(>|OQC=_?6&{8N+3PoeMyAy&_+%*uzy|}x(d)!DMxI1luKc$7XSh4w@ zXWx4-m*Wx=2rcxx`(^U_?(EF&?C$K$tih1Tklc_iodL(ZhHgm>hAsw!A#YyodM)lp z;$Ddo#_K``!{AH?gT1|WUBuB~$X&r;aCbLer%Yt1HWCTo$Ml?Q8VtQhCo&YlHIl*| z?LH0$1JcboSOax!_RqbYqrnKD;8+&W%gxzgB7x_gE>2|&<;;)+pJW3hHv3fnE-~J# z?B;C0!L6D_zyiMn;`%+8%VM{T6RybK;LPD$OJ z9l9g0yOVo2mW?w;Nzk^Xa?EFof{-1{#k+rd89r>WG-z;4dh`gA!Gp*Bo-SSL8l#-; z4fxIlY5ciz!az9_yjOlccSL?Vb69Si3;_hojZ>jAWt^w{@PnVEOXnk{N`>M3>9>+6 zH++kHivfoVYo`vEOA&|Utp8RC-?~5o*Gv`v6%!<2Ym~SSbrVOYa7mNqptNcg zCLPSR2E;mny$=Q(I61H=h99%t3b}b&O z;J~V>5_w>~Ts-P8zB|^+p4D??*KAiA)49e|(ECOB-nDXY&5ttQy@$BAcae^Dx=CaE zK~lTiNCmxHxyr<$6J&I+KC;lGr}(U$A^scY$oL=XzVPZ;?bhVJt;D5vU-`DkOv#gD zvt-G9Kr&|Xm5dqvBm>}!!a-8Fu)li7clhq)zO4n{{^I6r*L(JeZfD1hSo5+;eIF@a zEI@MR^p{NdK0^i#GG_FZ969{dGrq%j@y$8ZwLU#-ew)avy+iJ~!&~)OK7Qz)h2y8+ znlx&~GuM8bWWW#Gr9Xh@yti=N^jmy~?|QX&%-yqA=|rr@>G1!TZLJNRD`)*=Oveg^ zXAZ1icfrUuJr{X&a3%1Z_xP-{UDi+T>}adLF>eDO>-mEu27@1vcQ*nKvHq^7Fc?Il zfN~};z#c&Pqka%PG07S2h1ma7CIAuvQUkI9@)?2WyvJv;ClZgR?ww4sFB#JP63`Ja zAK(i(1-N1ap7S1`bwbHWG5_$b!OcbGT?FTg0e=8go{*<9s;#~7h%!Qc zS5?4s{h50^2kF-T>y^9&i_+%H-9d8f4oK;=gI3TnIlT zKb?(`UoJ#K4o3&x?_*>g*pG->!8`L|^G-JMN!yfpH?%Df3nynCrWljUN*I{bVt zLiH!t(SKe$9;o2@iC}f_Hr|~v!Alx9ZXgE^+>*R`{Z+oqpLkTS9x49*Mlrdwjo64@|&wG59BjaabeH)kiyldU>^f5|nE^wCvF~FgP9ZGEe587I1*=hhv=MzT4z7@(sth!FTBxE-zc~HNcSjQd!sspC7>=U}z zmA&)bRr!L}OjC5f5VA+Ep9n&`3YVYF>mb{KY755tubxH9)svwzuy;2p``xz~4&XT2z7KmNN@=~j2b*WjSx>P9jm28|cSQfdu==p~t|GOCP zNHE5=zo5=(c}BHyl|I@P+qP;87|UHb7AO}ZeB}J$g9>=gdpGcm@9^C}?p&8&Z(fvR zf%|0XoT)P2wZCkdK3onh@|2Ae`zm>gy#I%?Kbz2_0mg@WO?fjjjKK16NO$9X}4?7?XA28S;NQu~#EG54p*4E#E&Ex?gy?viUp!uxjq9V@6Vo`fcD9@i-KS`C zHPTOfmQR$iU22jCF9DCncdsWW{I<){0~=-CwBaaIW6;L|`Ps{m7PX{nJ!ffxaj#?P z2~xV~eEF*2Dk+qAgXGV>SwVr^8>EH<#*&`fWzM9n;yGj;zHw3CG7YB1G@16%gPSDs z;6@qOtq${l2pU{vA2Vlo2N~P7mbg3Hp?o!^ea$W^zb^}{ksR6fDjCi(GlAo0j(yD` zTXuh`i8fxT@)60L*+;Tx-6Q;#I5RD#$+VdV^J1N2-hdqdoTI$$8%iU)Ve(ah6_Pc} zeq&y~DsRfrHvwf$h74MN!*_`v@nu>}lW8*#=B4GS^6m&a+$En6tY0M)y+g&(E(ral zFXWdG($o5`H|1^loHCc+G7YBXfHWs~g{nNJ_G?Po{|X#BV!XjQW<~H|&8#79@9$nR zUV?Y+kkt!Aq*u2PsaQTh^5!9}wX~RCdZf#=&4KsSGd$ocaBWo)^JIs)lY2M1wrXO(7i^P zY^e0?8X}GA1&Ljy0Qv4)e<@MiU%mnGoc9{y8Q-iDFL73 zJR2al5wfX!eAfJXz7tzgwr4g9*eGD6fILVm;%=~eg?Z@JY63V{9sqDP0?&Dm z&-hMy<9z;Kp$GD(J|PK?`2lSKivULfoZtTqcm>eMBKnc{_>AxH-NgWBK>i6`szWPg zAQT1^1Ec~Z=+C`Mu;8e=&HR-d8#)Q|OUR8G=- zl1zH_>LpK~zL5L(U&>#9J(LqCBB3jqEH!G>z_>lLKqvGE5Dx|P2fVR<0Z-_T97y+X z0S5rD=?IAz@g*Ld>&zbBURF*VC|l=w%iiTvq2r&Wfako&XMAUT_xkD^4~*CAmoFlz zQl)|pW1obF{{`AdNS{9EWc&7e^2;yJ6`6}U^Toe0+SaOb1z;5cXf1@|}yzji!Gz3Yo_ zmW=KrU+2#zB}$f*d-r~YZs@8>FPANwuaqtwD&4!Em5m$kqAbtlfB(BH<;$0+j_wd3 zwY35eclY*{N%tmzU!voVIs&{WL7%gAuD6`@-yt_Km)7vJqEEn_-K1MEJLf$7&ZXnx zziXpp&X`^X4jhPj^BlURBTBcU&v`iKqK=32j=p`*qkcVu4z4lrKLYsN5`TBSfT6OP1V{ zt5+}Mojk;UiY4x-FS>jz1>Db@)1CUeY5ki^;FiUT-j=$QnDI60k<4hXzkBUem{fPN zmy)GQ$#1{i6Hm`8(P*#ZP1=9`b&y=T^i-BDTf%ny2)I|b#2@$)|BQe=(Q$W>`J+0k zx?+{yW;!S0M%@h8FR6Qt(!&NRUFpr!hgBOO?frLeLfy$ALxv2I-+zBDRjNc-#h>-Z z+4&UO@!!&{Su?%vWHiTL!@Wu(9NFKS5gvr4CnHQ=J4|>l6uIKSK_0nTIvfRpQpan|$`!XV4q_N?_n0&>dT^ zJJflsSaDmfT)BvMa!ZCZDc5XtYmuatV{uavH2fV|Z+-$OSwr0CkLn^9pi9%~&U!${`l8ngbL z@0$bh@71c7syo5K0ay=tAlb81rxc~1(%UZU&g#{7aQdsg7Sv2DO!pn1(ok|k?aIe-3~oId?n^-Gou43^R?M9O`lo??Ls#hEDO(=Pty|Az+qS!^pDI@_OtNJ0 zkxrdhcb-W7`t>DG*7P#cxr&ZEkJOPtXyH=7K$HKYo^yTW=&q&Ejqg!yfU+~b zxSHgx&J*L2>0CY*3|-(N>EE-9lq_CU9332F&YU@F>^yet7&&qz95VB=^z7MFQhl0I zrVi;KJ7x?~I1*PQ&cuBOuUU6y4s4<7@hP88s!l}VY<(OP6Mk{I&oZzd z6?Fn@d|0Dd;nmf=4xI!3zq2f~o8lS*(~p&Y#MOus-{TrG*RQ$G5xQ%o_+gD^-^%G~ z-F(NwaYEo4I`8pW=q}hO!G4JSBERLB`uAJcK(nz2ptoY|aPmP<=W3L*l=A z$^h}|P*pF(Z@`^vJ)*5O?K9T#==Fv4)yu{8-$<160QAXgv2HSNWM`RzHG;8NH{p5- zX~Fb3=HnWR0-oa@pYffsU2DsfK22q=Ye(6>aJ<~Qc3w^e?ZNu4pQ4?{Bcn_}-t6x= zE@7XuW5!UdQDMylX%Tk-*NSA4aji{C=qt5#zqsHUTNyaU;dqVZ(cfd5+8U^@%8Pk2Z{ncgXOsy>Ibg(jBaTA> zra22~KdfyF)D15wkb5K2*XT_9LOrkdf7Hste24>aAx^~2h&PYKnYh~llwA_*+V=UQ zC1BSf8PYFAN|p3iYrb56G^f@3#-DjHPv%pqgue{x8zKQa56SigV^o_2zQmt6+hZP( z49DhxFwYKEUo7(M4%@9|X!AjG#Q%s)8y6~dYXwMw{Gf?(E%kkrMaP+W_t7>a<`QluJdBW}O-V;xRl#_HK@lOOX-C z`v7F&Ovs;3T-SdN+>Sw3_6F`*VArE`J!%q(f4FDxZm-Ib745h)-u1&f4=49&qI5o? zuo1p|)CXk=m!N$|WYMg{GN5;`)UU%eXFn-akT$nIs=is`rK+SOSserAW-Jkq7OOsb^vzjXM8`xB#avz%d)_r)j^fqy5^^qrDTJ+p|2M0jfiG z&B6Bo#=(c>M92~G+kHeftqhliz-987F!350Dx-#m zDDZL(#l28jFypX#hwq*UKBB&58cd66QhqZJ$j4{Q6M4@C4z#06JF1tSd5_Qd4&UWB#%~ueEv5-z+6jhpY^6g!@Iz2K>Qp$- z1^5>2zZP(D2H$%Fh5<)60?*YwJmWjsyXu=%Xk*ly#g>)rnT-NA3fL%MqkxS9HVS-T z6wvI4=-=d?NU%C4Fjh*!BaDm;ZaDhk2&)6wGARoYE}+p;1h3Js$aLhQIPdc_9xBmfaQQ+ z0dXnETfaj+XEXb+U!_c*6x~T8W%E<4STSX1hBC8jp$*M(z;r-eKz2Z4K!W|C+cS*T zY%YtSyt4rJ-gmk6^j+K7OW|A@#gHhGbm`JX&Ye3azy5j`{&V|f(4axE8~IMzO`))) z#S_3;z&t=>KrR4!ULC6oeb#0Vb5XiAuElMh?J3*lk5RyL>NTl%<2!tp-`CerX* z4kuk=EU1+S?01a*-Oyt>2E4J{;2+^P%8@}5B}xpR>?LZn=;d|AWS5yGi!bb2LSR>P3ifor zpng1(XU|@#_Z~fZB$Fpkrj03S5DY*hlsErS4#TK+RmgWI0Ofj&JZHV-`q!Kh9hC1Q zZLDs<7Le=zQDuyaJqc|Fv^8RW$286a?Uq?y10_|;6k!Ro+y3=p(TXpvqny<-QtI*}0wx-K+BR zq#Y&wN>!Or2J$o^fHtk}!v?6j-?C*(dG+cA?E8KPEv(!2aeaxjNSpScRH}3sHoVW} z=bwL)B1MWY4cdN}jjjCd9jfZ~u9Z>l%T~+I^+EvG?R_`QQ)|)Wm*!`jFy+?U$)#gK zVqdWwa8D%5moHa!+TGo{ZJ_xY9@j#L47s4*->_kWAeI8l?+b|0cHh%kt&ykMs1_Q3AtbI9_1`SL-=U4dQd3uRAk%{EUjKi8Ua z=Jb<&`|hFqucTwgjx0N2I3UI!%FD8IF0#{l+3l4r9OEodUPak;#Fyu6ALd~F``Z^I z#dT00g?rn!ZBdpNu#5gJM!QzM{G^SYT{y-E&*kRL8&arHA*R9cVvKd4Wrw{q?JvCn zuZ`Kscdqr*o+wV`rW`W@c}klg+5<8^1O4>P?T{HCg7(SE1`jbKlpP*;eS@#*c-(D;C$wqPM%ABcr3>md&eTb;tg8Ho|Df}}8}3E( znd6*e7sjuqIu5yX8L{1HhwX>t#4+M{ob8)2 zTU5E1jWhFMh@sq|iFrF_d1>Rxm^h44L%HC$e7sTqjaS#cOUEjkY@IfHVB1=znCi3O zr?EoS<+*cjVhrG~Y}i=`tkHq@Jv=U}_m?eOCW#U!mV!B0_qBP6v5Y9I6YM}tyyy7- zFTMP1^Ryd{*tcHU>$9FyRx7z>g;#o6iMtQVM<11t4KpQT$6{rxe;W1`j1$ASe5^n0 z7ie#;_kWCyLt9clKLgu&+V+QTo+ovj$}9fUkL%T|SFm}1317NDFqgT8cr}q|7lDcf zIxR>Kj)^%oe)a0Pv}n;neP83dLVDfT`THlz>IlI6M)7~VUUnW?{)We-DDEI6#c|!x%{H+i$`qQ}q+Z{@*kXp#0E5AO9Gy&EL6o;i#-zGFv`N zlNxqRS!DY3>9CnUD}Vp}RPmN`n#jmM5TEG=;!_>R*gjCng{oB}FgJP$zp6_ZBju5_ zso`7F7V%)vmd(qE^Kot(|Jfgem_MT(>E4B-Rb4gl+*oGDIAULYWXBTa=RjW4e@d4T zsxMUKvObR6)k~z0*qQ*WP|73-4N4l0#f!oFMDQmnEbAup)#K-w(;ygSP7f##FzN2d``LqAYW=j)Cxva7ad_DN z&m$%e$7*_c-`BWbPybZtAsO#6LTXg8M?A<(iWU&u@NMa?^c$SlA3b_h9zTAllLPJK zA>v3iQgy#4e0nHLXAf#=saIl|S@vE4lia6`@%r(-m29U!6#dHRAH_PY;}jnrzU#ZJ zPqdk5tSb8EP4pTm^=ej=_3PG2@7}#t8PlgvFOH6mGH%>BS%>@0n>Uw~DL;{n>C(tT z=udo?c**(+z4aJQdL3Yydjb{#)IIWk5@KB)Ts&H}XSQYf?j&TLeiwSs{$BYHTsSU& zJ@{Q#E}A0^>(r3kIkRJolvvRsG2}h#N9OctrFZMvvY#=+AeY&nOpJ=-rPE*`${rY1 zetTIBJ3zK+g(u}w9NTJ~-$RT~>Z1Ok1xPH$fZ5 zrlp@z0OnLC*^Tm3hOHUf!?GW^g7R~X3H8B2+21o(aI|s0mGW8AM)&a{|E^;W%kmsW z%r4S$_mWABc9llHd3hFpRxRG`xARMggvNrUmNqh zbpG;4oanFaziEMNoarVD+`7W=rkV0PVV}!%bzQ%v+t>8_y#G4jS=*@f0X}CM^kJg^ z;Tq`3f4y}>P6WU&7(N}$+Z-2594WKePqXZ#!_MeFI-~*D)Lxp(|C+Ckd7q4SYpl!k z*PwoyzWTJG9{)okC`%724pGXvaX9klk1PKTgfYbDZux1tB_Ua61R zGRI5k3vSgZU=cg`GU?&HI}Sa#ANivQ5PYjsE~yP_}jdz=S^ z?_DRGX1OVyCt_#e8;xI<<<-ll`z*1XZq821Uu78j zy&e5I9hA=#{hz33-M)B|+_-R3W)gx^^i8y#;t%N>KtbdF>sLff-eCZ$;&iHMS zJ#$A{j)PeCKlJjGkIFZiI(zCg(ViIRkZ~?ZYt}0rTmHt=GlZ;fLj#soW4eUR{V>_(yz-(Su$;@ zG^jLGy43G3TNnK(H!fY2C1VGH7L|>3iC$N9UeMQrxDsdLu4Dr0E%|tO=L&NlDe|9X zzs~X#SU&3Z_{}k&t%{zs^XD_tTz`*8@|0yJUkA0QCmrGARj<+rlyfFx`L0!dQ(3Yc zRQ^Z0Ka%E$^Li{o`u}L)hxH3zsaiD>W4ga&;-u@cZR;)g@&6_*ng&bRuV*S>C;F4I z&MRAz00!GXG7iN*l*js(MtBRE@-@tqO;Ct^0;4< zCepG>AE{JgniK}_vSr<;%Aoriy^Ajs*RtBSJqiEppOn6bzW(&*(9>pKtS=QyOqUi; zeWhR1CaT^kTA0ZYwo8^_9LmC2l3P(WmQgP+j$wcmX4iOL72jyGS--F36w)0GKDU8? zT!n9bRP|Q(H6!mVb#U)`t@)~JdR(sMSf~nmxpE3 z?-`HFaU29teSpO?<$LJ{UiWO+Qfij-RP{Pb=7T0aSmN|=UF)xlNi@r@B9(K7{|(ffi@XO4s5^}Dh%W8NI4!H0iU!`=wt$=*w=n0Up>-1 zBi5Yai}$;{&pefnAJ+Pd74?%gEdt?(7AnVsA}~i{Osg1(ab zudM+j#WUKmEyVkvgT0y)F}6G7*US1w^A}{(0wrd?IvnGd1$N}*B40QwW$p?=9n-?lu8v1^-3@8iBy%<{+l}3VpNBtud@SOMf?CJ^BDatLhVe*z~FfFFZw7DL&bWCrR zC-YYI8@K?^6ACvi?oGN0jmiUOhqo=Z)$fo#(o}wlS#VwhFb;rv;cypo{1=olw1HyG z4)TODm-Ey8tA14bFdf;yUag~^!J7E_u>J5uKB$1_yvG;_e205B@f&{2G?*6CWZKNb zm>1hL^;cZGATC)v5gX70e%JrR5Bb4HNcnlD!TC1;X8_xp4{*GS`T@UL`RRZ-O5i!|<@k*6@Li;%>q3~upG=c!8}ndZ%oA%^3A=~Q+tLnr==As$ z=Xn4X0c`-T0NR3Y0r;aFM*-M#$>9p%h7owqdt96KH@?en_$||@2w<9@qAe%-&~4$( zX{hW<+GCu7y~&Ke4_tG}3djk_Z3Le4UV6U6cir&~zqS2r6tGdiMgbcIY!t9jz(xTZ z1#A?sQNTt48wC=E0w$v|(RY1;YpEUYUdKMyd2n<`MoD0PtMYSW90&Z;syGe|VQ^ht zr%>@6_@cYIW~c}Ll4ze9l4ze9QqTbpzZ5E-L)0d2LO=Q$)GvvCbl0xAzYTw; zqjve>{CC$-NpxL1>gCO2q=TDAC(u(70DJkU2W}d@wQYCY6XEKg`Y}#~J8^}_v?)#m z&TMfaNEF-8MgbcIY!t9j;J-`(ua0(x(OoNJ?F9SBJ3FO@9{O`YCZkR|8;*?WLf|>| z)O_|i-}P+glzdD#yF}id6LziXzfA9ct88j5f@=%y9Mgf+y1U>)P_*IxfEI{jDj<@zsx z>;7!Bc7Ps$IRIY(*Ar>?^%U?5AnyoFgK05Mrp-K<7xS!)b**fk&JM{tw61R$*RJw^ zg~T6JIXuzUygRFP*i>lSTp#WRSO8#qy$^T+cvn8h_j~5aylG>%4A2dAfw-l1YiEc0 zY5zgd@MA-UZE0A$8t~3A)nmWgFlZz;i%+`SrfvCr-Bky8yt|sU*e`pSm|{ ziT%Z2+x{*E_`!G;y#WIOwE)=wNdf<_f2uFjwNDlZ<{ybri>Dk_a zaVARvrU0%1KBl(*#(LJ=p_rsgoia9_$MlSK;XGh5pbek^fPKeD|AQ|a+Iy8GkoU#F zZ`v3B3`mIa`WyLyeEyMlV52Mjn^cf&8B~nIXgvPplTQ%K#{qlII?I<|ekq@R`f0T1 zV6f#imsl!yj}g=C8RA;$cV9oUF5JC)R}LIFpkhE*s8B)e_xpyrz&?Uwg=l-% z|5KF#x^lz79+k=a0)Rz;M+rgKH>F|XuQA>R{fOvyLjM!)L(BLQ^hu%L4gFIHJm)<= z<2$;q3crP~OH^Ejc+`!N`0kqmSxs%9{MX~1=^SP(zcOX8-(%fK#5lUD;+y^U+cU(# zj=3_teECxKAAWv*;_2zB_OIri+=-3xftZgOuy5!O_(y*pjKiqy?NAl`3I)8k@twwR zjYsrl9EbRJ+>?+o{ump8`#B$6GaJ5F%Mj;dtsM8=CZ`d1`W)hc=y7^@&U<{ucj%|Z z{cHIx(_mUmldjGteg9=|t60Lc>!&u=aR zsAFVoXjp>1-=9&3$2E2@z=L<;vE{cq&*>{UwO=z?J!!BU#J<;zm&CX@jDw>1jCe3w zoS%e@aTZlqmo~HtLitqY)3YV$ymDT(>xqr2t6we=B_75F8RA;?9?%#h;^5%^> z&b#ItuI>BGxUY;4%b4few*60j`ss;j4}fTi;)4Cd$dMy0=L~1S`GW6@`|d~>{9?P$ zg5!L^-oUNOGEiW_zgezlClsq;Hd@#VlX@-4x@SkEXc7$@^w=zj4> zOf&9@JEUzj`8;)$?B{+R+{;A&v43}Uy%eqO#}+feye=^Qb@}pP(!c+C=nU>44)kN_ z5MJs@t0VVMG42VV%K^>{I3GyxpL<96V{=dFcFK=+J&smm&cv7h-wDXe1gICPPoZo;pW`{CPc+$Iu3S09c>dMIqdRy0R&l(|Wq)k^H-Bbc7q}0= zci)A|fC1;_mtVB_vCM=rpl8pXI`8%4*o0=j;yq){x+varZT6kVd5Z6dd&WMUvh`ww zuWGX&BF`;l330q~A_V((`Hl19(3mA^Gp#wUm-?zRi@7cd|Y(0NeXUNfoDrTl?Q=q9Ooj=rT z_D6oZHcHOyQ98eL>C(xeLx)Vfq3j{ZUs# zJ-`0rIG$sz$0y&ZBVkNk>RaAzzo8Ff z97x&kvtzwv&z8kR`_DiBT&Snhf7Im+9(+N~@nhm?eC|{B|M0_^XybpzjAuN3@LPww zI4_eB{0E;I3x_e~x&!`-FW>n-c~2c3V|Fq&wjPhxye)l<=al_pMh#Va+UT^eT)8s# z^!iRUPy;r;Z*T9?^KSef!-v?(r1Y_Rl)R zwS#Nd9-H#`^Upt}X3d)BI8gS}zVw}c;2+2Pg#lc*k00-i@}BYEIgkIZ@LjJ5l>G;{ zt(B})-A3xqw_U{Dm-^mt)<2|=dHDSA<_LGU1JuZ3Axa2#bzq^ioUfw_N z!B5mL=A-grU4uT3dj+Wd8L@|jwnt;I+V_V0C`1A7oy1;^*r$X0HS&FJ&qi(UiD+q> z?SsVo7b63unxmbm?StlOe`1|4JZ{R`AIv!`lS=H?H;w<;Iab8_Dr>A~g@go8Bth&r)+coh7JSM-$ zOYTF)eG5xrO73x}?NvDq`+m)k zuuXH+ewExCiu;zG^4+A;y>$41+TZNPsSs7KIPTlOb&X`ng#F8nV{Wd;2L}i1tWj$< zefyqEP}$$7&$(!8KwJan+OLiSj<*0M0B`++4-4jdRVerA#}IGc`>dOTn6KFL)U3Tk z@h`rz*4U;|hsf(|#{=X-=pO7rx`T;y>-P-JL73{dWMoabI}7ez4usPU6s7o%iwO zv)TJbIZioz4f}7iT_4-GO0`Y4U!NtTO?=k&MB?6)G3?!#p!bdS2z6)2%;DIJPTRj8 zgjPHlJ9ex&Yh>fbJ8#_IGA3DXd7rYsT)8mV3_mgDK^qV!Cv8tn;*c2jqSZ?meB|A} zic8{3P!S0*>jtebZ6z@2SVt+PxVs<0|{vXG31FjZ>DheTQ#dpk%gc z*Ty!j?Xmk$@H*x^xR39gk=!y#8YZyPrb*!h}!}x%E z#-8@wtmHcT?)K?Uke0UpHXYWVM%_8fPVFKmGpd58M7M*DjLovs5P9b3fmRhzJw^shjE1<7|SC z`v(oW5X~mk-`^kUr#Ip78DxL=`en`cgdy*ydNEErc>AUw>j`wcC2_nF7ygs?(+0Fu zc6hP#Uh5Ay$3Ba_phGv!R(ncEjmO^~uil4j;NH8Pn^YATRhno|`&;T@_21R2kClB+ zLdpKzx&7cj5@kcmeB9jJO!-p=eU&G(jA--Po>1f^c^WJGxyOWe+p0dyVhh6P$B$&YQT`v^HMs- zHeqi{ZMey+K4?N$HL9;aKkGYKmD zhYpP{`+awB)8^J{w|z0og|ZGZMK7wmU) zz-rrPdp~h-oyK?e>C~ZoFnv35v4ptGA|J@FE={B9TGjsbw7t-Omo7b$k|jeDg#VNu zv=^iwfcekW)z!p%^1o=_%&+D$9D5t42pq|Kg=@_LO^jkhoG#HPg4!E~$ zoTfX(d;CW2t!&gasBx)T{XMY2LmE1LAC2~wHlF9sJ(Na`j!D6S0ZRVUR^x5^0gfN+ z?Ze>%^0$fov?(iJzPt&C#EBD0z4Aq3lKna#{h-f<-Imj9{0Gn3_PMtI=bQM?w!db| zP^^=LX}VoCo`(&5;11}{XM%R`qkq?QZaOcRhYxr~UIwh1r1GVni1P{R>!~;5-pJG$ zUJTu<_6@&s#7C}1`lC8xuYm2I!1y9P8-8y&?$`OQAOFPMt|obH{^MSe-tE-5?~QXl@}7Gqt9>-F?nxcb zW$brGUAWH@=-tiQ$=l0&GhVX|gU{5thpe9=5!)BZaq#7g|2D-d^6fhOvuUUibHkc}AdH$ zSiUT=$$p*xJWj)!E_HjRD!_Bd|C~6U0+_#R^?dt+v5N0(Kjekqit+ErcWs@9^@VlF zAL)_TYdm{*ANh^k2c%>bnF#+CU}or2-&UHQ?)kEu~gj0 zPkdL&ah=~-%Q?JxuBuDtv0vg<$Yb)FYxk!1n;>h5@#J?b^@{uE?p(i2vYFZZa1L0s zXi*i%W&Qf~a^uDgtkFJujsNC19zJ{_XU;s7g$sX{_U%u>mm@^kbW`S&_dJdobtzhW zfW3S7%4eT_X5xRgOzCCr$j&mU2lsZj?vrd*FBp5HK0xu`qrH=1OxLQk@0e`%j@5cZ zo87H*JtY!-EXPyvll8_n4tdV87WMn=r^#=&ZTj<>+Vuy<>zLLtZeYEllXFWGA1H@) zV0}e&W!fsCY5-`5ruY zuqhwrSEbxHvS;onS?bo&D&MT|b>e8}s@Nl1jrnNP%W;3GmG?~Pv0hE+UQhPUch|# z@AlyBauwsplJ}O^Po8LX!O!nEdGsi%Kj9d&bm`Kj{F1@fWJrfb;=9C4cFY(m^Rw@#eb27|6ThvUFLHG;&YNSC^*YZz)wn!j`(ia#qfBSt9VO2bP8M78 z<)6G}d3<+nkPdBINWuJh<&#fhkMqZIBK>ptqo3}XGiSixDE|%1dSH3YctDILq1UbK znbTum`#wT_H2c+s;E^@nS==M9O9QCyV;x|uhFD|jj=|p8Tc-|;x$UcQKV&=R)U>za z+>7%n6@vjZN*EiU53|lt4zMo>_uVB6ew--IEt^V_FA7R3D>41pZ*%U(xNognw}yT= zyEJUrP-f4bEvHVMQZXDTMY13?qd#q#s<$J z^E;CF-firu@1Ggl`;;fM2DXs>^W7}QdD=XU`W4RmPaa$^SFle#=V2eSTz^~naqGfS zp*`S@)8W|ve6K8eQ(t zQ>IK&x@29S_~gkGWlP?%W5;Ov-ek#=%CJt2#h2?T#{QT*nmMp}T>5|+-t83cp$~FA z6dV5;XJyTp9#L)IILGIl{K(EFm<#OD#_R0IRa^fs+v(f#^=-am{byaE{5ux7PgcyE zE?rz&%bZy=Rnwk3cOLvilbZOi%X1!+BuS!txah~{?d>h?+O@N^?Ns08%b7)1BPNE@ zRT}xv^VQ%Lc@`T_@$4Yl|L5rY3*kIQI}bJP`=O5PnmNoQV}jOBQ)@b^t?P5NSmsJ^ zL%;vhH(Fg_T%lv~@DKQdLB20rI8VMUQB2aJKZq@kTTIW=r20e#x->Agd%gYhxNFuh z_|?8~9C!u1r+puFzTDfwYJDO9=MHJD*6t|dsTZZ*NAbtj)-^iBrS7Ah+`D;EUcG!N z&!0Y)TNjVZsnA2Rart~1(6g(QFY~Q@j~Rz;Cb#s{y|E0>tRoK{>K@rmM2qpv}7`-#MPM$@qBD0Bz;r@0#t#pZ9Jn`kCYc zo}CT%mF;Vn$cTZx#j$b)$(SL1wAhg;k|&dr;QNq{4Q2DRA*S)ZCGUC7K7jr88~On9 zyaymgTMyQsNxd2&Hr_6b=QLe^yvl5=Bc^t8=gMh${_Lr|eDPfFUO$if^oxjUL#i%d z9>KZ9g(Cs7Yr}FGHKd=kYT8g*HK>91-a_L$Y!A%)du#lsodtCPV_};E{>BF5@tpf> zSbc|a?dY>f->r{&{)YBN`o;$mdK~-5-JjLC@A04aReV8n-dmm%Pu2ta6>?sA4gKxu zL+jNVSX_0k`!FY*(ZA`N`hYdyy>D#%-v&Q@;$w;92Xk%CS7ZAj?T8=kl2~^ctM3YQ z5tonptC$0f?en^3S&3FWShwt(meyiIyF--#Fy_wgexX|JDrIYgP$s-K=5WDr~ zE5I#!+kaCUI{mp0#xa=HHg6pN>(3}7sIPm&7{CX-KM@oE*?$~Dj76QEZ^HAvKG*qd z3mF8JCXfE%Om)WX|yR!WctS`?tXQ zbyKy5t;hf6dN%Lz*^I$$)w_&M!fzR8l4;aC0_di+XKl*;!c82d= zhx-0c_x(m$ZESCBgXA;U9Yc1kkiE;NBHpW;tQgl{=DK#2$-SE>pEK`H)m3>JLymEq zqhdE}@mbjx;tu%6n1)IRX>$FGdC%|SM zZ?N6oxpKJP~OO_TbGH+*ue@>lEgZ{pc~ zYtKoi@mTLU709vQ`CVLy}*xvS-DQDsD8($g;AID;gNb6R)@PsJTIm&)8P5 zc21ipWpigX1|a`A2jIF*Ec~avht+i-#s8iSVzlp#5ht@bjpaLfdAUBou@+;0v5hS9 z>Y>`Uil2=3WEoF87W`u?+rRdVv>S)Gc4s33RNFt|vqQx|VLhgg_IV-SBM7W$F*R&Bjm29A$7wtAQLuk)NS-Yy_5 z+6=FoHbNK&isK97_ObKZlD-@#99TCWbA9aF@$)r|6EF@3@AdacTfNMTkMiTd)@q-D zfX$0kd*gV=)KA3Hp3K`9*Vbt(N81{GOt5R#@Hg?FytiuerP}{n%72~b92c|xQ0CEI zcpApn96xb<#`F_vuN9rHF&`0c5dDS;To`}Q3Yfj~w*3stwq}~E((^xk{75#<9j%o$ z1|19Y(dz?c17q8-!kmLNJCFWd*-XSkF3<*>>v9|i#8LiR)%_Ev_>TXSoPa#KjPY;S z?$ww#xhZ==lUygyW2A$19n_oOH}X~S6MI6qXh8msa;_q-h{Zm&rkR@SOMfjPLMWe&gDvno5IdFj>NQV$27Ay>(OR z`2%;ZR&w9+z1YfRstbsH&i1k%KGmG_T#oeD#tWdYdAqlEPTS1D)sto0o5z2@0he3W z@F5=Ky4A&=C|hHf|G-C&fzQ}(jMYA8cn28`*{;fxfIL_2*T@USk9JiQpUAiV&6-K) zx}BtjQ(viHd4yE|-dpTSO_p-SXUex&n{Sf z4!qevAKtSD{C|D^uVg%U&v|*sj^%2OHW6zDOp7w`-DSHbpUG$P-L*}1w9UHGvr$WF zQ>~}etvE^?zncO+EtJCf)=A!6+Yxtr5A3K9V6EI&GGz2KK}MWs%t$}X_(0V688Y}O zAO3agepX}o0e!lMVTynBBc65_PlV-?ocYacKx~C2Y%M{3Qk9KydpNUWV zx5^9EM`+^*(s=dc!HVXjz0Lp+LL%uIITZ-ggE4g#*lx$h|D_)Yv z=Da1}&28rs@|<@09v)iT-o5*td{ZJ+3Ka^H>eVA<$&z2--|U5^|NdK-=j1!vH~HPVYA>l;W(?Lk7b)4CE$corUZefK zM;^ykUucWZeOKt$hb4U2ZT_WvIPTwnuG&1~x_f(HQG0l$Pp`=t*3*2swn@n^7pXF_ zY%Jpll$B*x@RqbA%{kuS-gYwvwNd@SMU} zN1esC`D4|X1pY&+Z;$D_*zRjQ*XqnrjI+AbZ7=mJjZp17f9@@ap~|)$)vjaX$-nWe zY189~@ALri-JUC-uAhH?Dl1psLfqY>%7@3&$5ZQbwDPcQEaP`Y=SjVat|+^U48?ep z^x_zTw26&&F+HP9;GC0l2gcPVuP6hy%<(dgS7U6OwEG+ih*1Yb8MAe+x7z=X`M=$G zhWKgy7suBfYj={G^8=41NK z@;>`AeN0N4d|haznzwYW(*bkPTBrwFA7M$?H(l$z(s>ud`{a2;*Z>y8@#h%ciIKN4 zy|2r6&b@m#YAy{b50g?~E<|15rTT7`_kE7jENT4lTu0Rf%7qd|mVkD{6%9wWt@_q7 zAtpWmN~#{+ow z836nG*uno_?dwHfpxf5Ip`Mp^u-xNXx7pIy18!Z=?3BWN!)3~t5bS~FFL`of{RQ

#?mp+)WLL`RZtx+` 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../ui.h" + +struct BlockingProcess { + RM_APP_TYPE type{}; + std::wstring name{}; + std::wstring exe_path{}; + int pid{}; +}; + +bool blocking_processes(std::vector& result, std::wstring& error, const std::wstring& file) { + DWORD dwSession{0}; + WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 }; + DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey); + if(dwError != ERROR_SUCCESS) { + error = L"Failed to start rm session (" + std::to_wstring(dwError) + L")"; + goto error_exit; + } + + PCWSTR pszFile = file.data(); + dwError = RmRegisterResources(dwSession, 1, &pszFile, 0, nullptr, 0, nullptr); + if(dwError != ERROR_SUCCESS) { + error = L"Failed to register resource (" + std::to_wstring(dwError) + L")"; + goto error_exit; + } + + DWORD dwReason; + UINT i; + UINT nProcInfoNeeded; + UINT nProcInfo = 10; + RM_PROCESS_INFO rgpi[10]; + dwError = RmGetList(dwSession, &nProcInfoNeeded, &nProcInfo, rgpi, &dwReason); + if(dwError != ERROR_SUCCESS) { + error = L"Failed to get list from rm (" + std::to_wstring(dwError) + L")"; + goto error_exit; + } + + result.reserve(nProcInfo); + for (i = 0; i < nProcInfo; i++) { + auto& info = result.emplace_back(); + + info.type = rgpi[i].ApplicationType; + info.name = rgpi[i].strAppName; + info.pid = rgpi[i].Process.dwProcessId; + info.exe_path = L"unknown"; + + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rgpi[i].Process.dwProcessId); + if (hProcess) { + FILETIME ftCreate, ftExit, ftKernel, ftUser; + if (GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser) && CompareFileTime(&rgpi[i].Process.ProcessStartTime, &ftCreate) == 0) { + WCHAR sz[MAX_PATH]; + DWORD cch{MAX_PATH}; + if (QueryFullProcessImageNameW(hProcess, 0, sz, &cch) && cch <= MAX_PATH) { + info.exe_path = sz; + } + } + CloseHandle(hProcess); + } + } + + RmEndSession(dwSession); + return true; + + error_exit: + if(dwSession) + RmEndSession(dwSession); + return false; +} + +#if 0 +enum LabelDefault { LABEL_MAX }; +enum BrushDefault { BRUSH_MAX }; +template +class Win32Window { + public: + template + using WithLabels = Win32Window; + + template + using WithBrush = Win32Window; + + Win32Window(); + + protected: + std::array hLabel{}; + std::array hBrush{}; +}; + +static void a() { + auto window = new Win32Window(); +} +#endif + +class FileInUseWindow { + public: + static constexpr auto ClassName = "FileInUseWindow"; + static bool register_class(); + + explicit FileInUseWindow(HWND); + virtual ~FileInUseWindow(); + + bool initialize(); + void finalize(); + + void set_file(const std::wstring&); + void update_blocking_info(); + + ui::FileBlockedResult result{ui::FileBlockedResult::UNSET}; + bool deleteOnClose{true}; + private: + static constexpr auto kBackgroundColor = RGB(240, 240, 240); + + static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + INT_PTR window_proc_color_static(HWND hElement, HDC hDC); + INT_PTR window_proc_command(HWND hwnd, DWORD cID); + void set_blocking_info(const std::wstring_view&, const std::vector&); + + enum Label { + LABEL_FIRST_LINE, + LABEL_FILE_NAME, + LABEL_SECOND_LINE, + LABEL_MAX + }; + + enum Brush { + BRUSH_BACKGROUND, + BRUSH_MAX + }; + + enum Font { + FONT_TEXT, + FONT_FILE_NAME, + FONT_PROCESS_INFO, + FONT_MAX + }; + + enum Tooltip { + TOOLTIP_FILE, + TOOLTIP_MAX + }; + + enum Button { + BUTTON_CANCEL, + BUTTON_CONTINUE, + BUTTON_REFRESH, + BUTTON_MAX + }; + + HWND hWindow; + + std::array hLabels{}; + std::array hTooltips{}; + std::array hButton{}; + HWND hListBox{}; + + std::array hBrush{}; + std::array hFont{}; + + std::wstring blocking_file{}; + bool window_active{false}; + + bool update_exit{false}; + bool force_update{false}; + std::thread update_thread{}; + std::condition_variable update_cv{}; + std::mutex update_mutex{}; +}; + +bool FileInUseWindow::register_class() { + WNDCLASS wc = {}; + wc.lpfnWndProc = FileInUseWindow::window_proc; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = "FileInUseWindow"; + return RegisterClass(&wc); +} + +LRESULT FileInUseWindow::window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + auto window = (FileInUseWindow*) GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch(uMsg) { + case WM_CREATE: + window = new FileInUseWindow(hwnd); + if(!window->initialize()) + assert(false); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) window); + /* Certain window data is cached, so changes you make using SetWindowLongPtr will not take effect until you call the SetWindowPos function */ + SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + return 0; + case WM_DESTROY: + window->finalize(); + if(window->deleteOnClose) + delete window; + PostQuitMessage(0); + return 0; + case WM_CLOSE: + if(window->result == ui::FileBlockedResult::UNSET) { + if (MessageBoxW(hwnd, L"Do you really want to cancel the update?", L"Are you sure?", MB_OKCANCEL) == IDOK) { + window->result = ui::FileBlockedResult::CANCELED; + DestroyWindow(hwnd); + } + } else { + DestroyWindow(hwnd); + } + return 0; + case WM_CTLCOLORSTATIC: + return window->window_proc_color_static((HWND) lParam, (HDC) wParam); + case WM_COMMAND: + return window->window_proc_command((HWND) lParam, LOWORD(wParam)); + case WM_ACTIVATE: + if(!window) return 0; + if(wParam == WA_INACTIVE) { + window->window_active = false; + } else if(wParam == WA_ACTIVE) { + window->window_active = true; + window->force_update = true; + window->update_cv.notify_all(); /* update the list */ + } + return 0; + default: + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } +} + +HWND CreateToolTip(HWND window, HWND hwnd, PTSTR pszText) { + HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, + WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hwnd, NULL, + (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_WNDPROC), NULL); + + if(!hwndTip) return nullptr; + + // Associate the tooltip with the tool. + TOOLINFO toolInfo = { 0 }; + toolInfo.cbSize = sizeof(toolInfo); + toolInfo.uId = 22; + toolInfo.hwnd = hwnd; + toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + toolInfo.lpszText = pszText; + SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + SendMessage(hwndTip, TTM_ACTIVATE, TRUE, 0); + + return hwndTip; +} + +HWND CreateAHorizontalScrollBar(HWND hwndParent, int sbHeight) +{ + RECT rect; + + // Get the dimensions of the parent window's client area; + if (!GetClientRect(hwndParent, &rect)) + return NULL; + + // Create the scroll bar. + return (CreateWindowExW( + 0, // no extended styles + L"SCROLLBAR", // scroll bar control class + nullptr, // no window text + WS_CHILD | WS_VISIBLE // window styles + | SBS_HORZ, // horizontal scroll bar style + rect.left, // horizontal position + rect.bottom - sbHeight - 12, // vertical position + rect.right, // width of the scroll bar + sbHeight, // height of the scroll bar + hwndParent, // handle to main window + (HMENU) NULL, // no menu + (HINSTANCE) GetWindowLongPtr(hwndParent, GWLP_WNDPROC), // instance owning this window + (PVOID) NULL // pointer not needed + )); +} + +FileInUseWindow::FileInUseWindow(HWND hwnd) : hWindow{hwnd} {} +FileInUseWindow::~FileInUseWindow() = default; + +bool FileInUseWindow::initialize() { + ::SetWindowLong(this->hWindow, GWL_STYLE, GetWindowLong(this->hWindow, GWL_STYLE) & ~ (WS_SIZEBOX | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)); + + this->hBrush[Brush::BRUSH_BACKGROUND] = CreateSolidBrush(kBackgroundColor); + this->hFont[Font::FONT_TEXT] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial"); + this->hFont[Font::FONT_FILE_NAME] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas"); + this->hFont[Font::FONT_PROCESS_INFO] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas"); + + SetClassLongPtr(this->hWindow, GCLP_HBRBACKGROUND, (LONG_PTR) this->hBrush[Brush::BRUSH_BACKGROUND]); + + { + this->hLabels[Label::LABEL_FIRST_LINE] = CreateWindow(WC_STATIC, "Failed to unlock the following file:", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 10, 10, 400, 20, + this->hWindow, nullptr, + (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); + + this->hLabels[Label::LABEL_FILE_NAME] = CreateWindow(WC_STATIC, "", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 10, 30, 400, 20, + this->hWindow, nullptr, + (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); + this->hTooltips[Tooltip::TOOLTIP_FILE] = CreateToolTip(this->hWindow, this->hLabels[Label::LABEL_FILE_NAME], "Hello World"); //FIXME: Tooltip not working... + + this->hLabels[Label::LABEL_SECOND_LINE] = CreateWindow(WC_STATIC, "Please close these processes:", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 10, 60, 400, 20, + this->hWindow, nullptr, + (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); + + for(auto& hLabel : this->hLabels) + SendMessage(hLabel, WM_SETFONT, (WPARAM) this->hFont[Font::FONT_TEXT], TRUE); + SendMessage(this->hLabels[Label::LABEL_FILE_NAME], WM_SETFONT, (WPARAM) this->hFont[Font::FONT_FILE_NAME], TRUE); + } + + { + + this->hListBox = CreateWindow("ListBox", nullptr, WS_VISIBLE | WS_CHILD | LBS_STANDARD | LBS_NOTIFY | WS_HSCROLL , 10, 80, 565, 200, this->hWindow, nullptr, + (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); + SendMessage(this->hListBox, WM_SETFONT, (WPARAM) this->hFont[Font::FONT_PROCESS_INFO], TRUE); + } + + { +#if 0 + this->hButton[Button::BUTTON_CANCEL] = CreateWindowW( + WC_BUTTONW, // Predefined class; Unicode assumed + L"Cancel", // Button text + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles + 10, // x position + 280, // y position + 100, // Button width + 30, // Button height + this->hWindow, // Parent window + nullptr, // No menu. + (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), + nullptr); // Pointer not needed. +#endif + + this->hButton[Button::BUTTON_REFRESH] = CreateWindowW( + WC_BUTTONW, // Predefined class; Unicode assumed + L"Refresh", // Button text + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles + 10, // x position + 280, // y position + 100, // Button width + 30, // Button height + this->hWindow, // Parent window + nullptr, // No menu. + (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), + nullptr); // Pointer not needed. + + this->hButton[Button::BUTTON_CONTINUE] = CreateWindowW( + WC_BUTTONW, // Predefined class; Unicode assumed + L"Continue", // Button text + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles + 475, // x position + 280, // y position + 100, // Button width + 30, // Button height + this->hWindow, // Parent window + nullptr, // No menu. + (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), + nullptr); // Pointer not needed. + } + + ShowWindow(this->hWindow, SW_SHOWNORMAL); + this->window_active = true; + + this->update_thread = std::thread([&]{ + while(!this->update_exit) { + { + std::unique_lock update_lock{this->update_mutex}; + this->update_cv.wait_for(update_lock, std::chrono::milliseconds{1000}); + if(this->update_exit) return; + } + if(this->window_active && !this->force_update) continue; /* only auto update in the background */ + this->force_update = false; + + this->update_blocking_info(); + } + }); + return true; +} + +INT_PTR FileInUseWindow::window_proc_color_static(HWND hElement, HDC hDC) { + SetBkColor(hDC, kBackgroundColor); + return (INT_PTR) this->hBrush[Brush::BRUSH_BACKGROUND]; +} + +INT_PTR FileInUseWindow::window_proc_command(HWND hwnd, DWORD cID) { + if(hwnd == this->hButton[Button::BUTTON_CANCEL]) { + SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); + } else if(hwnd == this->hButton[Button::BUTTON_REFRESH]) { + this->force_update = true; + this->update_cv.notify_all(); + } else if(hwnd == this->hButton[Button::BUTTON_CONTINUE]) { + this->result = ui::FileBlockedResult::PROCESSES_CLOSED; + SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); + } + + return 0; +} + +void FileInUseWindow::finalize() { + this->update_exit = true; + this->update_cv.notify_all(); + if(this->update_thread.joinable()) + this->update_thread.join(); + + //TODO: Free resources? +} + +static inline std::wstring_view app_type_prefix(RM_APP_TYPE type) { + switch (type) { + case RM_APP_TYPE::RmOtherWindow: + return L"Child App : "; + case RM_APP_TYPE::RmMainWindow: + return L"Application: "; + case RM_APP_TYPE::RmConsole: + return L"Console App: "; + case RM_APP_TYPE::RmService: + return L"NT Service : "; + case RM_APP_TYPE::RmExplorer: + return L"Explorer : "; + case RM_APP_TYPE::RmCritical: + return L"System : "; + default: + return L" "; + } +} + +void FileInUseWindow::set_file(const std::wstring &file) { + this->blocking_file = file; + this->update_blocking_info(); +} + +void FileInUseWindow::update_blocking_info() { + std::wstring error{}; + std::vector result_{}; + if(!blocking_processes(result_, error, this->blocking_file)) { + MessageBoxW(this->hWindow, (L"Failed to get file info:\n" + error).c_str(), L"Failed to query file info", MB_OK | MB_ICONERROR); + SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); + return; + } + + this->set_blocking_info(this->blocking_file, result_); +} + +void FileInUseWindow::set_blocking_info(const std::wstring_view &file, const std::vector &processes) { + SetWindowTextW(this->hLabels[Label::LABEL_FILE_NAME], file.data()); + + std::vector output_processes{}; + for(const auto& type : { + RM_APP_TYPE::RmMainWindow, + RM_APP_TYPE::RmConsole, + RM_APP_TYPE::RmService, + RM_APP_TYPE::RmCritical, + RM_APP_TYPE::RmOtherWindow + }) { + for(const auto& proc : processes) { + if(proc.type != type) continue; + auto it = std::find_if(output_processes.begin(), output_processes.end(), [&](const BlockingProcess& entry) { + return entry.name == proc.name && proc.exe_path == entry.exe_path; + }); + if(it != output_processes.end()) + continue; + output_processes.emplace_back(proc); + } + } + + HDC hDC = GetDC(NULL); + SelectFont(hDC, this->hFont[Font::FONT_PROCESS_INFO]); + + size_t width{0}; + SendMessage(this->hListBox, LB_RESETCONTENT, 0, 0); + for(auto& process : output_processes) { + auto message = L" " + std::wstring{app_type_prefix(process.type)} + process.name + L" " + std::to_wstring(process.pid) + L" (" + process.exe_path + L") "; + SendMessageW(this->hListBox, LB_ADDSTRING, 0, (LPARAM) message.c_str()); + + RECT r = { 0, 0, 0, 0 }; + DrawTextW(hDC, message.data(), message.length(), &r, DT_CALCRECT); + if(r.right - r.left > width) + width = r.right - r.left; + } + + SendMessage(this->hListBox, LB_SETHORIZONTALEXTENT, (WPARAM) width, 0); + ReleaseDC(NULL, hDC); + + EnableWindow(this->hButton[Button::BUTTON_CONTINUE], output_processes.empty()); +} + +inline std::wstring mbtow(const std::string_view& str) { + int length = MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, nullptr, 0); + std::wstring result{}; + result.resize(length + 1); + MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, result.data() , length); + return result; +} + +#define IDR_APP_ICON 101 +ui::FileBlockedResult ui::open_file_blocked(const std::string& file) { + FileInUseWindow::register_class(); + auto hInstance = GetModuleHandle(nullptr); + + auto hWindow = CreateWindowEx( + 0, // Optional window styles. + FileInUseWindow::ClassName, // Window class + "One or more files are still in use", // Window text + WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU, // Window style + + // Size and position + CW_USEDEFAULT, CW_USEDEFAULT, 600, 360, + + nullptr, // Parent window + nullptr, // Menu + hInstance, // Instance handle + nullptr // Additional application data + ); + if (hWindow == nullptr) + return ui::FileBlockedResult::INTERNAL_ERROR; + + auto hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_APP_ICON)); + SendMessage(hWindow, WM_SETICON, ICON_BIG, (LPARAM) hIcon); + SendMessage(hWindow, WM_SETICON, ICON_SMALL, (LPARAM) hIcon); + DestroyIcon(hIcon); + + auto window = (FileInUseWindow*) GetWindowLongPtr(hWindow, GWLP_USERDATA); + window->deleteOnClose = false; + + window->set_file(mbtow(file)); + + MSG msg = { }; + while (GetMessage(&msg, hWindow, 0, 0)) + { + if (msg.message == WM_NULL) + break; + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + auto result = window->result; + delete window; + return result; +} \ No newline at end of file