diff --git a/Sources/utiluti/AppCommands.swift b/Sources/utiluti/AppCommands.swift index 9088587..a8ddb73 100644 --- a/Sources/utiluti/AppCommands.swift +++ b/Sources/utiluti/AppCommands.swift @@ -10,7 +10,7 @@ import ArgumentParser import UniformTypeIdentifiers import AppKit // for NSWorkspace -struct AppCommands: ParsableCommand { +struct AppCommands: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "app", abstract: "list uniform types identifiers and url schemes associated with an app", @@ -20,7 +20,7 @@ struct AppCommands: ParsableCommand { ] ) - struct Types: ParsableCommand { + struct Types: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "List the uniform type identifiers this app can open") @@ -31,7 +31,7 @@ struct AppCommands: ParsableCommand { help: "show more information") var verbose: Bool = false - func run() { + func run() async { guard let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appID), let appBundle = Bundle(url: appURL), @@ -82,7 +82,7 @@ struct AppCommands: ParsableCommand { } } - struct Schemes: ParsableCommand { + struct Schemes: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "List the urls schemes this app can open") @@ -93,7 +93,7 @@ struct AppCommands: ParsableCommand { help: "show more information") var verbose: Bool = false - func run() { + func run() async { guard let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appID), let appBundle = Bundle(url: appURL), diff --git a/Sources/utiluti/FileCommands.swift b/Sources/utiluti/FileCommands.swift index 6d3b904..6489bdb 100644 --- a/Sources/utiluti/FileCommands.swift +++ b/Sources/utiluti/FileCommands.swift @@ -12,7 +12,7 @@ import ArgumentParser import UniformTypeIdentifiers import AppKit // for NSWorkspace -struct FileCommands: ParsableCommand { +struct FileCommands: AsyncParsableCommand { static var subCommands: [ParsableCommand.Type] { if #available(macOS 12.0, *) { @@ -28,21 +28,21 @@ struct FileCommands: ParsableCommand { subcommands: subCommands ) - struct GetUTI: ParsableCommand { + struct GetUTI: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "get the uniform type identifier of a file") @Argument(help:ArgumentHelp("file path", valueName: "path")) var path: String - func run() { + func run() async { let url = URL(fileURLWithPath: path) let typeIdentifier = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier print(typeIdentifier ?? "") } } - struct App: ParsableCommand { + struct App: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "get the app that will open this file") @@ -54,7 +54,7 @@ struct FileCommands: ParsableCommand { valueName: "bundleID")) var bundleID = false - func run() { + func run() async { let url = URL(fileURLWithPath: path) if let app = NSWorkspace.shared.urlForApplication(toOpen: url) { if bundleID { @@ -73,7 +73,7 @@ struct FileCommands: ParsableCommand { } @available(macOS 12, *) - struct ListApps: ParsableCommand { + struct ListApps: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "get all app that can open this file") @@ -85,7 +85,7 @@ struct FileCommands: ParsableCommand { valueName: "bundleID")) var bundleID = false - func run() { + func run() async { let url = URL(fileURLWithPath: path) let apps = NSWorkspace.shared.urlsForApplications(toOpen: url) for app in apps { diff --git a/Sources/utiluti/GetUTI.swift b/Sources/utiluti/GetUTI.swift index ee79bc9..fe9ed03 100644 --- a/Sources/utiluti/GetUTI.swift +++ b/Sources/utiluti/GetUTI.swift @@ -9,7 +9,7 @@ import Foundation import ArgumentParser import UniformTypeIdentifiers -struct GetUTI: ParsableCommand { +struct GetUTI: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "Get the type identifier (UTI) for a file extension") @@ -19,7 +19,7 @@ struct GetUTI: ParsableCommand { @Flag(help: "show dynamic identifiers") var showDynamic = false - func run() { + func run() async { guard let utype = UTType(filenameExtension: fileExtension) else { Self.exit(withError: ExitCode(3)) } diff --git a/Sources/utiluti/LSKit.swift b/Sources/utiluti/LSKit.swift index 7bc7d54..594047c 100644 --- a/Sources/utiluti/LSKit.swift +++ b/Sources/utiluti/LSKit.swift @@ -65,27 +65,21 @@ struct LSKit { - scheme: url scheme (excluding the `:` or `/`, e.g. `http`) - Returns: OSStatus (discardable) */ - @discardableResult static func setDefaultApp(identifier: String, forScheme scheme: String) -> OSStatus { + @discardableResult static func setDefaultApp(identifier: String, forScheme scheme: String) async -> OSStatus { if #available(macOS 12, *) { // print("running on macOS 12, using NSWorkspace") - let ws = NSWorkspace.shared - - // since the new NSWorkspace function is asynchronous we have to use semaphores here - let semaphore = DispatchSemaphore(value: 0) - var errCode: OSStatus = 0 - - guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 } - ws.setDefaultApplication(at: appURL, toOpenURLsWithScheme: scheme) { err in - // err is an NSError wrapped in a CocoaError - if let err = err as? CocoaError { - if let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError { - errCode = OSStatus(clamping: underlyingError.code) - } + do { + let ws = NSWorkspace.shared + guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 } + try await ws.setDefaultApplication(at: appURL, toOpenURLsWithScheme: scheme) + return 0 + } catch { + if let err = error as? CocoaError, let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError { + return OSStatus(clamping: underlyingError.code) + } else { + return 1 } - semaphore.signal() } - semaphore.wait() - return errCode } else { return LSSetDefaultHandlerForURLScheme(scheme as CFString, identifier as CFString) } @@ -147,31 +141,26 @@ struct LSKit { - forTypeIdentifier: uniform type identifier ( e.g. `public.html`) - Returns: OSStatus (discardable) */ - @discardableResult static func setDefaultApp(identifier: String, forTypeIdentifier utidentifier: String) -> OSStatus { + @discardableResult static func setDefaultApp(identifier: String, forTypeIdentifier utidentifier: String) async -> OSStatus { if #available(macOS 12, *) { // print("running on macOS 12, using NSWorkspace") guard let utype = UTType(utidentifier) else { return 1 } - - let ws = NSWorkspace.shared - // since the new NSWorkspace function is asynchronous we have to use semaphores here - let semaphore = DispatchSemaphore(value: 0) - var errCode: OSStatus = 0 - - guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 } - ws.setDefaultApplication(at: appURL, toOpen: utype) { err in + do { + let ws = NSWorkspace.shared + guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 } + try await ws.setDefaultApplication(at: appURL, toOpen: utype) + return 0 + } catch { // err is an NSError wrapped in a CocoaError - if let err = err as? CocoaError { - if let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError { - errCode = OSStatus(clamping: underlyingError.code) - } + if let err = error as? CocoaError, let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError { + return OSStatus(clamping: underlyingError.code) + } else { + return 1 } - semaphore.signal() } - semaphore.wait() - return errCode } else { return LSSetDefaultRoleHandlerForContentType(utidentifier as CFString, .all, identifier as CFString) } diff --git a/Sources/utiluti/ManageCommand.swift b/Sources/utiluti/ManageCommand.swift index 13c1539..83de485 100644 --- a/Sources/utiluti/ManageCommand.swift +++ b/Sources/utiluti/ManageCommand.swift @@ -8,7 +8,7 @@ import Foundation import ArgumentParser -struct ManageCommand: ParsableCommand { +struct ManageCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "manage", abstract: "read and apply settings from a managed preferences or a file" @@ -47,7 +47,7 @@ struct ManageCommand: ParsableCommand { return prefs.dictionaryRepresentation(forKeys: Array(keys)) } - func manageTypes(types: [String:Any]) throws { + func manageTypes(types: [String:Any]) async throws { for (uti, value) in types { guard let bundleID = value as? String else { @@ -55,7 +55,7 @@ struct ManageCommand: ParsableCommand { continue } - let result = LSKit.setDefaultApp(identifier: bundleID, forTypeIdentifier: uti) + let result = await LSKit.setDefaultApp(identifier: bundleID, forTypeIdentifier: uti) if result == 0 { print("set \(bundleID) for \(uti)") } else { @@ -64,7 +64,7 @@ struct ManageCommand: ParsableCommand { } } - func manageURLs(urls: [String:Any]) throws { + func manageURLs(urls: [String:Any]) async throws { for (urlScheme, value) in urls { guard let bundleID = value as? String else { @@ -72,7 +72,7 @@ struct ManageCommand: ParsableCommand { continue } - let result = LSKit.setDefaultApp(identifier: bundleID, forScheme: urlScheme) + let result = await LSKit.setDefaultApp(identifier: bundleID, forScheme: urlScheme) if result == 0 { print("set \(bundleID) for \(urlScheme)") @@ -82,24 +82,24 @@ struct ManageCommand: ParsableCommand { } } - func run() throws { + func run() async throws { if typeFile == nil && urlFile == nil { // neither file path is set, read from defaults let types = try dictionary(fromDefaults: "com.scriptingosx.utiluti.type") - try manageTypes(types: types) + try await manageTypes(types: types) let urls = try dictionary(fromDefaults: "com.scriptingosx.utiluti.url") - try manageURLs(urls: urls) + try await manageURLs(urls: urls) } else { // one or both of the file paths are set if let typeFile { let types = try dictionary(forFile: typeFile) - try manageTypes(types: types) + try await manageTypes(types: types) } if let urlFile { let urls = try dictionary(forFile: urlFile) - try manageURLs(urls: urls) + try await manageURLs(urls: urls) } } } diff --git a/Sources/utiluti/TypeCommands.swift b/Sources/utiluti/TypeCommands.swift index 9552541..5dc1057 100644 --- a/Sources/utiluti/TypeCommands.swift +++ b/Sources/utiluti/TypeCommands.swift @@ -9,7 +9,7 @@ import Foundation import ArgumentParser import UniformTypeIdentifiers -struct TypeCommands: ParsableCommand { +struct TypeCommands: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "type", @@ -38,14 +38,14 @@ struct TypeCommands: ParsableCommand { var bundleID = false } - struct Get: ParsableCommand { + struct Get: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "Get the path to the default application.") @OptionGroup var utidentifier: UTIdentifier @OptionGroup var bundleID: IdentifierFlag - func run() { + func run() async { guard let appURL = LSKit.defaultAppURL(forTypeIdentifier: utidentifier.value) else { print("") return @@ -61,14 +61,14 @@ struct TypeCommands: ParsableCommand { } } - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "List all applications that can handle this type identifier.") @OptionGroup var utidentifier: UTIdentifier @OptionGroup var bundleID: IdentifierFlag - func run() { + func run() async { let appURLs = LSKit.appURLs(forTypeIdentifier: utidentifier.value) for appURL in appURLs { @@ -85,15 +85,15 @@ struct TypeCommands: ParsableCommand { } } - struct Set: ParsableCommand { + struct Set: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "Set the default app for this type identifier.") @OptionGroup var utidentifier: UTIdentifier @Argument var identifier: String - func run() { - let result = LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: utidentifier.value) + func run() async { + let result = await LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: utidentifier.value) if result == 0 { print("set \(identifier) for \(utidentifier.value)") @@ -104,13 +104,13 @@ struct TypeCommands: ParsableCommand { } } - struct FileExtensions: ParsableCommand { + struct FileExtensions: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "prints the file extensions for the given type identifier") @OptionGroup var utidentifier: UTIdentifier - func run() { + func run() async { guard let utype = UTType(utidentifier.value) else { print("") TypeCommands.exit(withError: ExitCode(3)) @@ -121,13 +121,13 @@ struct TypeCommands: ParsableCommand { } } - struct Info: ParsableCommand { + struct Info: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "prints information for the given type identifier") @OptionGroup var utidentifier: UTIdentifier - func run() { + func run() async { guard let utype = UTType(utidentifier.value) else { print("") TypeCommands.exit(withError: ExitCode(3)) diff --git a/Sources/utiluti/URLCommands.swift b/Sources/utiluti/URLCommands.swift index ed741f3..0783892 100644 --- a/Sources/utiluti/URLCommands.swift +++ b/Sources/utiluti/URLCommands.swift @@ -8,7 +8,7 @@ import Foundation import ArgumentParser -struct URLCommands: ParsableCommand { +struct URLCommands: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "url", abstract: "Manipulate default URL scheme handlers", @@ -37,14 +37,14 @@ struct URLCommands: ParsableCommand { var bundleID = false } - struct Get: ParsableCommand { + struct Get: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "Get the path to the default application.") @OptionGroup var scheme: URLScheme @OptionGroup var bundleID: IdentifierFlag - func run() { + func run() async { guard let appURL = LSKit.defaultAppURL(forScheme: scheme.value) else { print("") Self.exit(withError: ExitCode(1)) @@ -60,14 +60,14 @@ struct URLCommands: ParsableCommand { } } - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "List all applications that can handle this URL scheme.") @OptionGroup var scheme: URLScheme @OptionGroup var bundleID: IdentifierFlag - func run() { + func run() async { let appURLs = LSKit.appURLs(forScheme: scheme.value) for appURL in appURLs { @@ -84,7 +84,7 @@ struct URLCommands: ParsableCommand { } } - struct Set: ParsableCommand { + struct Set: AsyncParsableCommand { static let configuration = CommandConfiguration(abstract: "Set the default app for this URL scheme.") @@ -93,8 +93,8 @@ struct URLCommands: ParsableCommand { valueName: "bundleID")) var identifier: String - func run() { - let result = LSKit.setDefaultApp(identifier: identifier, forScheme: scheme.value) + func run() async { + let result = await LSKit.setDefaultApp(identifier: identifier, forScheme: scheme.value) if result == 0 { print("set \(identifier) for \(scheme.value)")