Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Sources/utiluti/AppCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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")

Expand All @@ -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),
Expand Down Expand Up @@ -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")

Expand All @@ -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),
Expand Down
14 changes: 7 additions & 7 deletions Sources/utiluti/FileCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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, *) {
Expand All @@ -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 ?? "<unknown>")
}
}

struct App: ParsableCommand {
struct App: AsyncParsableCommand {
static let configuration
= CommandConfiguration(abstract: "get the app that will open this file")

Expand All @@ -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 {
Expand All @@ -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")

Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions Sources/utiluti/GetUTI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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))
}
Expand Down
55 changes: 22 additions & 33 deletions Sources/utiluti/LSKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
20 changes: 10 additions & 10 deletions Sources/utiluti/ManageCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -47,15 +47,15 @@ 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 {
if verbose { print("skipping non-string value '\(value)' for \(uti)")}
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 {
Expand All @@ -64,15 +64,15 @@ 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 {
if verbose { print("skipping non-string value '\(value)' for \(urlScheme)")}
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)")
Expand All @@ -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)
}
}
}
Expand Down
24 changes: 12 additions & 12 deletions Sources/utiluti/TypeCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
import ArgumentParser
import UniformTypeIdentifiers

struct TypeCommands: ParsableCommand {
struct TypeCommands: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "type",
Expand Down Expand Up @@ -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("<no default app found>")
return
Expand All @@ -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 {
Expand All @@ -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)")
Expand All @@ -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("<none>")
TypeCommands.exit(withError: ExitCode(3))
Expand All @@ -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("<none>")
TypeCommands.exit(withError: ExitCode(3))
Expand Down
Loading