From b0b5ad018c819d0575ee856514d24bd86eee5afb Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 31 Dec 2024 21:15:51 -0800 Subject: [PATCH] =?UTF-8?q?Fix=20theme-importing=20=E2=80=94=C2=A0use=20st?= =?UTF-8?q?artAccessingSecurityScopedResource=20and=20stopAccessingSecurit?= =?UTF-8?q?yScopedResource,=20which=20appear=20to=20be=20required=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Shared/ArticleStyles/ArticleTheme.swift | 44 +++++++++++-------- .../ArticleStyles/ArticleThemesManager.swift | 10 ++--- iOS/SceneCoordinator.swift | 2 +- iOS/Settings/ArticleThemeImporter.swift | 22 ++++++---- .../ArticleThemesTableViewController.swift | 17 ++++--- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/Shared/ArticleStyles/ArticleTheme.swift b/Shared/ArticleStyles/ArticleTheme.swift index 302c45a53..1d9eeea9b 100644 --- a/Shared/ArticleStyles/ArticleTheme.swift +++ b/Shared/ArticleStyles/ArticleTheme.swift @@ -16,14 +16,14 @@ struct ArticleTheme: Equatable { private static let defaultThemeName = NSLocalizedString("Default", comment: "Default") private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value") - let path: String? + let url: URL? let template: String? let css: String? let isAppTheme: Bool var name: String { - guard let path = path else { return Self.defaultThemeName } - return Self.themeNameForPath(path) + guard let url else { return Self.defaultThemeName } + return Self.themeNameForPath(url.path) } var creatorHomePage: String { @@ -41,7 +41,7 @@ struct ArticleTheme: Equatable { private let info: ArticleThemePlist? init() { - self.path = nil; + self.url = nil self.info = ArticleThemePlist(name: "Article Theme", themeIdentifier: "com.ranchero.netnewswire.theme.article", creatorHomePage: "https://netnewswire.com/", creatorName: "Ranchero Software", version: 1) let corePath = Bundle.main.path(forResource: "core", ofType: "css")! @@ -54,27 +54,33 @@ struct ArticleTheme: Equatable { isAppTheme = true } - init(path: String, isAppTheme: Bool) throws { - self.path = path + init(url: URL, isAppTheme: Bool) throws { + + _ = url.startAccessingSecurityScopedResource() + defer { + url.stopAccessingSecurityScopedResource() + } - let infoPath = (path as NSString).appendingPathComponent("Info.plist") - let data = try Data(contentsOf: URL(fileURLWithPath: infoPath)) - self.info = try PropertyListDecoder().decode(ArticleThemePlist.self, from: data) - - let corePath = Bundle.main.path(forResource: "core", ofType: "css")! - let stylesheetPath = (path as NSString).appendingPathComponent("stylesheet.css") - if let stylesheetCSS = Self.stringAtPath(stylesheetPath) { - self.css = Self.stringAtPath(corePath)! + "\n" + stylesheetCSS + self.url = url + + let coreURL = Bundle.main.url(forResource: "core", withExtension: "css")! + let styleSheetURL = url.appendingPathComponent("stylesheet.css") + if let stylesheetCSS = Self.stringAtPath(styleSheetURL.path) { + self.css = Self.stringAtPath(coreURL.path)! + "\n" + stylesheetCSS } else { self.css = nil } - - let templatePath = (path as NSString).appendingPathComponent("template.html") - self.template = Self.stringAtPath(templatePath) - + + let templateURL = url.appendingPathComponent("template.html") + self.template = Self.stringAtPath(templateURL.path) + self.isAppTheme = isAppTheme + + let infoURL = url.appendingPathComponent("Info.plist") + let data = try Data(contentsOf: infoURL) + self.info = try PropertyListDecoder().decode(ArticleThemePlist.self, from: data) } - + static func stringAtPath(_ f: String) -> String? { if !FileManager.default.fileExists(atPath: f) { return nil diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift index ff0365e9f..3075235d0 100644 --- a/Shared/ArticleStyles/ArticleThemesManager.swift +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -97,20 +97,20 @@ final class ArticleThemesManager: NSObject, NSFilePresenter { return ArticleTheme.defaultTheme } - let path: String + let url: URL let isAppTheme: Bool - if let appThemePath = Bundle.main.url(forResource: themeName, withExtension: ArticleTheme.nnwThemeSuffix)?.path { - path = appThemePath + if let appThemeURL = Bundle.main.url(forResource: themeName, withExtension: ArticleTheme.nnwThemeSuffix) { + url = appThemeURL isAppTheme = true } else if let installedPath = pathForThemeName(themeName, folder: folderPath) { - path = installedPath + url = URL(fileURLWithPath: installedPath) isAppTheme = false } else { return nil } do { - return try ArticleTheme(path: path, isAppTheme: isAppTheme) + return try ArticleTheme(url: url, isAppTheme: isAppTheme) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) return nil diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index a071288bf..fb8b71b70 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1251,7 +1251,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func importTheme(filename: String) { do { - try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename) + try ArticleThemeImporter.importTheme(controller: rootSplitViewController, url: URL(fileURLWithPath: filename)) } catch { NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error]) } diff --git a/iOS/Settings/ArticleThemeImporter.swift b/iOS/Settings/ArticleThemeImporter.swift index fc2494da1..b1f554ae3 100644 --- a/iOS/Settings/ArticleThemeImporter.swift +++ b/iOS/Settings/ArticleThemeImporter.swift @@ -10,9 +10,9 @@ import UIKit struct ArticleThemeImporter { - static func importTheme(controller: UIViewController, filename: String) throws { - let theme = try ArticleTheme(path: filename, isAppTheme: false) - + static func importTheme(controller: UIViewController, url: URL) throws { + let theme = try ArticleTheme(url: url, isAppTheme: false) + let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text") let title = NSString.localizedStringWithFormat(localizedTitleText as NSString, theme.name, theme.creatorName) as String @@ -24,18 +24,24 @@ struct ArticleThemeImporter { let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel)) - if let url = URL(string: theme.creatorHomePage) { + if let websiteURL = URL(string: theme.creatorHomePage) { 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) + UIApplication.shared.open(websiteURL) + try? Self.importTheme(controller: controller, url: url) } alertController.addAction(visitSiteAction) } func importTheme() { + + _ = url.startAccessingSecurityScopedResource() + defer { + url.stopAccessingSecurityScopedResource() + } + do { - try ArticleThemesManager.shared.importTheme(filename: filename) + try ArticleThemesManager.shared.importTheme(filename: url.path) confirmImportSuccess(controller: controller, themeName: theme.name) } catch { controller.presentError(error) @@ -45,7 +51,7 @@ struct ArticleThemeImporter { let installThemeTitle = NSLocalizedString("Install Theme", comment: "Install Theme") let installThemeAction = UIAlertAction(title: installThemeTitle, style: .default) { action in - if ArticleThemesManager.shared.themeExists(filename: filename) { + if ArticleThemesManager.shared.themeExists(filename: url.path) { let title = NSLocalizedString("Duplicate Theme", comment: "Duplicate Theme") let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme") let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String diff --git a/iOS/Settings/ArticleThemesTableViewController.swift b/iOS/Settings/ArticleThemesTableViewController.swift index 2143ab86d..77b9110da 100644 --- a/iOS/Settings/ArticleThemesTableViewController.swift +++ b/iOS/Settings/ArticleThemesTableViewController.swift @@ -117,11 +117,18 @@ 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]) + + if url.startAccessingSecurityScopedResource() { + + defer { + url.stopAccessingSecurityScopedResource() + } + + do { + try ArticleThemeImporter.importTheme(controller: self, url: url) + } catch { + NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error]) + } } } - }