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 0000000..04be6c1 Binary files /dev/null and b/native/updater/win32/logo.ico differ diff --git a/native/updater/win32/retry_ui.cpp b/native/updater/win32/retry_ui.cpp new file mode 100644 index 0000000..f6c5e86 --- /dev/null +++ b/native/updater/win32/retry_ui.cpp @@ -0,0 +1,555 @@ +// +// Created by WolverinDEV on 21/04/2020. +// + +#include +#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