From 39f77c01a9c1889d6f26a5d24ade9ea53684a3f3 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:22:47 -0400 Subject: [PATCH] Create `typealias AppID = UInt64`. Use `AppID` everywhere appropriate. Associated appID cleanup. Partial #478 Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Sources/mas/AppStore/Downloader.swift | 4 ++-- Sources/mas/AppStore/SSPurchase.swift | 2 +- Sources/mas/Commands/Home.swift | 2 +- Sources/mas/Commands/Info.swift | 2 +- Sources/mas/Commands/Install.swift | 2 +- Sources/mas/Commands/Lucky.swift | 6 +++--- Sources/mas/Commands/Open.swift | 11 ++--------- Sources/mas/Commands/Outdated.swift | 2 +- Sources/mas/Commands/Purchase.swift | 2 +- Sources/mas/Commands/Uninstall.swift | 4 +--- Sources/mas/Commands/Upgrade.swift | 8 ++++---- Sources/mas/Commands/Vendor.swift | 2 +- Sources/mas/Controllers/AppLibrary.swift | 4 ++-- Sources/mas/Controllers/MasStoreSearch.swift | 4 ++-- Sources/mas/Controllers/StoreSearch.swift | 4 ++-- Sources/mas/Formatters/SearchResultFormatter.swift | 4 ++-- Sources/mas/Mas.swift | 2 ++ Sources/mas/Models/SearchResult.swift | 4 ++-- Tests/masTests/Commands/HomeSpec.swift | 2 +- Tests/masTests/Commands/InfoSpec.swift | 2 +- Tests/masTests/Commands/OpenSpec.swift | 4 ++-- Tests/masTests/Commands/UninstallSpec.swift | 2 +- Tests/masTests/Commands/VendorSpec.swift | 2 +- Tests/masTests/Controllers/MasStoreSearchSpec.swift | 2 +- Tests/masTests/Controllers/StoreSearchMock.swift | 9 ++------- 25 files changed, 40 insertions(+), 52 deletions(-) diff --git a/Sources/mas/AppStore/Downloader.swift b/Sources/mas/AppStore/Downloader.swift index b832335..3dd44a5 100644 --- a/Sources/mas/AppStore/Downloader.swift +++ b/Sources/mas/AppStore/Downloader.swift @@ -17,7 +17,7 @@ import StoreFoundation /// Only works for free apps. Defaults to false. /// - Returns: A promise that completes when the downloads are complete. If any fail, /// the promise is rejected with the first error, after all remaining downloads are attempted. -func downloadAll(_ appIDs: [UInt64], purchase: Bool = false) -> Promise { +func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise { var firstError: Error? return appIDs.reduce(Guarantee.value(())) { previous, appID in previous.then { @@ -34,7 +34,7 @@ func downloadAll(_ appIDs: [UInt64], purchase: Bool = false) -> Promise { } } -private func downloadWithRetries(_ appID: UInt64, purchase: Bool = false, attempts: Int = 3) -> Promise { +private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempts: Int = 3) -> Promise { SSPurchase().perform(adamId: appID, purchase: purchase) .recover { error -> Promise in guard attempts > 1 else { diff --git a/Sources/mas/AppStore/SSPurchase.swift b/Sources/mas/AppStore/SSPurchase.swift index 3fd5703..62f2651 100644 --- a/Sources/mas/AppStore/SSPurchase.swift +++ b/Sources/mas/AppStore/SSPurchase.swift @@ -11,7 +11,7 @@ import PromiseKit import StoreFoundation extension SSPurchase { - func perform(adamId: UInt64, purchase: Bool) -> Promise { + func perform(adamId: AppID, purchase: Bool) -> Promise { var parameters: [String: Any] = [ "productType": "C", "price": 0, diff --git a/Sources/mas/Commands/Home.swift b/Sources/mas/Commands/Home.swift index 37162af..f538f2e 100644 --- a/Sources/mas/Commands/Home.swift +++ b/Sources/mas/Commands/Home.swift @@ -17,7 +17,7 @@ extension Mas { ) @Argument(help: "ID of app to show on MAS Preview") - var appId: Int + var appId: AppID /// Runs the command. func run() throws { diff --git a/Sources/mas/Commands/Info.swift b/Sources/mas/Commands/Info.swift index cf0b686..a330f42 100644 --- a/Sources/mas/Commands/Info.swift +++ b/Sources/mas/Commands/Info.swift @@ -18,7 +18,7 @@ extension Mas { ) @Argument(help: "ID of app to show info") - var appId: Int + var appId: AppID /// Runs the command. func run() throws { diff --git a/Sources/mas/Commands/Install.swift b/Sources/mas/Commands/Install.swift index a7d55f7..ff0b0e8 100644 --- a/Sources/mas/Commands/Install.swift +++ b/Sources/mas/Commands/Install.swift @@ -19,7 +19,7 @@ extension Mas { @Flag(help: "force reinstall") var force = false @Argument(help: "app ID(s) to install") - var appIds: [UInt64] + var appIds: [AppID] /// Runs the command. func run() throws { diff --git a/Sources/mas/Commands/Lucky.swift b/Sources/mas/Commands/Lucky.swift index de16507..bad0511 100644 --- a/Sources/mas/Commands/Lucky.swift +++ b/Sources/mas/Commands/Lucky.swift @@ -28,7 +28,7 @@ extension Mas { } func run(appLibrary: AppLibrary, storeSearch: StoreSearch) throws { - var appId: Int? + var appId: AppID? do { let results = try storeSearch.search(for: appName).wait() @@ -44,7 +44,7 @@ extension Mas { guard let identifier = appId else { fatalError() } - try install(UInt64(identifier), appLibrary: appLibrary) + try install(identifier, appLibrary: appLibrary) } /// Installs an app. @@ -52,7 +52,7 @@ extension Mas { /// - Parameters: /// - appId: App identifier /// - appLibrary: Library of installed apps - fileprivate func install(_ appId: UInt64, appLibrary: AppLibrary) throws { + fileprivate func install(_ appId: AppID, appLibrary: AppLibrary) throws { // Try to download applications with given identifiers and collect results if let product = appLibrary.installedApp(forId: appId), !force { printWarning("\(product.appName) is already installed") diff --git a/Sources/mas/Commands/Open.swift b/Sources/mas/Commands/Open.swift index a7dd37e..bc0afc9 100644 --- a/Sources/mas/Commands/Open.swift +++ b/Sources/mas/Commands/Open.swift @@ -9,7 +9,6 @@ import ArgumentParser import Foundation -private let markerValue = "appstore" private let masScheme = "macappstore" extension Mas { @@ -21,7 +20,7 @@ extension Mas { ) @Argument(help: "the app ID") - var appId: String = markerValue + var appId: AppID? /// Runs the command. func run() throws { @@ -30,18 +29,12 @@ extension Mas { func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws { do { - if appId == markerValue { + guard let appId else { // If no app ID is given, just open the MAS GUI app try openCommand.run(arguments: masScheme + "://") return } - guard let appId = Int(appId) - else { - printError("Invalid app ID") - throw MASError.noSearchResultsFound - } - guard let result = try storeSearch.lookup(app: appId).wait() else { throw MASError.noSearchResultsFound diff --git a/Sources/mas/Commands/Outdated.swift b/Sources/mas/Commands/Outdated.swift index ab78ab4..aeb99f0 100644 --- a/Sources/mas/Commands/Outdated.swift +++ b/Sources/mas/Commands/Outdated.swift @@ -33,7 +33,7 @@ extension Mas { fulfilled: appLibrary.installedApps.map { installedApp in firstly { - storeSearch.lookup(app: installedApp.itemIdentifier.intValue) + storeSearch.lookup(app: installedApp.itemIdentifier.uint64Value) }.done { storeApp in guard let storeApp else { if verbose { diff --git a/Sources/mas/Commands/Purchase.swift b/Sources/mas/Commands/Purchase.swift index 7ac1c75..601f6b2 100644 --- a/Sources/mas/Commands/Purchase.swift +++ b/Sources/mas/Commands/Purchase.swift @@ -16,7 +16,7 @@ extension Mas { ) @Argument(help: "app ID(s) to install") - var appIds: [UInt64] + var appIds: [AppID] /// Runs the command. func run() throws { diff --git a/Sources/mas/Commands/Uninstall.swift b/Sources/mas/Commands/Uninstall.swift index 2d37cef..96d572a 100644 --- a/Sources/mas/Commands/Uninstall.swift +++ b/Sources/mas/Commands/Uninstall.swift @@ -21,7 +21,7 @@ extension Mas { @Flag(help: "dry run") var dryRun = false @Argument(help: "ID of app to uninstall") - var appId: Int + var appId: AppID /// Runs the uninstall command. func run() throws { @@ -29,8 +29,6 @@ extension Mas { } func run(appLibrary: AppLibrary) throws { - let appId = UInt64(appId) - guard let product = appLibrary.installedApp(forId: appId) else { throw MASError.notInstalled } diff --git a/Sources/mas/Commands/Upgrade.swift b/Sources/mas/Commands/Upgrade.swift index 603902e..fc602b2 100644 --- a/Sources/mas/Commands/Upgrade.swift +++ b/Sources/mas/Commands/Upgrade.swift @@ -58,11 +58,11 @@ extension Mas { appIds.isEmpty ? appLibrary.installedApps : appIds.compactMap { - if let appId = UInt64($0) { - // if argument a UInt64, lookup app by id using argument + if let appId = AppID($0) { + // if argument an AppID, lookup app by id using argument return appLibrary.installedApp(forId: appId) } else { - // if argument not a UInt64, lookup app by name using argument + // if argument not an AppID, lookup app by name using argument return appLibrary.installedApp(named: $0) } } @@ -70,7 +70,7 @@ extension Mas { let promises = apps.map { installedApp in // only upgrade apps whose local version differs from the store version firstly { - storeSearch.lookup(app: installedApp.itemIdentifier.intValue) + storeSearch.lookup(app: installedApp.itemIdentifier.uint64Value) }.map { result -> (SoftwareProduct, SearchResult)? in guard let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) else { return nil diff --git a/Sources/mas/Commands/Vendor.swift b/Sources/mas/Commands/Vendor.swift index 10e4eee..32fa0fb 100644 --- a/Sources/mas/Commands/Vendor.swift +++ b/Sources/mas/Commands/Vendor.swift @@ -17,7 +17,7 @@ extension Mas { ) @Argument(help: "the app ID to show the vendor's website") - var appId: Int + var appId: AppID /// Runs the command. func run() throws { diff --git a/Sources/mas/Controllers/AppLibrary.swift b/Sources/mas/Controllers/AppLibrary.swift index d481a52..477876f 100644 --- a/Sources/mas/Controllers/AppLibrary.swift +++ b/Sources/mas/Controllers/AppLibrary.swift @@ -17,7 +17,7 @@ protocol AppLibrary { /// /// - Parameter forId: MAS ID for app. /// - Returns: Software Product of app if found; nil otherwise. - func installedApp(forId: UInt64) -> SoftwareProduct? + func installedApp(forId: AppID) -> SoftwareProduct? /// Uninstalls an app. /// @@ -32,7 +32,7 @@ extension AppLibrary { /// /// - Parameter forId: MAS ID for app. /// - Returns: Software Product of app if found; nil otherwise. - func installedApp(forId identifier: UInt64) -> SoftwareProduct? { + func installedApp(forId identifier: AppID) -> SoftwareProduct? { let appId = NSNumber(value: identifier) return installedApps.first { $0.itemIdentifier == appId } } diff --git a/Sources/mas/Controllers/MasStoreSearch.swift b/Sources/mas/Controllers/MasStoreSearch.swift index dda28f8..b5738ed 100644 --- a/Sources/mas/Controllers/MasStoreSearch.swift +++ b/Sources/mas/Controllers/MasStoreSearch.swift @@ -53,7 +53,7 @@ class MasStoreSearch: StoreSearch { } // Combine the results, removing any duplicates. - var seenAppIDs = Set() + var seenAppIDs = Set() return when(fulfilled: results).flatMapValues { $0 }.filterValues { result in seenAppIDs.insert(result.trackId).inserted } @@ -64,7 +64,7 @@ class MasStoreSearch: StoreSearch { /// - Parameter appId: MAS ID of app /// - Returns: A Promise for the search result record of app, or nil if no apps match the ID, /// or an Error if there is a problem with the network request. - func lookup(app appId: Int) -> Promise { + func lookup(app appId: AppID) -> Promise { guard let url = lookupURL(forApp: appId, inCountry: country) else { fatalError("Failed to build URL for \(appId)") } diff --git a/Sources/mas/Controllers/StoreSearch.swift b/Sources/mas/Controllers/StoreSearch.swift index c326699..2b4e11e 100644 --- a/Sources/mas/Controllers/StoreSearch.swift +++ b/Sources/mas/Controllers/StoreSearch.swift @@ -11,7 +11,7 @@ import PromiseKit /// Protocol for searching the MAS catalog. protocol StoreSearch { - func lookup(app appId: Int) -> Promise + func lookup(app appId: AppID) -> Promise func search(for appName: String) -> Promise<[SearchResult]> } @@ -49,7 +49,7 @@ extension StoreSearch { /// /// - Parameter appId: MAS app identifier. /// - Returns: URL for the lookup service or nil if appId can't be encoded. - func lookupURL(forApp appId: Int, inCountry country: String?) -> URL? { + func lookupURL(forApp appId: AppID, inCountry country: String?) -> URL? { guard var components = URLComponents(string: "https://itunes.apple.com/lookup") else { return nil } diff --git a/Sources/mas/Formatters/SearchResultFormatter.swift b/Sources/mas/Formatters/SearchResultFormatter.swift index 15fa4aa..ed01448 100644 --- a/Sources/mas/Formatters/SearchResultFormatter.swift +++ b/Sources/mas/Formatters/SearchResultFormatter.swift @@ -26,9 +26,9 @@ enum SearchResultFormatter { let price = result.price ?? 0.0 if includePrice { - output += String(format: "%12d %@ $%5.2f (%@)\n", appId, appName, price, version) + output += String(format: "%12lu %@ $%5.2f (%@)\n", appId, appName, price, version) } else { - output += String(format: "%12d %@ (%@)\n", appId, appName, version) + output += String(format: "%12lu %@ (%@)\n", appId, appName, version) } } diff --git a/Sources/mas/Mas.swift b/Sources/mas/Mas.swift index 1b95c02..a3a28ec 100644 --- a/Sources/mas/Mas.swift +++ b/Sources/mas/Mas.swift @@ -9,6 +9,8 @@ import ArgumentParser import PromiseKit +typealias AppID = UInt64 + @main struct Mas: ParsableCommand { static let configuration = CommandConfiguration( diff --git a/Sources/mas/Models/SearchResult.swift b/Sources/mas/Models/SearchResult.swift index 7ac757b..129df7c 100644 --- a/Sources/mas/Models/SearchResult.swift +++ b/Sources/mas/Models/SearchResult.swift @@ -14,7 +14,7 @@ struct SearchResult: Decodable { var price: Double? var sellerName: String var sellerUrl: String? - var trackId: Int + var trackId: AppID var trackName: String var trackViewUrl: String var version: String @@ -27,7 +27,7 @@ struct SearchResult: Decodable { price: Double = 0.0, sellerName: String = "", sellerUrl: String = "", - trackId: Int = 0, + trackId: AppID = 0, trackName: String = "", trackViewUrl: String = "", version: String = "" diff --git a/Tests/masTests/Commands/HomeSpec.swift b/Tests/masTests/Commands/HomeSpec.swift index 158ae77..6f740d3 100644 --- a/Tests/masTests/Commands/HomeSpec.swift +++ b/Tests/masTests/Commands/HomeSpec.swift @@ -32,7 +32,7 @@ public class HomeSpec: QuickSpec { expect { try Mas.Home.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand) } - .to(throwError(MASError.searchFailed)) + .to(throwError()) } it("can't find app with unknown ID") { expect { diff --git a/Tests/masTests/Commands/InfoSpec.swift b/Tests/masTests/Commands/InfoSpec.swift index 99604ef..8047c95 100644 --- a/Tests/masTests/Commands/InfoSpec.swift +++ b/Tests/masTests/Commands/InfoSpec.swift @@ -46,7 +46,7 @@ public class InfoSpec: QuickSpec { expect { try Mas.Info.parse(["--", "-999"]).run(storeSearch: storeSearch) } - .to(throwError(MASError.searchFailed)) + .to(throwError()) } it("can't find app with unknown ID") { expect { diff --git a/Tests/masTests/Commands/OpenSpec.swift b/Tests/masTests/Commands/OpenSpec.swift index 54d3bbe..c0cca20 100644 --- a/Tests/masTests/Commands/OpenSpec.swift +++ b/Tests/masTests/Commands/OpenSpec.swift @@ -33,7 +33,7 @@ public class OpenSpec: QuickSpec { expect { try Mas.Open.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand) } - .to(throwError(MASError.searchFailed)) + .to(throwError()) } it("can't find app with unknown ID") { expect { @@ -55,7 +55,7 @@ public class OpenSpec: QuickSpec { } it("just opens MAS if no app specified") { expect { - try Mas.Open.parse(["appstore"]).run(storeSearch: storeSearch, openCommand: openCommand) + try Mas.Open.parse([]).run(storeSearch: storeSearch, openCommand: openCommand) } .toNot(throwError()) expect(openCommand.arguments).toNot(beNil()) diff --git a/Tests/masTests/Commands/UninstallSpec.swift b/Tests/masTests/Commands/UninstallSpec.swift index e779c9d..691692d 100644 --- a/Tests/masTests/Commands/UninstallSpec.swift +++ b/Tests/masTests/Commands/UninstallSpec.swift @@ -18,7 +18,7 @@ public class UninstallSpec: QuickSpec { Mas.initialize() } describe("uninstall command") { - let appId = 12345 + let appId: AppID = 12345 let app = SoftwareProductMock( appName: "Some App", bundleIdentifier: "com.some.app", diff --git a/Tests/masTests/Commands/VendorSpec.swift b/Tests/masTests/Commands/VendorSpec.swift index de38f2a..499534a 100644 --- a/Tests/masTests/Commands/VendorSpec.swift +++ b/Tests/masTests/Commands/VendorSpec.swift @@ -32,7 +32,7 @@ public class VendorSpec: QuickSpec { expect { try Mas.Vendor.parse(["--", "-999"]).run(storeSearch: storeSearch, openCommand: openCommand) } - .to(throwError(MASError.searchFailed)) + .to(throwError()) } it("can't find app with unknown ID") { expect { diff --git a/Tests/masTests/Controllers/MasStoreSearchSpec.swift b/Tests/masTests/Controllers/MasStoreSearchSpec.swift index 99e1bdb..335700a 100644 --- a/Tests/masTests/Controllers/MasStoreSearchSpec.swift +++ b/Tests/masTests/Controllers/MasStoreSearchSpec.swift @@ -54,7 +54,7 @@ public class MasStoreSearchSpec: QuickSpec { context("when lookup used") { it("can find slack") { - let appId = 803_453_959 + let appId: AppID = 803_453_959 let networkSession = NetworkSessionMockFromFile(responseFile: "lookup/slack.json") let storeSearch = MasStoreSearch(networkManager: NetworkManager(session: networkSession)) diff --git a/Tests/masTests/Controllers/StoreSearchMock.swift b/Tests/masTests/Controllers/StoreSearchMock.swift index 5037b92..a276951 100644 --- a/Tests/masTests/Controllers/StoreSearchMock.swift +++ b/Tests/masTests/Controllers/StoreSearchMock.swift @@ -11,7 +11,7 @@ import PromiseKit @testable import mas class StoreSearchMock: StoreSearch { - var apps: [Int: SearchResult] = [:] + var apps: [AppID: SearchResult] = [:] func search(for appName: String) -> Promise<[SearchResult]> { let filtered = apps.filter { $1.trackName.contains(appName) } @@ -19,12 +19,7 @@ class StoreSearchMock: StoreSearch { return .value(results) } - func lookup(app appId: Int) -> Promise { - // Negative numbers are invalid - guard appId > 0 else { - return Promise(error: MASError.searchFailed) - } - + func lookup(app appId: AppID) -> Promise { guard let result = apps[appId] else { return Promise(error: MASError.noSearchResultsFound)