Better error management + refactoring

- Implement a function to determine at runtime whether we’re running in CLI or as a prefpane.
- More verbose errors in both cases.
- Try to account for ThisAppDoesNothing appearing unknown to LaunchServices by manually trying to launch it from one of several locations it might be found. (Should fix #9 and #12)
- Fold all errors and success messages into a single displayAlert() function used both in CLI and GUI versions.
This commit is contained in:
Gregorio Litenstein
2019-06-17 00:51:04 -04:00
parent 224d24e260
commit 3a09834f5a
8 changed files with 127 additions and 65 deletions
+1 -1
View File
@@ -22,6 +22,6 @@ class GetApps: Command {
if let output = copyStringArrayAsString(LSWrappers.copyAllApps()) {
print(output)
}
else { throw CLIError.error("There was an error generating the list of applications.") }
else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate the list of installed applications.") }
}
}
+1 -1
View File
@@ -102,6 +102,6 @@ class ReadCommand: OptionCommand {
if (nil != handler) {
print(handler!)
} else { throw CLIError.error(("An incompatible combination was used, or no application is registered to handle \(arg)")) }
} else { throw CLIError.error(("SwiftDefaultApps ERROR: An incompatible combination was used, or no application is registered to handle \(arg)")) }
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ class GetUTIs: Command {
if let output = copyDictionaryAsString(LSWrappers.UTType.copyAllUTIs().sorted(by: { $0.0 < $1.0 })) {
print(output)
}
else { throw CLIError.error("There was an error generating the list.") }
else { throw CLIError.error("SwiftDefaultApps ERROR: Couldn't generate list of UTIs") }
}
}
+3 -2
View File
@@ -83,7 +83,8 @@ class SetCommand: OptionCommand {
statusCode = kLSUnknownErr
break
}
if (statusCode == 0) { print("Default handler has succesfully changed to \(bundleID!).") }
else { throw CLIError.error(LSWrappers.LSErrors.init(value: statusCode).print(argument: (app: inApplication, content: self.contentType!))) }
do {
try displayAlert(error: statusCode, arg1: (bundleID != nil ? bundleID : inApplication), arg2: self.contentType!)
} catch { print(error) }
}
}
+28 -8
View File
@@ -323,14 +323,34 @@ class LSWrappers {
- Returns: `true` if the bundle identifier is registered with Launch Services as an application, `false` otherwise.
*/
static func isAppInstalled (withBundleID: String) -> Bool {
let temp = withBundleID as CFString
if (LSCopyApplicationURLsForBundleIdentifier(temp,nil)?.takeRetainedValue() as NSArray?) != nil {
return true
}
else {
return false
}
if (withBundleID == "cl.fail.lordkamina.ThisAppDoesNothing" && NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: "cl.fail.lordkamina.ThisAppDoesNothing") == nil) {
if (!areWeCLI() && prefPaneLocation() == nil) { return false }
let appSearchPaths: [URL] = [ areWeCLI() ? Bundle.main.bundleURL : Bundle(url: prefPaneLocation()!)!.resourceURL! ] +
FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationDirectory, in: FileManager.SearchPathDomainMask.allDomainsMask)
var appPath: URL?
for path in appSearchPaths {
let appURL = path.appendingPathComponent("ThisAppDoesNothing.app").absoluteURL
if (FileManager.default.isExecutableFile(atPath: appURL.path) == true) {
appPath = appURL
break
}
}
guard appPath != nil else {
return false
}
do {
let ranApp = try NSWorkspace.shared.launchApplication(at: appPath!, options: [NSWorkspace.LaunchOptions.withoutAddingToRecents], configuration: [:])
if (ranApp.processIdentifier == -1) {
return false
}
else { return true }
}
catch {
return false
}
}
let retval: Bool = ((LSCopyApplicationURLsForBundleIdentifier(withBundleID as CFString,nil)?.takeRetainedValue() as NSArray?) != nil)
return retval
}
/**
Performs a myriad of sanity checks on user input corresponding to a possible application. The main purpose of this function is to make sure we're passing a value as sane as possible to the setHandler functions.
+71
View File
@@ -80,6 +80,8 @@ prefix func /(pattern:String) -> NSRegularExpression? {
//// EXTENSIONS
extension String: Error {}
extension DispatchQueue {
static let labelPrefix = "io.zamzam.ZamzamKit"
static let database = DispatchQueue(label: "\(DispatchQueue.labelPrefix).database", qos: .utility)
@@ -232,6 +234,75 @@ extension Dictionary
}
#endif
/** Determines whether the app is running in Prefpane or CLI form by checking the Main Bundle Identifier. */
func areWeCLI() -> Bool {
return (Bundle.main.bundleIdentifier == nil)
}
/** Determines the URL for the location of the SwiftDefaultApps prefpane, is any. */
func prefPaneLocation() -> URL? {
if let bundle = Bundle(identifier:"cl.fail.lordkamina.SwiftDefaultApps") {
return bundle.bundleURL
}
let prefpaneSearchPaths: [URL] = FileManager.default.urls(for: FileManager.SearchPathDirectory.preferencePanesDirectory, in: FileManager.SearchPathDomainMask.allDomainsMask)
for path in prefpaneSearchPaths {
if (FileManager.default.isExecutableFile(atPath: path.appendingPathComponent("SwiftDefaultApps.prefpane").absoluteURL.path) == true) {
return path.appendingPathComponent("SwiftDefaultApps.prefpane").absoluteURL
}
}
return nil
}
/**
Display a modal alert
- Parameter error: A numeric value indicating the error code as per LSWrappers.LSErrors.
- Parameter arg1: String containing info pertaining to the error.
- Parameter arg2: String containing info pertaining to the error.
*/
func displayAlert(error: OSStatus = Int32.min, arg1: String?, arg2: String?) throws {
if (error != 0 && (arg1 == nil || arg2 == nil)) { throw ("Arguments cannot be nil when displaying an error.") }
if (!areWeCLI()) {
let alert = NSAlert()
alert.icon = NSWorkspace.shared.icon(forFile: Bundle(identifier: "cl.fail.lordkamina.SwiftDefaultApps")!.bundlePath)
alert.addButton(withTitle: "OK")
if (error != Int32.min) {
alert.informativeText = LSWrappers.LSErrors(value: error).print(argument: (app: arg1, content: arg2))
switch (Int(error)) {
case Int(errSecInvalidBundleInfo)..<0:
alert.messageText = "Error"
alert.alertStyle = .critical
case 0:
alert.messageText = "Success"
alert.alertStyle = .informational
case 1...:alert.messageText = "Success"
alert.alertStyle = .informational
default:
alert.messageText = "Warning"
alert.alertStyle = .warning
}
}
else {
alert.informativeText = "SwiftDefaultApps ERROR: Called displayAlert() with an undefined error code."
}
DispatchQueue.main.async {
alert.runModal()
}
}
else {
if (error < 0) {
throw(LSWrappers.LSErrors.init(value: error).print(argument: (app: arg1, content: arg2)))
}
else if (error == 0) {
print(LSWrappers.LSErrors.init(value: error).print(argument: (app: arg1, content: arg2)))
}
else if (error == Int32.min) {
guard (arg1 != nil) else { throw("SwiftDefaultApps ERROR: Called displayAlert() with an undefined error code and message.") }
print(arg1!)
}
}
}
/** NSFont extensions to style the different kinds of row in the NSTreeView. */
extension NSFont {
/** Returns a SmallCaps version of the font it is invoked on. */
@@ -105,26 +105,17 @@ internal class SWDATreeRow:NSObject {
let type = content.contentType
var status = OSStatus()
if let bundleID = self.rowContent?.application?.appBundleID {
status = (type == .URL) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!))
let alert = NSAlert()
alert.informativeText = (status == 0) ? "Succesfully changed default handler for \(self.rowTitle) to \(self.rowContent?.application?.displayName ?? "Invalid App")" : LSWrappers.LSErrors(value: status).print(argument: (app: (self.rowContent?.application?.displayName)!, content: self.rowTitle))
alert.icon = ControllersRef.appIcon
alert.messageText = (status == 0) ? "Success" : "Error"
alert.alertStyle = (status == 0) ? .informational : .critical
alert.addButton(withTitle: "OK")
DispatchQueue.main.async {
if let parent = self.parentNode {
for node in parent.children {
node.willChangeValue(forKey: "isDefaultHandler")
node.didChangeValue(forKey: "isDefaultHandler")
}
}
DispatchQueue.main.async {
alert.runModal()
}
}
}
if let bundleID = self.rowContent?.application?.appBundleID {
status = (type == .URI) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!))
try! displayAlert(error: status, arg1: (self.rowContent?.application?.displayName), arg2: self.rowTitle)
if let parent = self.parentNode {
for node in parent.children {
node.willChangeValue(forKey: "isDefaultHandler")
node.didChangeValue(forKey: "isDefaultHandler")
}
}
}
}
}
}
@@ -149,27 +140,16 @@ internal class SWDATreeRow:NSObject {
let contentName = content.contentName
let type = content.contentType
var status = OSStatus()
if let bundleID = self.rowContent?.application?.appBundleID {
status = (type == .URL) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!))
let alert = NSAlert()
alert.informativeText = (status == 0) ? "Succesfully changed default handler for \(content.displayName) to \(self.rowTitle)" : LSWrappers.LSErrors(value: status).print(argument: (app: self.rowTitle, content: contentName))
alert.icon = ControllersRef.appIcon
alert.messageText = (status == 0) ? "Success" : "Error"
alert.alertStyle = (status == 0) ? .informational : .critical
alert.addButton(withTitle: "OK")
DispatchQueue.main.async {
if let parent = self.parentNode {
for node in parent.children {
node.willChangeValue(forKey: "isDefaultHandler")
node.didChangeValue(forKey: "isDefaultHandler")
}
}
DispatchQueue.main.async {
alert.runModal()
}
}
}
if let bundleID = self.rowContent?.application?.appBundleID {
status = (type == .URI) ? LSWrappers.Schemes.setDefaultHandler(contentName, bundleID) : LSWrappers.UTType.setDefaultHandler(contentName, bundleID, LSRolesMask(from:self.roleMask!))
try! displayAlert(error: status, arg1: (self.rowContent?.application?.displayName), arg2: self.rowTitle)
if let parent = self.parentNode {
for node in parent.children {
node.willChangeValue(forKey: "isDefaultHandler")
node.didChangeValue(forKey: "isDefaultHandler")
}
}
}
else if (self.rowTitle == "Other...") {
let openpanel = NSOpenPanel()
openpanel.treatsFilePackagesAsDirectories = false
@@ -111,19 +111,9 @@ class SWDATabTemplate: DRYView {
SWDAHandlersModel.setValue(nil, forKey: "allSchemes")
self.setValue(nil, forKey: "contentArrayStore")
}
else {
let alert = NSAlert()
alert.informativeText = LSWrappers.LSErrors(value: result).print(argument: (app: "Do Nothing", content: customNewScheme!.stringValue))
alert.messageText = "Error"
alert.icon = ControllersRef.appIcon
alert.alertStyle = .critical
alert.addButton(withTitle: "OK")
DispatchQueue.main.async {
alert.runModal()
}
try! displayAlert(error: result, arg1: "Do Nothing", arg2: customNewScheme!.stringValue)
return
}
}
/** Identifies which tab the current instance belongs to. */
var tabIndex: Int? = -1