diff --git a/Mythic.xcodeproj/project.pbxproj b/Mythic.xcodeproj/project.pbxproj index 4dda45b6..98c37e6c 100644 --- a/Mythic.xcodeproj/project.pbxproj +++ b/Mythic.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 6A1C6F422AEFD95900F89AE7 /* WineInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1C6F412AEFD95900F89AE7 /* WineInterface.swift */; }; 6A1C6F462AEFD9B100F89AE7 /* WineInterfaceExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1C6F452AEFD9B100F89AE7 /* WineInterfaceExt.swift */; }; 6A1ECF722B0CA0560010DC17 /* EventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1ECF712B0CA0560010DC17 /* EventManager.swift */; }; + 6A218EEF2BDFDB810023D4A2 /* OnboardingR2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A218EEE2BDFDB810023D4A2 /* OnboardingR2.swift */; }; 6A34366E2B8D7F1200D35BCA /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = 6A34366D2B8D7F1200D35BCA /* Shimmer */; }; 6A3578AF2BAAE0F900092D7F /* SubscriptedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3578AE2BAAE0F900092D7F /* SubscriptedTextView.swift */; }; 6A371B562AE7A5E20054BF7A /* Installer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A371B552AE7A5E20054BF7A /* Installer.swift */; }; @@ -103,6 +104,7 @@ 6A1C6F412AEFD95900F89AE7 /* WineInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WineInterface.swift; sourceTree = ""; }; 6A1C6F452AEFD9B100F89AE7 /* WineInterfaceExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WineInterfaceExt.swift; sourceTree = ""; }; 6A1ECF712B0CA0560010DC17 /* EventManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventManager.swift; sourceTree = ""; }; + 6A218EEE2BDFDB810023D4A2 /* OnboardingR2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingR2.swift; sourceTree = ""; }; 6A3578AE2BAAE0F900092D7F /* SubscriptedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptedTextView.swift; sourceTree = ""; }; 6A371B552AE7A5E20054BF7A /* Installer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Installer.swift; sourceTree = ""; }; 6A371B5A2AE7E93B0054BF7A /* Libraries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Libraries.swift; sourceTree = ""; }; @@ -455,6 +457,7 @@ 6AB474A92AACBCD000AB9C63 /* Views */ = { isa = PBXGroup; children = ( + 6A218EEE2BDFDB810023D4A2 /* OnboardingR2.swift */, 6A3578AD2BAAE0D300092D7F /* Extensions */, 6ADF5AB52B9877AF0041D04E /* GameListEvo */, 6AF0EB2B2AADE2530044C09C /* WebView.swift */, @@ -713,6 +716,7 @@ 6AE4A9A32B44682E0060D483 /* Lock.swift in Sources */, 6A1ECF722B0CA0560010DC17 /* EventManager.swift in Sources */, 6AA6CCE32AD52B2600F664A5 /* LegendaryInterface.swift in Sources */, + 6A218EEF2BDFDB810023D4A2 /* OnboardingR2.swift in Sources */, 6AC3E1892B374D2E000EB765 /* VariableManager.swift in Sources */, 6A008E9B2BC0AF350039AEF3 /* DownloadsEvo.swift in Sources */, 6A00F20B2AC5F8730054858A /* Auth.swift in Sources */, @@ -865,7 +869,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2599; + CURRENT_PROJECT_VERSION = 2607; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Mythic/Preview Content\""; DEVELOPMENT_TEAM = 67ZBY275P8; @@ -883,7 +887,7 @@ "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = ""; - MARKETING_VERSION = 0.1.0; + MARKETING_VERSION = 0.0.0; PRODUCT_BUNDLE_IDENTIFIER = xyz.blackxfiied.Mythic; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -904,7 +908,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2599; + CURRENT_PROJECT_VERSION = 2607; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Mythic/Preview Content\""; DEVELOPMENT_TEAM = 67ZBY275P8; @@ -922,7 +926,7 @@ "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = ""; - MARKETING_VERSION = 0.1.0; + MARKETING_VERSION = 0.0.0; PRODUCT_BUNDLE_IDENTIFIER = xyz.blackxfiied.Mythic; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Mythic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mythic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e6eac9a6..c1a86445 100644 --- a/Mythic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mythic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "90d4e1ca661bb2c2485f9e9402d06ff3e38745c75cd64fec431f020402f4048b", "pins" : [ { "identity" : "bluesocket", @@ -14,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Lakr233/ColorfulX", "state" : { - "revision" : "24dea76cbd7dbcc1a31da6f2213264d449e7687b", - "version" : "2.4.4" + "revision" : "24bbc2f4b27c853f2be5ebed144252d9bcd27ee0", + "version" : "2.4.5" } }, { @@ -41,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/sparkle-project/Sparkle", "state" : { - "revision" : "47d3d90aee3c52b6f61d04ceae426e607df62347", - "version" : "2.5.2" + "revision" : "0a4caaf7a81eea2cece651ef4b17331fa0634dff", + "version" : "2.6.0" } }, { @@ -77,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SwiftyJSON/SwiftyJSON", "state" : { - "revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", - "version" : "5.0.1" + "revision" : "af76cf3ef710b6ca5f8c05f3a31307d44a3c5828", + "version" : "5.0.2" } }, { @@ -95,10 +96,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/weichsel/ZIPFoundation", "state" : { - "revision" : "b979e8b52c7ae7f3f39fa0182e738e9e7257eb78", - "version" : "0.9.18" + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" } } ], - "version" : 2 + "version" : 3 } diff --git a/Mythic/Controllers/Legendary/LegendaryInterface.swift b/Mythic/Controllers/Legendary/LegendaryInterface.swift index 8aecea53..fc157243 100644 --- a/Mythic/Controllers/Legendary/LegendaryInterface.swift +++ b/Mythic/Controllers/Legendary/LegendaryInterface.swift @@ -44,7 +44,7 @@ class Legendary { private static let _runningCommandsQueue = DispatchQueue(label: "legendaryRunningCommands", attributes: .concurrent) /// Dictionary to monitor running commands and their identifiers. - private static var runningCommands: [String: Process] { + static var runningCommands: [String: Process] { get { _runningCommandsQueue.sync { return _runningCommands @@ -76,7 +76,7 @@ class Legendary { It handles the process's standard input, standard output, and standard error, as well as any interactions based on the output provided by the `input` closure. */ @available(*, message: "Revamped recently") - static func command(arguments args: [String], identifier: String, waits: Bool = true, input: ((String) -> String?)? = nil, environment: [String: String]? = nil, completion: @escaping (CommandOutput, Process) -> Void) async throws { + static func command(arguments args: [String], identifier: String, waits: Bool = true, input: ((String) -> String?)? = nil, environment: [String: String]? = nil, completion: @escaping (CommandOutput) -> Void) async throws { let task = Process() task.executableURL = URL(filePath: Bundle.main.path(forResource: "legendary/cli", ofType: nil)!) @@ -105,7 +105,7 @@ class Legendary { stdin.fileHandleForWriting.write(data) } output.stderr = availableOutput - completion(output, task) // ⚠️ FIXME: critical performance issues + completion(output) // ⚠️ FIXME: critical performance issues } stdout.fileHandleForReading.readabilityHandler = { [stdin, output] handle in @@ -115,12 +115,11 @@ class Legendary { stdin.fileHandleForWriting.write(data) } output.stdout = availableOutput - completion(output, task) // ⚠️ FIXME: critical performance issues + completion(output) // ⚠️ FIXME: critical performance issues } - task.terminationHandler = { [stdin] _ in + task.terminationHandler = { _ in runningCommands.removeValue(forKey: identifier) - try? stdin.fileHandleForWriting.close() } log.debug("[command] executing command [\(identifier)]: `\(terminalFormat)`") @@ -236,7 +235,7 @@ class Legendary { let diskSpeedRegex: Regex = try! .init(#"\+ Disk\s+- (?[\d.]+) \w+/\w+ \(write\) / (?[\d.]+) \w+/\w+ \(read\)"#) // swiftlint:enable force_try - try await command(arguments: argBuilder, identifier: "install") { output, _ in + try await command(arguments: argBuilder, identifier: "install") { output in // MARK: stderr (installation) handling // TODO: ONLY APPEND RAW DATA HERE, MAY SAVE ON PERFORMANCE @@ -298,7 +297,7 @@ class Legendary { try await command( arguments: ["move", game.id, newPath, "--skip-move"], identifier: "move" - ) { _, _ in } + ) { _ in } try await notifications.add( .init(identifier: UUID().uuidString, @@ -324,7 +323,7 @@ class Legendary { arguments: ["auth", "--code", authKey], identifier: "signin", waits: true - ) { output, _ in + ) { output in isLoggedIn = (isLoggedIn == true ? true : output.stderr.contains("Successfully logged in as")) } @@ -373,7 +372,7 @@ class Legendary { environmentVariables["WINEMSYNC"] = bottle.settings.msync ? "1" : "0" } - try await command(arguments: arguments, identifier: "launch\(game.id)") { _, _ in } + try await command(arguments: arguments, identifier: "launch\(game.id)") { _ in } DispatchQueue.main.async { GameOperation.shared.launching = nil @@ -595,11 +594,11 @@ class Legendary { if let metadataContents = try? files.contentsOfDirectory(atPath: metadata), !metadataContents.isEmpty { Task(priority: .background) { - try? await command(arguments: ["status"], identifier: "refreshMetadata") { _, _ in } + try? await command(arguments: ["status"], identifier: "refreshMetadata") { _ in } } } else { Task.sync(priority: .high) { // called during onboarding for speed - try? await command(arguments: ["status"], identifier: "refreshMetadata") { _, _ in } + try? await command(arguments: ["status"], identifier: "refreshMetadata") { _ in } } } diff --git a/Mythic/Controllers/LocalGames/LocalGames.swift b/Mythic/Controllers/LocalGames/LocalGames.swift index 882a78d6..ed16bd8f 100644 --- a/Mythic/Controllers/LocalGames/LocalGames.swift +++ b/Mythic/Controllers/LocalGames/LocalGames.swift @@ -92,7 +92,7 @@ class LocalGames { "MTL_HUD_ENABLED": bottle.settings.metalHUD ? "1" : "0", "WINEMSYNC": bottle.settings.msync ? "1" : "0" ] - ) { _, _/* task */ in } // TODO: pass task over through launch + ) { _ in } // TODO: pass task over through launch case .none: do { /* TODO: Error */ } } diff --git a/Mythic/Controllers/Wine/WineInterface.swift b/Mythic/Controllers/Wine/WineInterface.swift index 1b3ab3c5..354cf9e4 100644 --- a/Mythic/Controllers/Wine/WineInterface.swift +++ b/Mythic/Controllers/Wine/WineInterface.swift @@ -28,7 +28,7 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 private static let _runningCommandsQueue = DispatchQueue(label: "legendaryRunningCommands", attributes: .concurrent) /// Dictionary to monitor running commands and their identifiers. - private static var runningCommands: [String: Process] { + static var runningCommands: [String: Process] { get { var result: [String: Process]? _runningCommandsQueue.sync { @@ -152,7 +152,7 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 It handles the process's standard input, standard output, and standard error, as well as any interactions based on the output provided by the `input` closure. */ @available(*, message: "Revamped recently") - static func command(arguments args: [String], identifier: String, waits: Bool = true, bottleURL: URL?, input: ((String) -> String?)? = nil, environment: [String: String]? = nil, completion: @escaping (Legendary.CommandOutput, Process) -> Void) async throws { + static func command(arguments args: [String], identifier: String, waits: Bool = true, bottleURL: URL?, input: ((String) -> String?)? = nil, environment: [String: String]? = nil, completion: @escaping (Legendary.CommandOutput) -> Void) async throws { let task = Process() task.executableURL = Libraries.directory.appending(path: "Wine/bin/wine64") @@ -189,7 +189,7 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 stdin.fileHandleForWriting.write(data) } output.stderr = availableOutput - completion(output, task) // ⚠️ FIXME: critical performance issues + completion(output) } stdout.fileHandleForReading.readabilityHandler = { [stdin, output] handle in @@ -199,12 +199,11 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 stdin.fileHandleForWriting.write(data) } output.stdout = availableOutput - completion(output, task) // ⚠️ FIXME: critical performance issues + completion(output) } - task.terminationHandler = { [stdin] _ in + task.terminationHandler = { _ in runningCommands.removeValue(forKey: identifier) - try? stdin.fileHandleForWriting.close() } log.debug("[command] executing command [\(identifier)]: `\(terminalFormat)`") @@ -275,7 +274,7 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 do { let newBottle: Bottle = .init(url: bottleURL, settings: settings, busy: false) - try await command(arguments: ["wineboot"], identifier: "wineboot", bottleURL: bottleURL) { output, _ in + try await command(arguments: ["wineboot"], identifier: "wineboot", bottleURL: bottleURL) { output in // swiftlint:disable:next force_try if output.stderr.contains(try! Regex(#"wine: configuration in (.*?) has been updated\."#)) { allBottles?[name] = newBottle @@ -368,7 +367,7 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 private static func addRegistryKey(bottleURL: URL, key: String, name: String, data: String, type: RegistryType) async throws { guard bottleExists(bottleURL: bottleURL) else { throw BottleDoesNotExistError() } - try await command(arguments: ["reg", "add", key, "-v", name, "-t", type.rawValue, "-d", data, "-f"], identifier: "regadd", bottleURL: bottleURL) { _, _ in + try await command(arguments: ["reg", "add", key, "-v", name, "-t", type.rawValue, "-d", data, "-f"], identifier: "regadd", bottleURL: bottleURL) { _ in // FIXME: errors aren't handled } } @@ -376,15 +375,14 @@ class Wine { // TODO: https://forum.winehq.org/viewtopic.php?t=15416 // MARK: - Query Registry Key Method private static func queryRegistryKey(bottleURL: URL, key: String, name: String, type: RegistryType, completion: @escaping (Result) -> Void) async { do { - - try await command(arguments: ["reg", "query", key, "-v", name], identifier: "regquery", bottleURL: bottleURL) { output, task in + try await command(arguments: ["reg", "query", key, "-v", name], identifier: "regquery", bottleURL: bottleURL) { output in if output.stdout.contains(type.rawValue) { let array = output.stdout.split(omittingEmptySubsequences: true, whereSeparator: \.isWhitespace) if !array.isEmpty { completion(.success(String(array.last!))) } else { completion(.failure(UnableToQueryRegistyError())) - task.suspend(); return + runningCommands["regquery"]?.terminate(); return } } // FIXME: outside errors aren't handled diff --git a/Mythic/Deprecated/Views/Auth.swift b/Mythic/Deprecated/Views/Auth.swift index f9f51f6a..8474c9be 100644 --- a/Mythic/Deprecated/Views/Auth.swift +++ b/Mythic/Deprecated/Views/Auth.swift @@ -49,7 +49,7 @@ struct AuthView: View { } do { - try await Legendary.command(arguments: ["auth", "--code", code], identifier: "signin") { output, _ in + try await Legendary.command(arguments: ["auth", "--code", code], identifier: "signin") { output in if output.stderr.contains("ERROR: Login attempt failed") { displayError(); return } diff --git a/Mythic/Views/BottleList.swift b/Mythic/Views/BottleList.swift index 4b12b022..80255872 100644 --- a/Mythic/Views/BottleList.swift +++ b/Mythic/Views/BottleList.swift @@ -96,7 +96,7 @@ struct BottleListView: View { Button("Launch Configurator") { Task { - try await Wine.command(arguments: ["winecfg"], identifier: "winecfg", bottleURL: bottles[selectedBottleName]!.url) {_, _ in } + try await Wine.command(arguments: ["winecfg"], identifier: "winecfg", bottleURL: bottles[selectedBottleName]!.url) { _ in } } } .disabled(configuratorActive) diff --git a/Mythic/Views/GameListEvo/InstallGameView.swift b/Mythic/Views/GameListEvo/InstallGameView.swift index f76ce41c..0bb439b4 100644 --- a/Mythic/Views/GameListEvo/InstallGameView.swift +++ b/Mythic/Views/GameListEvo/InstallGameView.swift @@ -31,9 +31,9 @@ struct InstallViewEvo: View { .task(priority: .userInitiated) { fetchingOptionalPacks = true - try? await Legendary.command(arguments: ["install", game.id], identifier: "parseOptionalPacks") { output, task in + try? await Legendary.command(arguments: ["install", game.id], identifier: "parseOptionalPacks") { output in if output.stdout.contains("Do you wish to install") || output.stdout.contains("Additional packs") { - task.terminate() // kill is preferred + Legendary.runningCommands["parseOptionalPacks"]?.terminate(); return } if output.stdout.contains("The following optional packs are available") { // hate hardcoding diff --git a/Mythic/Views/GameListEvo/UninstallGameView.swift b/Mythic/Views/GameListEvo/UninstallGameView.swift index 55e83f1f..9790c69d 100644 --- a/Mythic/Views/GameListEvo/UninstallGameView.swift +++ b/Mythic/Views/GameListEvo/UninstallGameView.swift @@ -83,7 +83,7 @@ struct UninstallViewEvo: View { keepFiles ? "--keep-files" : nil, skipUninstaller ? "--skip-uninstaller" : nil, game.id - ] .compactMap { $0 }, identifier: "uninstall") { output, _ in + ] .compactMap { $0 }, identifier: "uninstall") { output in guard output.stderr.contains("ERROR:") else { return } let errorLine = output.stderr.trimmingPrefix(try! Regex(#"\[(.*?)\]"#)).trimmingPrefix("ERROR: ") // swiftlint:disable:previous force_try diff --git a/Mythic/Views/Navigation/Accounts.swift b/Mythic/Views/Navigation/Accounts.swift index 1d548238..13d414c7 100644 --- a/Mythic/Views/Navigation/Accounts.swift +++ b/Mythic/Views/Navigation/Accounts.swift @@ -64,7 +64,7 @@ struct AccountsView: View { message: .init("This will sign you out of the account \"\(Legendary.whoAmI())\"."), primaryButton: .destructive(.init("Sign Out")) { Task.sync(priority: .high) { - try? await Legendary.command(arguments: ["auth", "--delete"], identifier: "signout") { _, _ in } + try? await Legendary.command(arguments: ["auth", "--delete"], identifier: "signout") { _ in } } signedIn = Legendary.signedIn() diff --git a/Mythic/Views/Navigation/Library/Extensions/EpicImport.swift b/Mythic/Views/Navigation/Library/Extensions/EpicImport.swift index 35987fcb..92e3327a 100644 --- a/Mythic/Views/Navigation/Library/Extensions/EpicImport.swift +++ b/Mythic/Views/Navigation/Library/Extensions/EpicImport.swift @@ -159,7 +159,7 @@ extension LibraryView.GameImportView { "--platform", platform == .macOS ? "Mac" : platform == .windows ? "Windows" : "Mac", game.id, path - ] .compactMap { $0 }, identifier: "epicImport") { output, _ in + ] .compactMap { $0 }, identifier: "epicImport") { output in if output.stderr.contains("INFO: Game \"\(game.title)\" has been imported.") { isPresented = false isGameListRefreshCalled = true diff --git a/Mythic/Views/Navigation/Settings.swift b/Mythic/Views/Navigation/Settings.swift index 1e017bbb..19125c20 100644 --- a/Mythic/Views/Navigation/Settings.swift +++ b/Mythic/Views/Navigation/Settings.swift @@ -175,7 +175,7 @@ struct SettingsView: View { HStack { Button { Task { - try? await Legendary.command(arguments: ["cleanup"], identifier: "cleanup") { output, _ in + try? await Legendary.command(arguments: ["cleanup"], identifier: "cleanup") { output in isCleanupSuccessful = output.stderr.contains("Cleanup complete") // [cli] INFO: Cleanup complete! Removed 0.00 MiB. } }