Skip to content

Commit

Permalink
Merge pull request #621 from rgoldberg/620-external-command
Browse files Browse the repository at this point in the history
Replace `ExternalCommand` code that starts new processes with Apple library calls
  • Loading branch information
rgoldberg authored Oct 28, 2024
2 parents a3dbbde + 9ebb018 commit 7f30214
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 270 deletions.
29 changes: 10 additions & 19 deletions Sources/mas/Commands/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import ArgumentParser
import Foundation

extension MAS {
/// Opens app page on MAS Preview. Uses the iTunes Lookup API:
Expand All @@ -21,29 +22,19 @@ extension MAS {

/// Runs the command.
func run() throws {
try run(searcher: ITunesSearchAppStoreSearcher(), openCommand: OpenSystemCommand())
try run(searcher: ITunesSearchAppStoreSearcher())
}

func run(searcher: AppStoreSearcher, openCommand: ExternalCommand) throws {
do {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}
func run(searcher: AppStoreSearcher) throws {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

do {
try openCommand.run(arguments: result.trackViewUrl)
} catch {
printError("Unable to launch open command")
throw MASError.searchFailed
}
if openCommand.failed {
let reason = openCommand.process.terminationReason
printError("Open failed: (\(reason)) \(openCommand.stderr)")
throw MASError.searchFailed
}
} catch {
throw error as? MASError ?? .searchFailed
guard let url = URL(string: result.trackViewUrl) else {
throw MASError.runtimeError("Unable to construct URL from: \(result.trackViewUrl)")
}

try url.open().wait()
}
}
}
39 changes: 13 additions & 26 deletions Sources/mas/Commands/Open.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import AppKit
import ArgumentParser
import Foundation
import PromiseKit

private let masScheme = "macappstore"
Expand All @@ -35,7 +34,7 @@ extension MAS {
try openMacAppStore().wait()
return
}
try openInMacAppStore(pageForAppID: appID, searcher: searcher).wait()
try openInMacAppStore(pageForAppID: appID, searcher: searcher)
}
}
}
Expand Down Expand Up @@ -63,32 +62,20 @@ private func openMacAppStore() -> Promise<Void> {
}
}

private func openInMacAppStore(pageForAppID appID: AppID, searcher: AppStoreSearcher) -> Promise<Void> {
Promise { seal in
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.runtimeError("Unknown app ID \(appID)")
}

guard var urlComponents = URLComponents(string: result.trackViewUrl) else {
throw MASError.runtimeError("Unable to construct URL from: \(result.trackViewUrl)")
}
private func openInMacAppStore(pageForAppID appID: AppID, searcher: AppStoreSearcher) throws {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.runtimeError("Unknown app ID \(appID)")
}

urlComponents.scheme = masScheme
guard var urlComponents = URLComponents(string: result.trackViewUrl) else {
throw MASError.runtimeError("Unable to construct URL from: \(result.trackViewUrl)")
}

guard let url = urlComponents.url else {
throw MASError.runtimeError("Unable to construct URL from: \(urlComponents)")
}
urlComponents.scheme = masScheme

if #available(macOS 10.15, *) {
NSWorkspace.shared.open(url, configuration: NSWorkspace.OpenConfiguration()) { _, error in
if let error {
seal.reject(error)
}
seal.fulfill(())
}
} else {
NSWorkspace.shared.open(url)
seal.fulfill(())
}
guard let url = urlComponents.url else {
throw MASError.runtimeError("Unable to construct URL from: \(urlComponents)")
}

try url.open().wait()
}
39 changes: 15 additions & 24 deletions Sources/mas/Commands/Vendor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import ArgumentParser
import Foundation

extension MAS {
/// Opens vendor's app page in a browser. Uses the iTunes Lookup API:
Expand All @@ -21,33 +22,23 @@ extension MAS {

/// Runs the command.
func run() throws {
try run(searcher: ITunesSearchAppStoreSearcher(), openCommand: OpenSystemCommand())
try run(searcher: ITunesSearchAppStoreSearcher())
}

func run(searcher: AppStoreSearcher, openCommand: ExternalCommand) throws {
do {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

guard let vendorWebsite = result.sellerUrl else {
throw MASError.noVendorWebsite
}

do {
try openCommand.run(arguments: vendorWebsite)
} catch {
printError("Unable to launch open command")
throw MASError.searchFailed
}
if openCommand.failed {
let reason = openCommand.process.terminationReason
printError("Open failed: (\(reason)) \(openCommand.stderr)")
throw MASError.searchFailed
}
} catch {
throw error as? MASError ?? .searchFailed
func run(searcher: AppStoreSearcher) throws {
guard let result = try searcher.lookup(appID: appID).wait() else {
throw MASError.noSearchResultsFound
}

guard let urlString = result.sellerUrl else {
throw MASError.noVendorWebsite
}

guard let url = URL(string: urlString) else {
throw MASError.runtimeError("Unable to construct URL from: \(urlString)")
}

try url.open().wait()
}
}
}
6 changes: 3 additions & 3 deletions Sources/mas/Controllers/ITunesSearchAppStoreSearcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ class ITunesSearchAppStoreSearcher: AppStoreSearcher {
// Search for apps for compatible platforms, in order of preference.
// Macs with Apple Silicon can run iPad and iPhone apps.
var entities = [Entity.desktopSoftware]
if SysCtlSystemCommand.isAppleSilicon {
entities += [.iPadSoftware, .iPhoneSoftware]
}
#if arch(arm64)
entities += [.iPadSoftware, .iPhoneSoftware]
#endif

let results = entities.map { entity in
guard let url = searchURL(for: searchTerm, inCountry: country, ofEntity: entity) else {
Expand Down
63 changes: 0 additions & 63 deletions Sources/mas/ExternalCommands/ExternalCommand.swift

This file was deleted.

23 changes: 0 additions & 23 deletions Sources/mas/ExternalCommands/OpenSystemCommand.swift

This file was deleted.

41 changes: 0 additions & 41 deletions Sources/mas/ExternalCommands/SysCtlSystemCommand.swift

This file was deleted.

32 changes: 32 additions & 0 deletions Sources/mas/Network/URL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// URL.swift
// mas
//
// Created by Ross Goldberg on 2024-10-28.
// Copyright © 2024 mas-cli. All rights reserved.
//

import AppKit
import Foundation
import PromiseKit

extension URL {
func open() -> Promise<Void> {
Promise { seal in
if #available(macOS 10.15, *) {
NSWorkspace.shared.open(self, configuration: NSWorkspace.OpenConfiguration()) { _, error in
if let error {
seal.reject(error)
}
seal.fulfill(())
}
} else {
if NSWorkspace.shared.open(self) {
seal.fulfill(())
} else {
seal.reject(MASError.runtimeError("Failed to open \(self)"))
}
}
}
}
}
10 changes: 3 additions & 7 deletions Tests/masTests/Commands/HomeSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import Quick
public class HomeSpec: QuickSpec {
override public func spec() {
let searcher = MockAppStoreSearcher()
let openCommand = MockOpenSystemCommand()

beforeSuite {
MAS.initialize()
Expand All @@ -25,13 +24,13 @@ public class HomeSpec: QuickSpec {
}
it("fails to open app with invalid ID") {
expect {
try MAS.Home.parse(["--", "-999"]).run(searcher: searcher, openCommand: openCommand)
try MAS.Home.parse(["--", "-999"]).run(searcher: searcher)
}
.to(throwError())
}
it("can't find app with unknown ID") {
expect {
try MAS.Home.parse(["999"]).run(searcher: searcher, openCommand: openCommand)
try MAS.Home.parse(["999"]).run(searcher: searcher)
}
.to(throwError(MASError.noSearchResultsFound))
}
Expand All @@ -43,11 +42,8 @@ public class HomeSpec: QuickSpec {
)
searcher.apps[mockResult.trackId] = mockResult
expect {
try MAS.Home.parse([String(mockResult.trackId)])
.run(searcher: searcher, openCommand: openCommand)
return openCommand.arguments
try MAS.Home.parse([String(mockResult.trackId)]).run(searcher: searcher)
}
== [mockResult.trackViewUrl]
}
}
}
Expand Down
Loading

0 comments on commit 7f30214

Please sign in to comment.