Merge pull request #272 from mas-cli/purchase-cleanup

🧹 Purchase cleanup
This commit is contained in:
Ben Chatelain 2020-05-14 22:05:50 -06:00 committed by GitHub
commit 38f20a8607
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 141 additions and 136 deletions

View file

@ -8,4 +8,3 @@
excluded:
- Carthage
- docs
- MasKitTests

View file

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
- ✨ New `purchase` command for purchasing free apps #264 (resolves #2, #145)
thanks, [@blochberger](https://github.com/blochberger)!
- 🐟 Seriously more interactive fish completions #242
thanks, [@lwolfsonkin](https://github.com/lwolfsonkin)!
- 🧹 Purchase cleanup #272
- ♻️ SoftwareMap Protocol #271
- 🕊 Swift 5 #255
- ⚒️ Xcode 10.2 and macOS 10.14 required to build
@ -14,10 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- ⬆️ Nimble (8.0.4) #255
- ⬆️ Quick (2.2.0) #255
- Result #255
- Seriously more interactive fish completions #242
thanks, [@lwolfsonkin](https://github.com/lwolfsonkin)!
- 💡 Update readme with simpler tap usage #241
- Added support for purchasing apps (#2, #145)
## [v1.6.4] 🔎 Search Fix - 2020-05-11

View file

@ -12,15 +12,17 @@ import StoreFoundation
/// Monitors app download progress.
///
/// - Parameter adamId: An app ID?
/// - Parameter purchase: Flag indicating whether the app needs to be purchased.
/// Only works for free apps. Defaults to false.
/// - Returns: An error, if one occurred.
func download(_ adamId: UInt64, isPurchase: Bool) -> MASError? {
func download(_ adamId: UInt64, purchase: Bool = false) -> MASError? {
guard let account = ISStoreAccount.primaryAccount else {
return .notSignedIn
}
guard let storeAccount = account as? ISStoreAccount
else { fatalError("Unable to cast StoreAccount to ISStoreAccount") }
let purchase = SSPurchase(adamId: adamId, account: storeAccount, isPurchase: isPurchase)
let purchase = SSPurchase(adamId: adamId, account: storeAccount, purchase: purchase)
var purchaseError: MASError?
var observerIdentifier: CKDownloadQueueObserver?

View file

@ -13,7 +13,7 @@ typealias SSPurchaseCompletion =
(_ purchase: SSPurchase?, _ completed: Bool, _ error: Error?, _ response: SSPurchaseResponse?) -> Void
extension SSPurchase {
convenience init(adamId: UInt64, account: ISStoreAccount, isPurchase: Bool) {
convenience init(adamId: UInt64, account: ISStoreAccount, purchase: Bool = false) {
self.init()
var parameters: [String: Any] = [
@ -24,7 +24,7 @@ extension SSPurchase {
"appExtVrsId": 0
]
if isPurchase {
if purchase {
parameters["macappinstalledconfirmed"] = 1
parameters["pricingParameters"] = "STDQ"
@ -42,7 +42,7 @@ extension SSPurchase {
appleID = account.identifier
// Not sure if this is needed, but lets use it here.
if isPurchase {
if purchase {
isRedownload = false
}

View file

@ -29,8 +29,7 @@ public struct HomeCommand: CommandProtocol {
/// Runs the command.
public func run(_ options: HomeOptions) -> Result<(), MASError> {
do {
guard let result = try storeSearch.lookup(app: options.appId)
else {
guard let result = try storeSearch.lookup(app: options.appId) else {
print("No results found")
return .failure(.noSearchResultsFound)
}

View file

@ -25,8 +25,7 @@ public struct InfoCommand: CommandProtocol {
/// Runs the command.
public func run(_ options: InfoOptions) -> Result<(), MASError> {
do {
guard let result = try storeSearch.lookup(app: options.appId)
else {
guard let result = try storeSearch.lookup(app: options.appId) else {
print("No results found")
return .failure(.noSearchResultsFound)
}

View file

@ -18,7 +18,6 @@ public struct InstallCommand: CommandProtocol {
private let appLibrary: AppLibrary
/// Public initializer.
/// - Parameter appLibrary: AppLibrary manager.
public init() {
self.init(appLibrary: MasAppLibrary())
}
@ -38,7 +37,7 @@ public struct InstallCommand: CommandProtocol {
return nil
}
return download(appId, isPurchase: false)
return download(appId)
}
switch downloadResults.count {

View file

@ -73,7 +73,7 @@ public struct LuckyCommand: CommandProtocol {
return nil
}
return download(appId, isPurchase: false)
return download(appId)
}
switch downloadResults.count {

View file

@ -14,20 +14,29 @@ public struct PurchaseCommand: CommandProtocol {
public let verb = "purchase"
public let function = "Purchase and download free apps from the Mac App Store"
/// Designated initializer.
private let appLibrary: AppLibrary
/// Public initializer.
public init() {
self.init(appLibrary: MasAppLibrary())
}
/// Internal initializer.
/// - Parameter appLibrary: AppLibrary manager.
init(appLibrary: AppLibrary = MasAppLibrary()) {
self.appLibrary = appLibrary
}
/// Runs the command.
public func run(_ options: Options) -> Result<(), MASError> {
// Try to download applications with given identifiers and collect results
let downloadResults = options.appIds.compactMap { (appId) -> MASError? in
if let product = installedApp(appId) {
if let product = appLibrary.installedApp(forId: appId) {
printWarning("\(product.appName) has already been purchased.")
return nil
}
return download(appId, isPurchase: true)
return download(appId, purchase: true)
}
switch downloadResults.count {
@ -39,13 +48,6 @@ public struct PurchaseCommand: CommandProtocol {
return .failure(.downloadFailed(error: nil))
}
}
fileprivate func installedApp(_ appId: UInt64) -> CKSoftwareProduct? {
let appId = NSNumber(value: appId)
let softwareMap = CKSoftwareMap.shared()
return softwareMap.allProducts()?.first { $0.itemIdentifier == appId }
}
}
public struct PurchaseOptions: OptionsProtocol {

View file

@ -71,7 +71,7 @@ public struct UpgradeCommand: CommandProtocol {
print(updates.map({ "\($0.title) (\($0.bundleVersion))" }).joined(separator: ", "))
let updateResults = updates.compactMap {
download($0.itemIdentifier.uint64Value, isPurchase: false)
download($0.itemIdentifier.uint64Value)
}
switch updateResults.count {

View file

@ -20,9 +20,6 @@ extension NetworkResult: Equatable {
case let (.failure(error1), .failure(error2)):
return error1.localizedDescription == error2.localizedDescription
// case (.none, .none):
// return true
default:
return false
}

View file

@ -0,0 +1,10 @@
#
# .swiftlint.yml
# MasKitTests
#
# https://github.com/realm/SwiftLint#configuration
#
---
disabled_rules:
- force_cast
- function_body_length

View file

@ -48,15 +48,9 @@ struct SoftwareMapMock: SoftwareMap {
}
func product(for bundleIdentifier: String) -> SoftwareProduct? {
for product in products {
if product.bundleIdentifier == bundleIdentifier {
for product in products where product.bundleIdentifier == bundleIdentifier {
return product
}
}
return nil
}
}
//public func == (lhs: Expectation<[SoftwareProduct]>, rhs: [SoftwareProduct]) {
// lhs.to(beCloseTo(rhs))
//}

View file

@ -14,6 +14,7 @@ class TestURLSessionDelegate: NSObject, URLSessionDelegate {
didReceive challenge: URLAuthenticationChallenge,
completionHandler: (URLSession.AuthChallengeDisposition,
URLCredential?) -> Void) {
// For example, you may want to override this to accept some self-signed certs here.
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
Constants.selfSignedHosts.contains(challenge.protectionSpace.host) {

View file

@ -19,6 +19,7 @@ fi
# : command: usage: command [-pVv] command [arg ...]
if command -v swiftlint > /dev/null; then
swiftlint autocorrect --format
swiftlint lint --quiet
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"