cocoa_main.mm (3937B)
1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "cocoa_progress_callback.h" 5 #include "updater.h" 6 7 #include "common/file_system.h" 8 #include "common/log.h" 9 #include "common/path.h" 10 #include "common/scoped_guard.h" 11 #include "common/string_util.h" 12 #include "common/timer.h" 13 14 #include <cstdlib> 15 #include <thread> 16 17 static void LaunchApplication(const char* path) 18 { 19 @autoreleasepool 20 { 21 NSTask* task = [[[NSTask alloc] init] autorelease]; 22 [task setLaunchPath:[NSString stringWithUTF8String:path]]; 23 [task launch]; 24 } 25 } 26 27 int main(int argc, char* argv[]) 28 { 29 [NSApplication sharedApplication]; 30 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 31 32 // Needed for keyboard in put. 33 const ProcessSerialNumber psn = {0, kCurrentProcess}; 34 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 35 36 CocoaProgressCallback progress; 37 38 if (argc != 4) 39 { 40 progress.ModalError("Expected 3 arguments: update zip, staging directory, output directory.\n\nThis program is not " 41 "intended to be run manually, please use the Qt frontend and click Help->Check for Updates."); 42 return EXIT_FAILURE; 43 } 44 45 std::string zip_path = argv[1]; 46 std::string staging_directory = argv[2]; 47 std::string destination_directory = argv[3]; 48 49 if (zip_path.empty() || staging_directory.empty() || destination_directory.empty()) 50 { 51 progress.ModalError("One or more parameters is empty."); 52 return EXIT_FAILURE; 53 } 54 55 if (const char* home_dir = getenv("HOME")) 56 { 57 static constexpr char log_file[] = "Library/Application Support/DuckStation/updater.log"; 58 std::string log_path = Path::Combine(home_dir, log_file); 59 Log::SetFileOutputParams(true, log_path.c_str()); 60 } 61 62 std::string program_to_launch = Path::Combine(destination_directory, "Contents/MacOS/DuckStation"); 63 int result = EXIT_SUCCESS; 64 65 std::thread worker([&progress, zip_path = std::move(zip_path), 66 destination_directory = std::move(destination_directory), 67 staging_directory = std::move(staging_directory), &result]() { 68 ScopedGuard app_stopper([]() { dispatch_async(dispatch_get_main_queue(), []() { [NSApp stop:nil]; }); }); 69 70 Updater updater(&progress); 71 if (!updater.Initialize(std::move(staging_directory), std::move(destination_directory))) 72 { 73 progress.ModalError("Failed to initialize updater."); 74 result = EXIT_FAILURE; 75 return; 76 } 77 78 if (!updater.OpenUpdateZip(zip_path.c_str())) 79 { 80 progress.FormatModalError("Could not open update zip '{}'. Update not installed.", zip_path); 81 result = EXIT_FAILURE; 82 return; 83 } 84 85 if (!updater.PrepareStagingDirectory()) 86 { 87 progress.ModalError("Failed to prepare staging directory. Update not installed."); 88 result = EXIT_FAILURE; 89 return; 90 } 91 92 if (!updater.StageUpdate()) 93 { 94 progress.ModalError("Failed to stage update. Update not installed."); 95 result = EXIT_FAILURE; 96 return; 97 } 98 99 if (!updater.ClearDestinationDirectory()) 100 { 101 progress.ModalError("Failed to clear destination directory. Your installation may be corrupted, please " 102 "re-download a fresh version from GitHub."); 103 result = EXIT_FAILURE; 104 return; 105 } 106 107 if (!updater.CommitUpdate()) 108 { 109 progress.ModalError( 110 "Failed to commit update. Your installation may be corrupted, please re-download a fresh version from GitHub."); 111 result = EXIT_FAILURE; 112 return; 113 } 114 115 updater.CleanupStagingDirectory(); 116 updater.RemoveUpdateZip(); 117 118 result = EXIT_SUCCESS; 119 }); 120 121 [NSApp run]; 122 123 worker.join(); 124 125 if (result == EXIT_SUCCESS) 126 { 127 progress.FormatInformation("Launching '{}'...", program_to_launch); 128 LaunchApplication(program_to_launch.c_str()); 129 } 130 131 return result; 132 }