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:
@@ -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.") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)")) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user