mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Change how we handle errors when working with Themes
This commit is contained in:
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user