mirror of
https://github.com/mas-cli/mas
synced 2025-02-16 12:38:30 +00:00
Improve spacing.
Partial #592 Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
This commit is contained in:
parent
3eaffa5c3e
commit
71fbe2e444
13 changed files with 75 additions and 59 deletions
|
@ -20,19 +20,23 @@ import StoreFoundation
|
||||||
/// the promise is rejected with the first error, after all remaining downloads are attempted.
|
/// the promise is rejected with the first error, after all remaining downloads are attempted.
|
||||||
func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise<Void> {
|
func downloadAll(_ appIDs: [AppID], purchase: Bool = false) -> Promise<Void> {
|
||||||
var firstError: Error?
|
var firstError: Error?
|
||||||
return appIDs.reduce(Guarantee.value(())) { previous, appID in
|
return
|
||||||
previous.then {
|
appIDs
|
||||||
downloadWithRetries(appID, purchase: purchase).recover { error in
|
.reduce(Guarantee.value(())) { previous, appID in
|
||||||
if firstError == nil {
|
previous.then {
|
||||||
firstError = error
|
downloadWithRetries(appID, purchase: purchase)
|
||||||
}
|
.recover { error in
|
||||||
|
if firstError == nil {
|
||||||
|
firstError = error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.done {
|
.done {
|
||||||
if let error = firstError {
|
if let error = firstError {
|
||||||
throw error
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempts: Int = 3) -> Promise<Void> {
|
private func downloadWithRetries(_ appID: AppID, purchase: Bool = false, attempts: Int = 3) -> Promise<Void> {
|
||||||
|
|
|
@ -15,13 +15,15 @@ extension ISStoreAccount: StoreAccount {
|
||||||
if #available(macOS 10.13, *) {
|
if #available(macOS 10.13, *) {
|
||||||
return race(
|
return race(
|
||||||
Promise { seal in
|
Promise { seal in
|
||||||
ISServiceProxy.genericShared().accountService.primaryAccount { storeAccount in
|
ISServiceProxy.genericShared().accountService
|
||||||
seal.fulfill(storeAccount)
|
.primaryAccount { storeAccount in
|
||||||
}
|
seal.fulfill(storeAccount)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
after(seconds: 30).then {
|
after(seconds: 30)
|
||||||
Promise(error: MASError.notSignedIn)
|
.then {
|
||||||
}
|
Promise(error: MASError.notSignedIn)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return .value(CKAccountStore.shared().primaryAccount)
|
return .value(CKAccountStore.shared().primaryAccount)
|
||||||
|
@ -76,9 +78,10 @@ extension ISStoreAccount: StoreAccount {
|
||||||
|
|
||||||
return race(
|
return race(
|
||||||
signInPromise,
|
signInPromise,
|
||||||
after(seconds: 30).then {
|
after(seconds: 30)
|
||||||
Promise(error: MASError.signInFailed(error: nil))
|
.then {
|
||||||
}
|
Promise(error: MASError.signInFailed(error: nil))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
import CommerceKit
|
import CommerceKit
|
||||||
import StoreFoundation
|
import StoreFoundation
|
||||||
|
|
||||||
@objc class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
|
@objc
|
||||||
|
class PurchaseDownloadObserver: NSObject, CKDownloadQueueObserver {
|
||||||
let purchase: SSPurchase
|
let purchase: SSPurchase
|
||||||
var completionHandler: (() -> Void)?
|
var completionHandler: (() -> Void)?
|
||||||
var errorHandler: ((MASError) -> Void)?
|
var errorHandler: ((MASError) -> Void)?
|
||||||
|
|
|
@ -23,7 +23,6 @@ extension SSPurchase {
|
||||||
if purchase {
|
if purchase {
|
||||||
parameters["macappinstalledconfirmed"] = 1
|
parameters["macappinstalledconfirmed"] = 1
|
||||||
parameters["pricingParameters"] = "STDQ"
|
parameters["pricingParameters"] = "STDQ"
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
parameters["pricingParameters"] = "STDRDL"
|
parameters["pricingParameters"] = "STDRDL"
|
||||||
}
|
}
|
||||||
|
@ -63,19 +62,20 @@ extension SSPurchase {
|
||||||
|
|
||||||
private func perform() -> Promise<Void> {
|
private func perform() -> Promise<Void> {
|
||||||
Promise<SSPurchase> { seal in
|
Promise<SSPurchase> { seal in
|
||||||
CKPurchaseController.shared().perform(self, withOptions: 0) { purchase, _, error, response in
|
CKPurchaseController.shared()
|
||||||
if let error {
|
.perform(self, withOptions: 0) { purchase, _, error, response in
|
||||||
seal.reject(MASError.purchaseFailed(error: error as NSError?))
|
if let error {
|
||||||
return
|
seal.reject(MASError.purchaseFailed(error: error as NSError?))
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard response?.downloads.isEmpty == false, let purchase else {
|
guard response?.downloads.isEmpty == false, let purchase else {
|
||||||
seal.reject(MASError.noDownloads)
|
seal.reject(MASError.noDownloads)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
seal.fulfill(purchase)
|
seal.fulfill(purchase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.then { purchase in
|
.then { purchase in
|
||||||
let observer = PurchaseDownloadObserver(purchase: purchase)
|
let observer = PurchaseDownloadObserver(purchase: purchase)
|
||||||
|
|
|
@ -35,13 +35,11 @@ extension Mas {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let result = try storeSearch.lookup(appID: appID).wait()
|
guard let result = try storeSearch.lookup(appID: appID).wait() else {
|
||||||
else {
|
|
||||||
throw MASError.noSearchResultsFound
|
throw MASError.noSearchResultsFound
|
||||||
}
|
}
|
||||||
|
|
||||||
guard var url = URLComponents(string: result.trackViewUrl)
|
guard var url = URLComponents(string: result.trackViewUrl) else {
|
||||||
else {
|
|
||||||
throw MASError.searchFailed
|
throw MASError.searchFailed
|
||||||
}
|
}
|
||||||
url.scheme = masScheme
|
url.scheme = masScheme
|
||||||
|
|
|
@ -32,7 +32,8 @@ extension Mas {
|
||||||
appLibrary.installedApps.map { installedApp in
|
appLibrary.installedApps.map { installedApp in
|
||||||
firstly {
|
firstly {
|
||||||
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
|
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
|
||||||
}.done { storeApp in
|
}
|
||||||
|
.done { storeApp in
|
||||||
guard let storeApp else {
|
guard let storeApp else {
|
||||||
if verbose {
|
if verbose {
|
||||||
printWarning(
|
printWarning(
|
||||||
|
|
|
@ -41,7 +41,8 @@ extension Mas {
|
||||||
print("Upgrading \(apps.count) outdated application\(apps.count > 1 ? "s" : ""):")
|
print("Upgrading \(apps.count) outdated application\(apps.count > 1 ? "s" : ""):")
|
||||||
print(
|
print(
|
||||||
apps.map { "\($0.installedApp.appName) (\($0.installedApp.bundleVersion)) -> (\($0.storeApp.version))" }
|
apps.map { "\($0.installedApp.appName) (\($0.installedApp.bundleVersion)) -> (\($0.storeApp.version))" }
|
||||||
.joined(separator: "\n"))
|
.joined(separator: "\n")
|
||||||
|
)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try downloadAll(apps.map(\.installedApp.itemIdentifier.appIDValue)).wait()
|
try downloadAll(apps.map(\.installedApp.itemIdentifier.appIDValue)).wait()
|
||||||
|
@ -71,7 +72,8 @@ extension Mas {
|
||||||
// only upgrade apps whose local version differs from the store version
|
// only upgrade apps whose local version differs from the store version
|
||||||
firstly {
|
firstly {
|
||||||
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
|
storeSearch.lookup(appID: installedApp.itemIdentifier.appIDValue)
|
||||||
}.map { result -> (SoftwareProduct, SearchResult)? in
|
}
|
||||||
|
.map { result -> (SoftwareProduct, SearchResult)? in
|
||||||
guard let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) else {
|
guard let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,13 @@ extension Mas {
|
||||||
|
|
||||||
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
|
func run(storeSearch: StoreSearch, openCommand: ExternalCommand) throws {
|
||||||
do {
|
do {
|
||||||
guard let result = try storeSearch.lookup(appID: appID).wait()
|
guard let result = try storeSearch.lookup(appID: appID).wait() else {
|
||||||
else {
|
|
||||||
throw MASError.noSearchResultsFound
|
throw MASError.noSearchResultsFound
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let vendorWebsite = result.sellerUrl
|
guard let vendorWebsite = result.sellerUrl else {
|
||||||
else { throw MASError.noVendorWebsite }
|
throw MASError.noVendorWebsite
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try openCommand.run(arguments: vendorWebsite)
|
try openCommand.run(arguments: vendorWebsite)
|
||||||
|
|
|
@ -14,9 +14,10 @@ class MasAppLibrary: AppLibrary {
|
||||||
private let softwareMap: SoftwareMap
|
private let softwareMap: SoftwareMap
|
||||||
|
|
||||||
/// Array of installed software products.
|
/// Array of installed software products.
|
||||||
lazy var installedApps: [SoftwareProduct] = softwareMap.allSoftwareProducts().filter { product in
|
lazy var installedApps: [SoftwareProduct] = softwareMap.allSoftwareProducts()
|
||||||
product.bundlePath.starts(with: "/Applications/")
|
.filter { product in
|
||||||
}
|
product.bundlePath.starts(with: "/Applications/")
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal initializer for providing a mock software map.
|
/// Internal initializer for providing a mock software map.
|
||||||
/// - Parameter softwareMap: SoftwareMap to use
|
/// - Parameter softwareMap: SoftwareMap to use
|
||||||
|
|
|
@ -53,9 +53,11 @@ class MasStoreSearch: StoreSearch {
|
||||||
|
|
||||||
// Combine the results, removing any duplicates.
|
// Combine the results, removing any duplicates.
|
||||||
var seenAppIDs = Set<AppID>()
|
var seenAppIDs = Set<AppID>()
|
||||||
return when(fulfilled: results).flatMapValues { $0 }.filterValues { result in
|
return when(fulfilled: results)
|
||||||
seenAppIDs.insert(result.trackId).inserted
|
.flatMapValues { $0 }
|
||||||
}
|
.filterValues { result in
|
||||||
|
seenAppIDs.insert(result.trackId).inserted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks up app details.
|
/// Looks up app details.
|
||||||
|
@ -75,14 +77,14 @@ class MasStoreSearch: StoreSearch {
|
||||||
return .value(nil)
|
return .value(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let pageUrl = URL(string: result.trackViewUrl)
|
guard let pageUrl = URL(string: result.trackViewUrl) else {
|
||||||
else {
|
|
||||||
return .value(result)
|
return .value(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return firstly {
|
return firstly {
|
||||||
self.scrapeAppStoreVersion(pageUrl)
|
self.scrapeAppStoreVersion(pageUrl)
|
||||||
}.map { pageVersion in
|
}
|
||||||
|
.map { pageVersion in
|
||||||
guard let pageVersion,
|
guard let pageVersion,
|
||||||
let searchVersion = Version(tolerant: result.version),
|
let searchVersion = Version(tolerant: result.version),
|
||||||
pageVersion > searchVersion
|
pageVersion > searchVersion
|
||||||
|
@ -94,7 +96,8 @@ class MasStoreSearch: StoreSearch {
|
||||||
var result = result
|
var result = result
|
||||||
result.version = pageVersion.description
|
result.version = pageVersion.description
|
||||||
return result
|
return result
|
||||||
}.recover { _ in
|
}
|
||||||
|
.recover { _ in
|
||||||
// If we were unable to scrape the App Store page, assume compatibility.
|
// If we were unable to scrape the App Store page, assume compatibility.
|
||||||
.value(result)
|
.value(result)
|
||||||
}
|
}
|
||||||
|
@ -120,7 +123,8 @@ class MasStoreSearch: StoreSearch {
|
||||||
private func scrapeAppStoreVersion(_ pageUrl: URL) -> Promise<Version?> {
|
private func scrapeAppStoreVersion(_ pageUrl: URL) -> Promise<Version?> {
|
||||||
firstly {
|
firstly {
|
||||||
networkManager.loadData(from: pageUrl)
|
networkManager.loadData(from: pageUrl)
|
||||||
}.map { data in
|
}
|
||||||
|
.map { data in
|
||||||
guard let html = String(data: data, encoding: .utf8),
|
guard let html = String(data: data, encoding: .utf8),
|
||||||
let capture = MasStoreSearch.appVersionExpression.firstMatch(in: html)?.captures[0],
|
let capture = MasStoreSearch.appVersionExpression.firstMatch(in: html)?.captures[0],
|
||||||
let version = Version(tolerant: capture)
|
let version = Version(tolerant: capture)
|
||||||
|
|
|
@ -18,7 +18,9 @@ private var standardError = FileHandle.standardError
|
||||||
extension FileHandle: TextOutputStream {
|
extension FileHandle: TextOutputStream {
|
||||||
/// Appends the given string to the stream.
|
/// Appends the given string to the stream.
|
||||||
public func write(_ string: String) {
|
public func write(_ string: String) {
|
||||||
guard let data = string.data(using: .utf8) else { return }
|
guard let data = string.data(using: .utf8) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
write(data)
|
write(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ class StoreSearchMock: StoreSearch {
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(appID: AppID) -> Promise<SearchResult?> {
|
func lookup(appID: AppID) -> Promise<SearchResult?> {
|
||||||
guard let result = apps[appID]
|
guard let result = apps[appID] else {
|
||||||
else {
|
|
||||||
return Promise(error: MASError.noSearchResultsFound)
|
return Promise(error: MASError.noSearchResultsFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,9 @@ class NetworkSessionMockFromFile: NetworkSessionMock {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadData(from _: URL) -> Promise<Data> {
|
override func loadData(from _: URL) -> Promise<Data> {
|
||||||
guard let fileURL = Bundle.url(for: responseFile)
|
guard let fileURL = Bundle.url(for: responseFile) else {
|
||||||
else { fatalError("Unable to load file \(responseFile)") }
|
fatalError("Unable to load file \(responseFile)")
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
return .value(try Data(contentsOf: fileURL, options: .mappedIfSafe))
|
return .value(try Data(contentsOf: fileURL, options: .mappedIfSafe))
|
||||||
|
|
Loading…
Add table
Reference in a new issue