Skip to content

Commit

Permalink
Merge pull request #599 from rgoldberg/313-uninstall
Browse files Browse the repository at this point in the history
Fix `uninstall`
  • Loading branch information
rgoldberg authored Oct 26, 2024
2 parents 6b27d51 + 98c85ac commit 2eeff82
Show file tree
Hide file tree
Showing 13 changed files with 936 additions and 91 deletions.
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# Rule options
--commas always
--extensionacl on-declarations
--hexliteralcase lowercase
--importgrouping testable-last
--lineaftermarks false
--ranges no-space
4 changes: 2 additions & 2 deletions Sources/mas/Commands/Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ extension Mas {
func run(appLibrary: AppLibrary) throws {
// Try to download applications with given identifiers and collect results
let appIDs = appIDs.filter { appID in
if let product = appLibrary.installedApp(withAppID: appID), !force {
printWarning("\(product.appName) is already installed")
if let appName = appLibrary.installedApps(withAppID: appID).first?.appName, !force {
printWarning("\(appName) is already installed")
return false
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/mas/Commands/Lucky.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ extension Mas {
/// - Throws: Any error that occurs while attempting to install the app.
private func install(appID: AppID, appLibrary: AppLibrary) throws {
// Try to download applications with given identifiers and collect results
if let product = appLibrary.installedApp(withAppID: appID), !force {
printWarning("\(product.appName) is already installed")
if let appName = appLibrary.installedApps(withAppID: appID).first?.appName, !force {
printWarning("\(appName) is already installed")
} else {
do {
try downloadAll([appID]).wait()
Expand Down
4 changes: 2 additions & 2 deletions Sources/mas/Commands/Purchase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ extension Mas {
func run(appLibrary: AppLibrary) throws {
// Try to download applications with given identifiers and collect results
let appIDs = appIDs.filter { appID in
if let product = appLibrary.installedApp(withAppID: appID) {
printWarning("\(product.appName) has already been purchased.")
if let appName = appLibrary.installedApps(withAppID: appID).first?.appName {
printWarning("\(appName) has already been purchased.")
return false
}

Expand Down
35 changes: 26 additions & 9 deletions Sources/mas/Commands/Uninstall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
//

import ArgumentParser
import CommerceKit
import StoreFoundation
import Foundation

extension Mas {
/// Command which uninstalls apps managed by the Mac App Store.
Expand All @@ -29,19 +28,37 @@ extension Mas {
}

func run(appLibrary: AppLibrary) throws {
guard let product = appLibrary.installedApp(withAppID: appID) else {
throw MASError.notInstalled
guard NSUserName() == "root" else {
throw MASError.macOSUserMustBeRoot
}

guard let username = getSudoUsername() else {
throw MASError.runtimeError("Could not determine the original username")
}

guard
let uid = getSudoUID(),
seteuid(uid) == 0
else {
throw MASError.runtimeError("Failed to switch effective user from 'root' to '\(username)'")
}

let installedApps = appLibrary.installedApps(withAppID: appID)
guard !installedApps.isEmpty else {
throw MASError.notInstalled(appID: appID)
}

if dryRun {
printInfo("\(product.appName) \(product.bundlePath)")
for installedApp in installedApps {
printInfo("'\(installedApp.appName)' '\(installedApp.bundlePath)'")
}
printInfo("(not removed, dry run)")
} else {
do {
try appLibrary.uninstallApp(app: product)
} catch {
throw MASError.uninstallFailed
guard seteuid(0) == 0 else {
throw MASError.runtimeError("Failed to revert effective user from '\(username)' back to 'root'")
}

try appLibrary.uninstallApps(atPaths: installedApps.map(\.bundlePath))
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/mas/Commands/Upgrade.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ extension Mas {
let apps =
appIDs.isEmpty
? appLibrary.installedApps
: appIDs.compactMap { appID in
: appIDs.flatMap { appID in
if let appID = AppID(appID) {
// argument is an AppID, lookup app by id using argument
return appLibrary.installedApp(withAppID: appID)
// argument is an AppID, lookup apps by id using argument
return appLibrary.installedApps(withAppID: appID)
}

// argument is not an AppID, lookup app by name using argument
return appLibrary.installedApp(named: appID)
// argument is not an AppID, lookup apps by name using argument
return appLibrary.installedApps(named: appID)
}

let promises = apps.map { installedApp in
Expand Down
34 changes: 14 additions & 20 deletions Sources/mas/Controllers/AppLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,29 @@ protocol AppLibrary {
/// Entire set of installed apps.
var installedApps: [SoftwareProduct] { get }

/// Finds an app for appID.
/// Uninstalls all apps located at any of the elements of `appPaths`.
///
/// - Parameter appID: app ID for app.
/// - Returns: SoftwareProduct of app if found; nil otherwise.
func installedApp(withAppID appID: AppID) -> SoftwareProduct?

/// Uninstalls an app.
///
/// - Parameter app: App to be removed.
/// - Throws: Error if there is a problem.
func uninstallApp(app: SoftwareProduct) throws
/// - Parameter appPaths: Paths to apps to be uninstalled.
/// - Throws: Error if any problem occurs.
func uninstallApps(atPaths appPaths: [String]) throws
}

/// Common logic
extension AppLibrary {
/// Finds an app for appID.
/// Finds all installed instances of apps whose app ID is `appID`.
///
/// - Parameter appID: app ID for app.
/// - Returns: SoftwareProduct of app if found; nil otherwise.
func installedApp(withAppID appID: AppID) -> SoftwareProduct? {
/// - Parameter appID: app ID for app(s).
/// - Returns: [SoftwareProduct] of matching apps.
func installedApps(withAppID appID: AppID) -> [SoftwareProduct] {
let appID = NSNumber(value: appID)
return installedApps.first { $0.itemIdentifier == appID }
return installedApps.filter { $0.itemIdentifier == appID }
}

/// Finds an app by name.
/// Finds all installed instances of apps whose name is `appName`.
///
/// - Parameter appName: Full title of an app.
/// - Returns: Software Product of app if found; nil otherwise.
func installedApp(named appName: String) -> SoftwareProduct? {
installedApps.first { $0.appName == appName }
/// - Parameter appName: Full name of app(s).
/// - Returns: [SoftwareProduct] of matching apps.
func installedApps(named appName: String) -> [SoftwareProduct] {
installedApps.filter { $0.appName == appName }
}
}
Loading

0 comments on commit 2eeff82

Please sign in to comment.