Skip to content

Commit a86ee36

Browse files
committed
Don't use semaphores for blocking
Methods with completion handlers, like the ones used on NSWorkspace, get imported as async methods to Swift Concurrency. This requires updating command definitions to be async.
1 parent 8468045 commit a86ee36

File tree

7 files changed

+66
-77
lines changed

7 files changed

+66
-77
lines changed

Sources/utiluti/AppCommands.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ArgumentParser
1010
import UniformTypeIdentifiers
1111
import AppKit // for NSWorkspace
1212

13-
struct AppCommands: ParsableCommand {
13+
struct AppCommands: AsyncParsableCommand {
1414
static let configuration = CommandConfiguration(
1515
commandName: "app",
1616
abstract: "list uniform types identifiers and url schemes associated with an app",
@@ -20,7 +20,7 @@ struct AppCommands: ParsableCommand {
2020
]
2121
)
2222

23-
struct Types: ParsableCommand {
23+
struct Types: AsyncParsableCommand {
2424
static let configuration
2525
= CommandConfiguration(abstract: "List the uniform type identifiers this app can open")
2626

@@ -31,7 +31,7 @@ struct AppCommands: ParsableCommand {
3131
help: "show more information")
3232
var verbose: Bool = false
3333

34-
func run() {
34+
func run() async {
3535
guard
3636
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appID),
3737
let appBundle = Bundle(url: appURL),
@@ -82,7 +82,7 @@ struct AppCommands: ParsableCommand {
8282
}
8383
}
8484

85-
struct Schemes: ParsableCommand {
85+
struct Schemes: AsyncParsableCommand {
8686
static let configuration
8787
= CommandConfiguration(abstract: "List the urls schemes this app can open")
8888

@@ -93,7 +93,7 @@ struct AppCommands: ParsableCommand {
9393
help: "show more information")
9494
var verbose: Bool = false
9595

96-
func run() {
96+
func run() async {
9797
guard
9898
let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appID),
9999
let appBundle = Bundle(url: appURL),

Sources/utiluti/FileCommands.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ArgumentParser
1212
import UniformTypeIdentifiers
1313
import AppKit // for NSWorkspace
1414

15-
struct FileCommands: ParsableCommand {
15+
struct FileCommands: AsyncParsableCommand {
1616

1717
static var subCommands: [ParsableCommand.Type] {
1818
if #available(macOS 12.0, *) {
@@ -28,21 +28,21 @@ struct FileCommands: ParsableCommand {
2828
subcommands: subCommands
2929
)
3030

31-
struct GetUTI: ParsableCommand {
31+
struct GetUTI: AsyncParsableCommand {
3232
static let configuration
3333
= CommandConfiguration(abstract: "get the uniform type identifier of a file")
3434

3535
@Argument(help:ArgumentHelp("file path", valueName: "path"))
3636
var path: String
3737

38-
func run() {
38+
func run() async {
3939
let url = URL(fileURLWithPath: path)
4040
let typeIdentifier = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier
4141
print(typeIdentifier ?? "<unknown>")
4242
}
4343
}
4444

45-
struct App: ParsableCommand {
45+
struct App: AsyncParsableCommand {
4646
static let configuration
4747
= CommandConfiguration(abstract: "get the app that will open this file")
4848

@@ -54,7 +54,7 @@ struct FileCommands: ParsableCommand {
5454
valueName: "bundleID"))
5555
var bundleID = false
5656

57-
func run() {
57+
func run() async {
5858
let url = URL(fileURLWithPath: path)
5959
if let app = NSWorkspace.shared.urlForApplication(toOpen: url) {
6060
if bundleID {
@@ -73,7 +73,7 @@ struct FileCommands: ParsableCommand {
7373
}
7474

7575
@available(macOS 12, *)
76-
struct ListApps: ParsableCommand {
76+
struct ListApps: AsyncParsableCommand {
7777
static let configuration
7878
= CommandConfiguration(abstract: "get all app that can open this file")
7979

@@ -85,7 +85,7 @@ struct FileCommands: ParsableCommand {
8585
valueName: "bundleID"))
8686
var bundleID = false
8787

88-
func run() {
88+
func run() async {
8989
let url = URL(fileURLWithPath: path)
9090
let apps = NSWorkspace.shared.urlsForApplications(toOpen: url)
9191
for app in apps {

Sources/utiluti/GetUTI.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99
import ArgumentParser
1010
import UniformTypeIdentifiers
1111

12-
struct GetUTI: ParsableCommand {
12+
struct GetUTI: AsyncParsableCommand {
1313
static let configuration
1414
= CommandConfiguration(abstract: "Get the type identifier (UTI) for a file extension")
1515

@@ -19,7 +19,7 @@ struct GetUTI: ParsableCommand {
1919
@Flag(help: "show dynamic identifiers")
2020
var showDynamic = false
2121

22-
func run() {
22+
func run() async {
2323
guard let utype = UTType(filenameExtension: fileExtension) else {
2424
Self.exit(withError: ExitCode(3))
2525
}

Sources/utiluti/LSKit.swift

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,21 @@ struct LSKit {
6565
- scheme: url scheme (excluding the `:` or `/`, e.g. `http`)
6666
- Returns: OSStatus (discardable)
6767
*/
68-
@discardableResult static func setDefaultApp(identifier: String, forScheme scheme: String) -> OSStatus {
68+
@discardableResult static func setDefaultApp(identifier: String, forScheme scheme: String) async -> OSStatus {
6969
if #available(macOS 12, *) {
7070
// print("running on macOS 12, using NSWorkspace")
71-
let ws = NSWorkspace.shared
72-
73-
// since the new NSWorkspace function is asynchronous we have to use semaphores here
74-
let semaphore = DispatchSemaphore(value: 0)
75-
var errCode: OSStatus = 0
76-
77-
guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 }
78-
ws.setDefaultApplication(at: appURL, toOpenURLsWithScheme: scheme) { err in
79-
// err is an NSError wrapped in a CocoaError
80-
if let err = err as? CocoaError {
81-
if let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError {
82-
errCode = OSStatus(clamping: underlyingError.code)
83-
}
71+
do {
72+
let ws = NSWorkspace.shared
73+
guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 }
74+
try await ws.setDefaultApplication(at: appURL, toOpenURLsWithScheme: scheme)
75+
return 0
76+
} catch {
77+
if let err = error as? CocoaError, let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError {
78+
return OSStatus(clamping: underlyingError.code)
79+
} else {
80+
return 1
8481
}
85-
semaphore.signal()
8682
}
87-
semaphore.wait()
88-
return errCode
8983
} else {
9084
return LSSetDefaultHandlerForURLScheme(scheme as CFString, identifier as CFString)
9185
}
@@ -147,31 +141,26 @@ struct LSKit {
147141
- forTypeIdentifier: uniform type identifier ( e.g. `public.html`)
148142
- Returns: OSStatus (discardable)
149143
*/
150-
@discardableResult static func setDefaultApp(identifier: String, forTypeIdentifier utidentifier: String) -> OSStatus {
144+
@discardableResult static func setDefaultApp(identifier: String, forTypeIdentifier utidentifier: String) async -> OSStatus {
151145
if #available(macOS 12, *) {
152146
// print("running on macOS 12, using NSWorkspace")
153147
guard let utype = UTType(utidentifier) else {
154148
return 1
155149
}
156-
157-
let ws = NSWorkspace.shared
158150

159-
// since the new NSWorkspace function is asynchronous we have to use semaphores here
160-
let semaphore = DispatchSemaphore(value: 0)
161-
var errCode: OSStatus = 0
162-
163-
guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 }
164-
ws.setDefaultApplication(at: appURL, toOpen: utype) { err in
151+
do {
152+
let ws = NSWorkspace.shared
153+
guard let appURL = ws.urlForApplication(withBundleIdentifier: identifier) else { return 1 }
154+
try await ws.setDefaultApplication(at: appURL, toOpen: utype)
155+
return 0
156+
} catch {
165157
// err is an NSError wrapped in a CocoaError
166-
if let err = err as? CocoaError {
167-
if let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError {
168-
errCode = OSStatus(clamping: underlyingError.code)
169-
}
158+
if let err = error as? CocoaError, let underlyingError = err.errorUserInfo["NSUnderlyingError"] as? NSError {
159+
return OSStatus(clamping: underlyingError.code)
160+
} else {
161+
return 1
170162
}
171-
semaphore.signal()
172163
}
173-
semaphore.wait()
174-
return errCode
175164
} else {
176165
return LSSetDefaultRoleHandlerForContentType(utidentifier as CFString, .all, identifier as CFString)
177166
}

Sources/utiluti/ManageCommand.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99
import ArgumentParser
1010

11-
struct ManageCommand: ParsableCommand {
11+
struct ManageCommand: AsyncParsableCommand {
1212
static let configuration = CommandConfiguration(
1313
commandName: "manage",
1414
abstract: "read and apply settings from a managed preferences or a file"
@@ -47,15 +47,15 @@ struct ManageCommand: ParsableCommand {
4747
return prefs.dictionaryRepresentation(forKeys: Array(keys))
4848
}
4949

50-
func manageTypes(types: [String:Any]) throws {
50+
func manageTypes(types: [String:Any]) async throws {
5151
for (uti, value) in types {
5252
guard let bundleID = value as? String
5353
else {
5454
if verbose { print("skipping non-string value '\(value)' for \(uti)")}
5555
continue
5656
}
5757

58-
let result = LSKit.setDefaultApp(identifier: bundleID, forTypeIdentifier: uti)
58+
let result = await LSKit.setDefaultApp(identifier: bundleID, forTypeIdentifier: uti)
5959
if result == 0 {
6060
print("set \(bundleID) for \(uti)")
6161
} else {
@@ -64,15 +64,15 @@ struct ManageCommand: ParsableCommand {
6464
}
6565
}
6666

67-
func manageURLs(urls: [String:Any]) throws {
67+
func manageURLs(urls: [String:Any]) async throws {
6868
for (urlScheme, value) in urls {
6969
guard let bundleID = value as? String
7070
else {
7171
if verbose { print("skipping non-string value '\(value)' for \(urlScheme)")}
7272
continue
7373
}
7474

75-
let result = LSKit.setDefaultApp(identifier: bundleID, forScheme: urlScheme)
75+
let result = await LSKit.setDefaultApp(identifier: bundleID, forScheme: urlScheme)
7676

7777
if result == 0 {
7878
print("set \(bundleID) for \(urlScheme)")
@@ -82,24 +82,24 @@ struct ManageCommand: ParsableCommand {
8282
}
8383
}
8484

85-
func run() throws {
85+
func run() async throws {
8686
if typeFile == nil && urlFile == nil {
8787
// neither file path is set, read from defaults
8888
let types = try dictionary(fromDefaults: "com.scriptingosx.utiluti.type")
89-
try manageTypes(types: types)
89+
try await manageTypes(types: types)
9090

9191
let urls = try dictionary(fromDefaults: "com.scriptingosx.utiluti.url")
92-
try manageURLs(urls: urls)
92+
try await manageURLs(urls: urls)
9393
} else {
9494
// one or both of the file paths are set
9595
if let typeFile {
9696
let types = try dictionary(forFile: typeFile)
97-
try manageTypes(types: types)
97+
try await manageTypes(types: types)
9898
}
9999

100100
if let urlFile {
101101
let urls = try dictionary(forFile: urlFile)
102-
try manageURLs(urls: urls)
102+
try await manageURLs(urls: urls)
103103
}
104104
}
105105
}

Sources/utiluti/TypeCommands.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99
import ArgumentParser
1010
import UniformTypeIdentifiers
1111

12-
struct TypeCommands: ParsableCommand {
12+
struct TypeCommands: AsyncParsableCommand {
1313

1414
static let configuration = CommandConfiguration(
1515
commandName: "type",
@@ -38,14 +38,14 @@ struct TypeCommands: ParsableCommand {
3838
var bundleID = false
3939
}
4040

41-
struct Get: ParsableCommand {
41+
struct Get: AsyncParsableCommand {
4242
static let configuration
4343
= CommandConfiguration(abstract: "Get the path to the default application.")
4444

4545
@OptionGroup var utidentifier: UTIdentifier
4646
@OptionGroup var bundleID: IdentifierFlag
4747

48-
func run() {
48+
func run() async {
4949
guard let appURL = LSKit.defaultAppURL(forTypeIdentifier: utidentifier.value) else {
5050
print("<no default app found>")
5151
return
@@ -61,14 +61,14 @@ struct TypeCommands: ParsableCommand {
6161
}
6262
}
6363

64-
struct List: ParsableCommand {
64+
struct List: AsyncParsableCommand {
6565
static let configuration
6666
= CommandConfiguration(abstract: "List all applications that can handle this type identifier.")
6767

6868
@OptionGroup var utidentifier: UTIdentifier
6969
@OptionGroup var bundleID: IdentifierFlag
7070

71-
func run() {
71+
func run() async {
7272
let appURLs = LSKit.appURLs(forTypeIdentifier: utidentifier.value)
7373

7474
for appURL in appURLs {
@@ -85,15 +85,15 @@ struct TypeCommands: ParsableCommand {
8585
}
8686
}
8787

88-
struct Set: ParsableCommand {
88+
struct Set: AsyncParsableCommand {
8989
static let configuration
9090
= CommandConfiguration(abstract: "Set the default app for this type identifier.")
9191

9292
@OptionGroup var utidentifier: UTIdentifier
9393
@Argument var identifier: String
9494

95-
func run() {
96-
let result = LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: utidentifier.value)
95+
func run() async {
96+
let result = await LSKit.setDefaultApp(identifier: identifier, forTypeIdentifier: utidentifier.value)
9797

9898
if result == 0 {
9999
print("set \(identifier) for \(utidentifier.value)")
@@ -104,13 +104,13 @@ struct TypeCommands: ParsableCommand {
104104
}
105105
}
106106

107-
struct FileExtensions: ParsableCommand {
107+
struct FileExtensions: AsyncParsableCommand {
108108
static let configuration
109109
= CommandConfiguration(abstract: "prints the file extensions for the given type identifier")
110110

111111
@OptionGroup var utidentifier: UTIdentifier
112112

113-
func run() {
113+
func run() async {
114114
guard let utype = UTType(utidentifier.value) else {
115115
print("<none>")
116116
TypeCommands.exit(withError: ExitCode(3))
@@ -121,13 +121,13 @@ struct TypeCommands: ParsableCommand {
121121
}
122122
}
123123

124-
struct Info: ParsableCommand {
124+
struct Info: AsyncParsableCommand {
125125
static let configuration
126126
= CommandConfiguration(abstract: "prints information for the given type identifier")
127127

128128
@OptionGroup var utidentifier: UTIdentifier
129129

130-
func run() {
130+
func run() async {
131131
guard let utype = UTType(utidentifier.value) else {
132132
print("<none>")
133133
TypeCommands.exit(withError: ExitCode(3))

0 commit comments

Comments
 (0)