Change how we handle errors when working with Themes

This commit is contained in:
Maurice Parker
2022-11-08 01:36:28 -06:00
parent 97d740c41c
commit b7b9344e3d
9 changed files with 95 additions and 116 deletions

View File

@@ -110,8 +110,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
private var crashReporter: PLCrashReporter!
#endif
private var themeImportPath: String?
override init() {
NSWindow.allowsAutomaticWindowTabbing = false
super.init()
@@ -130,7 +128,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(inspectableObjectsDidChange(_:)), name: .InspectableObjectsDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(importDownloadedTheme(_:)), name: .didEndDownloadingTheme, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(themeImportError(_:)), name: .didFailToImportThemeWithError, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil)
appDelegate = self
@@ -348,7 +345,51 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
while !isShutDownSyncDone && RunLoop.current.run(mode: .default, before: timeout) && timeout > Date() { }
}
func presentThemeImportError(_ error: Error) {
var informativeText: String = ""
if let decodingError = error as? DecodingError {
switch decodingError {
case .typeMismatch(let type, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist", comment: "Type mismatch")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, type as! CVarArg) as String
case .valueNotFound(let value, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the value—“%@”—is not found in the Info.plist.", comment: "Decoding value missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, value as! CVarArg) as String
case .keyNotFound(let codingKey, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the key—“%@”—is not found in the Info.plist.", comment: "Decoding key missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, codingKey.stringValue) as String
case .dataCorrupted(let context):
guard let underlyingError = context.underlyingError as NSError?,
let debugDescription = underlyingError.userInfo["NSDebugDescription"] as? String else {
informativeText = error.localizedDescription
break
}
let localizedError = NSLocalizedString("This theme cannot be used because of data corruption in the Info.plist: %@.", comment: "Decoding key missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, debugDescription) as String
default:
informativeText = error.localizedDescription
}
} else {
informativeText = error.localizedDescription
}
DispatchQueue.main.async {
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = NSLocalizedString("Theme Error", comment: "Theme error")
alert.informativeText = informativeText
alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK"))
alert.buttons[0].keyEquivalent = "\r"
let response = alert.runModal()
}
}
// MARK: Notifications
@objc func unreadCountDidChange(_ note: Notification) {
if note.object is AccountManager {
unreadCount = AccountManager.shared.unreadCount
@@ -949,8 +990,7 @@ internal extension AppDelegate {
}
}
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error, "path": filename])
logger.error("Error importing theme: \(error.localizedDescription, privacy: .public)")
presentThemeImportError(error)
}
}
@@ -969,67 +1009,6 @@ internal extension AppDelegate {
alert.beginSheetModal(for: window)
}
@objc func themeImportError(_ note: Notification) {
guard let userInfo = note.userInfo,
let error = userInfo["error"] as? Error else {
return
}
themeImportPath = userInfo["path"] as? String
var informativeText: String = ""
if let decodingError = error as? DecodingError {
switch decodingError {
case .typeMismatch(let type, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist", comment: "Type mismatch")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, type as! CVarArg) as String
case .valueNotFound(let value, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the value—“%@”—is not found in the Info.plist.", comment: "Decoding value missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, value as! CVarArg) as String
case .keyNotFound(let codingKey, _):
let localizedError = NSLocalizedString("This theme cannot be used because the the key—“%@”—is not found in the Info.plist.", comment: "Decoding key missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, codingKey.stringValue) as String
case .dataCorrupted(let context):
guard let underlyingError = context.underlyingError as NSError?,
let debugDescription = underlyingError.userInfo["NSDebugDescription"] as? String else {
informativeText = error.localizedDescription
break
}
let localizedError = NSLocalizedString("This theme cannot be used because of data corruption in the Info.plist: %@.", comment: "Decoding key missing")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, debugDescription) as String
default:
informativeText = error.localizedDescription
}
} else {
informativeText = error.localizedDescription
}
DispatchQueue.main.async {
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = NSLocalizedString("Theme Error", comment: "Theme download error")
alert.informativeText = informativeText
alert.addButton(withTitle: NSLocalizedString("Open Theme Folder", comment: "Open Theme Folder"))
alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK"))
let button = alert.buttons.first
button?.target = self
button?.action = #selector(self.openThemesFolder(_:))
alert.buttons[0].keyEquivalent = "\033"
alert.buttons[1].keyEquivalent = "\r"
alert.runModal()
}
}
@objc func openThemesFolder(_ sender: Any) {
if themeImportPath == nil {
let url = URL(fileURLWithPath: ArticleThemesManager.shared.folderPath)
NSWorkspace.shared.open(url)
} else {
let url = URL(fileURLWithPath: themeImportPath!)
NSWorkspace.shared.open(url.deletingLastPathComponent())
}
}
}
/*

View File

@@ -56,15 +56,14 @@ extension AppDelegate : AppDelegateAppleEvents {
if let themeURL = URL(string: themeURLString) {
let request = URLRequest(url: themeURL)
let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in
guard let location = location else {
guard let self, let location else {
return
}
do {
try ArticleThemeDownloader.shared.handleFile(at: location)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
self?.logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)")
self.presentThemeImportError(error)
}
}
task.resume()

View File

@@ -11,5 +11,4 @@ import Foundation
extension Notification.Name {
static let didBeginDownloadingTheme = Notification.Name("didBeginDownloadingTheme")
static let didEndDownloadingTheme = Notification.Name("didEndDownloadingTheme")
static let didFailToImportThemeWithError = Notification.Name("didFailToImportThemeWithError")
}

View File

@@ -30,13 +30,24 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging {
}
set {
if newValue != currentThemeName {
AppDefaults.shared.currentThemeName = newValue
currentTheme = articleThemeWithThemeName(newValue)
do {
currentTheme = try articleThemeWithThemeName(newValue)
AppDefaults.shared.currentThemeName = newValue
} catch {
logger.error("Unable to set new theme: \(error.localizedDescription, privacy: .public)")
}
}
}
}
lazy var currentTheme = { articleThemeWithThemeName(currentThemeName) }() {
lazy var currentTheme = {
do {
return try articleThemeWithThemeName(currentThemeName)
} catch {
logger.error("Unable to load theme \(self.currentThemeName): \(error.localizedDescription, privacy: .public)")
return ArticleTheme.defaultTheme
}
}() {
didSet {
NotificationCenter.default.post(name: .CurrentArticleThemeDidChangeNotification, object: self)
}
@@ -66,7 +77,11 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging {
func presentedSubitemDidChange(at url: URL) {
themeNames = buildThemeNames()
currentTheme = articleThemeWithThemeName(currentThemeName)
do {
currentTheme = try articleThemeWithThemeName(currentThemeName)
} catch {
appDelegate.presentThemeImportError(error)
}
}
// MARK: API
@@ -88,7 +103,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging {
try FileManager.default.copyItem(atPath: filename, toPath: toFilename)
}
func articleThemeWithThemeName(_ themeName: String) -> ArticleTheme {
func articleThemeWithThemeName(_ themeName: String) throws -> ArticleTheme {
if themeName == AppDefaults.defaultThemeName {
return ArticleTheme.defaultTheme
}
@@ -105,14 +120,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging {
return ArticleTheme.defaultTheme
}
do {
return try ArticleTheme(path: path, isAppTheme: isAppTheme)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
logger.error("Failed to import theme: \(error.localizedDescription, privacy: .public)")
return ArticleTheme.defaultTheme
}
return try ArticleTheme(path: path, isAppTheme: isAppTheme)
}
func deleteTheme(themeName: String) {

View File

@@ -219,6 +219,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
func presentThemeImportError(_ error: Error) {
let windowScene = {
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
return scenes.filter { $0.activationState == .foregroundActive }.first ?? scenes.first
}()
guard let sceneDelegate = windowScene?.delegate as? SceneDelegate else { return }
sceneDelegate.presentError(error)
}
}
// MARK: App Initialization

View File

@@ -326,7 +326,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(importDownloadedTheme(_:)), name: .didEndDownloadingTheme, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(themeDownloadDidFail(_:)), name: .didFailToImportThemeWithError, object: nil)
}
func restoreWindowState(_ activity: NSUserActivity?) {
@@ -543,16 +542,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
}
}
@objc func themeDownloadDidFail(_ note: Notification) {
guard let userInfo = note.userInfo,
let error = userInfo["error"] as? Error else {
return
}
DispatchQueue.main.async {
self.rootSplitViewController.presentError(error, dismiss: nil)
}
}
// MARK: API
func suspend() {
@@ -1275,13 +1264,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
}
func importTheme(filename: String) {
do {
try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error])
logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)")
}
ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename)
}
/// This will dismiss the foremost view controller if the user

View File

@@ -96,6 +96,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging {
coordinator.cleanUp(conditional: conditional)
}
func presentError(_ error: Error) {
self.window!.rootViewController?.presentError(error)
}
// Handle Opening of URLs
func scene(_ scene: UIScene, openURLContexts urlContexts: Set<UIOpenURLContext>) {
@@ -186,14 +190,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging {
NotificationCenter.default.post(name: .didBeginDownloadingTheme, object: nil)
}
let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in
guard
let location = location else { return }
guard let self, let location else { return }
do {
try ArticleThemeDownloader.shared.handleFile(at: location)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
self?.logger.error("Failed to import theme with error: \(error.localizedDescription, privacy: .public)")
self.presentError(error)
}
}
task.resume()
@@ -205,7 +207,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, Logging {
return
}
}
}
}

View File

@@ -11,8 +11,14 @@ import RSCore
struct ArticleThemeImporter: Logging {
static func importTheme(controller: UIViewController, filename: String) throws {
let theme = try ArticleTheme(path: filename, isAppTheme: false)
static func importTheme(controller: UIViewController, filename: String) {
let theme: ArticleTheme
do {
theme = try ArticleTheme(path: filename, isAppTheme: false)
} catch {
controller.presentError(error)
return
}
let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
let title = NSString.localizedStringWithFormat(localizedTitleText as NSString, theme.name, theme.creatorName) as String
@@ -29,7 +35,7 @@ struct ArticleThemeImporter: Logging {
let visitSiteTitle = NSLocalizedString("Show Website", comment: "Show Website")
let visitSiteAction = UIAlertAction(title: visitSiteTitle, style: .default) { action in
UIApplication.shared.open(url)
try? Self.importTheme(controller: controller, filename: filename)
Self.importTheme(controller: controller, filename: filename)
}
alertController.addAction(visitSiteAction)
}

View File

@@ -74,7 +74,7 @@ class ArticleThemesTableViewController: UITableViewController, Logging {
guard let cell = tableView.cellForRow(at: indexPath),
let themeName = cell.textLabel?.text else { return nil }
guard !ArticleThemesManager.shared.articleThemeWithThemeName(themeName).isAppTheme else { return nil }
guard let theme = try? ArticleThemesManager.shared.articleThemeWithThemeName(themeName), !theme.isAppTheme else { return nil }
let deleteTitle = NSLocalizedString("Delete", comment: "Delete")
let deleteAction = UIContextualAction(style: .normal, title: deleteTitle) { [weak self] (action, view, completion) in
@@ -114,12 +114,7 @@ extension ArticleThemesTableViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
do {
try ArticleThemeImporter.importTheme(controller: self, filename: url.standardizedFileURL.path)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
logger.error("Did fail to import theme: \(error.localizedDescription, privacy: .public)")
}
try ArticleThemeImporter.importTheme(controller: self, filename: url.standardizedFileURL.path)
}
}