mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Fix theme-importing — use startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource, which appear to be required now.
This commit is contained in:
@@ -16,14 +16,14 @@ struct ArticleTheme: Equatable {
|
|||||||
private static let defaultThemeName = NSLocalizedString("Default", comment: "Default")
|
private static let defaultThemeName = NSLocalizedString("Default", comment: "Default")
|
||||||
private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value")
|
private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value")
|
||||||
|
|
||||||
let path: String?
|
let url: URL?
|
||||||
let template: String?
|
let template: String?
|
||||||
let css: String?
|
let css: String?
|
||||||
let isAppTheme: Bool
|
let isAppTheme: Bool
|
||||||
|
|
||||||
var name: String {
|
var name: String {
|
||||||
guard let path = path else { return Self.defaultThemeName }
|
guard let url else { return Self.defaultThemeName }
|
||||||
return Self.themeNameForPath(path)
|
return Self.themeNameForPath(url.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
var creatorHomePage: String {
|
var creatorHomePage: String {
|
||||||
@@ -41,7 +41,7 @@ struct ArticleTheme: Equatable {
|
|||||||
private let info: ArticleThemePlist?
|
private let info: ArticleThemePlist?
|
||||||
|
|
||||||
init() {
|
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)
|
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")!
|
let corePath = Bundle.main.path(forResource: "core", ofType: "css")!
|
||||||
@@ -54,27 +54,33 @@ struct ArticleTheme: Equatable {
|
|||||||
isAppTheme = true
|
isAppTheme = true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(path: String, isAppTheme: Bool) throws {
|
init(url: URL, isAppTheme: Bool) throws {
|
||||||
self.path = path
|
|
||||||
|
_ = url.startAccessingSecurityScopedResource()
|
||||||
|
defer {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
let infoPath = (path as NSString).appendingPathComponent("Info.plist")
|
self.url = url
|
||||||
let data = try Data(contentsOf: URL(fileURLWithPath: infoPath))
|
|
||||||
self.info = try PropertyListDecoder().decode(ArticleThemePlist.self, from: data)
|
let coreURL = Bundle.main.url(forResource: "core", withExtension: "css")!
|
||||||
|
let styleSheetURL = url.appendingPathComponent("stylesheet.css")
|
||||||
let corePath = Bundle.main.path(forResource: "core", ofType: "css")!
|
if let stylesheetCSS = Self.stringAtPath(styleSheetURL.path) {
|
||||||
let stylesheetPath = (path as NSString).appendingPathComponent("stylesheet.css")
|
self.css = Self.stringAtPath(coreURL.path)! + "\n" + stylesheetCSS
|
||||||
if let stylesheetCSS = Self.stringAtPath(stylesheetPath) {
|
|
||||||
self.css = Self.stringAtPath(corePath)! + "\n" + stylesheetCSS
|
|
||||||
} else {
|
} else {
|
||||||
self.css = nil
|
self.css = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let templatePath = (path as NSString).appendingPathComponent("template.html")
|
let templateURL = url.appendingPathComponent("template.html")
|
||||||
self.template = Self.stringAtPath(templatePath)
|
self.template = Self.stringAtPath(templateURL.path)
|
||||||
|
|
||||||
self.isAppTheme = isAppTheme
|
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? {
|
static func stringAtPath(_ f: String) -> String? {
|
||||||
if !FileManager.default.fileExists(atPath: f) {
|
if !FileManager.default.fileExists(atPath: f) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -97,20 +97,20 @@ final class ArticleThemesManager: NSObject, NSFilePresenter {
|
|||||||
return ArticleTheme.defaultTheme
|
return ArticleTheme.defaultTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
let path: String
|
let url: URL
|
||||||
let isAppTheme: Bool
|
let isAppTheme: Bool
|
||||||
if let appThemePath = Bundle.main.url(forResource: themeName, withExtension: ArticleTheme.nnwThemeSuffix)?.path {
|
if let appThemeURL = Bundle.main.url(forResource: themeName, withExtension: ArticleTheme.nnwThemeSuffix) {
|
||||||
path = appThemePath
|
url = appThemeURL
|
||||||
isAppTheme = true
|
isAppTheme = true
|
||||||
} else if let installedPath = pathForThemeName(themeName, folder: folderPath) {
|
} else if let installedPath = pathForThemeName(themeName, folder: folderPath) {
|
||||||
path = installedPath
|
url = URL(fileURLWithPath: installedPath)
|
||||||
isAppTheme = false
|
isAppTheme = false
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
return try ArticleTheme(path: path, isAppTheme: isAppTheme)
|
return try ArticleTheme(url: url, isAppTheme: isAppTheme)
|
||||||
} catch {
|
} catch {
|
||||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1251,7 +1251,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
|
|
||||||
func importTheme(filename: String) {
|
func importTheme(filename: String) {
|
||||||
do {
|
do {
|
||||||
try ArticleThemeImporter.importTheme(controller: rootSplitViewController, filename: filename)
|
try ArticleThemeImporter.importTheme(controller: rootSplitViewController, url: URL(fileURLWithPath: filename))
|
||||||
} catch {
|
} catch {
|
||||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error])
|
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import UIKit
|
|||||||
|
|
||||||
struct ArticleThemeImporter {
|
struct ArticleThemeImporter {
|
||||||
|
|
||||||
static func importTheme(controller: UIViewController, filename: String) throws {
|
static func importTheme(controller: UIViewController, url: URL) throws {
|
||||||
let theme = try ArticleTheme(path: filename, isAppTheme: false)
|
let theme = try ArticleTheme(url: url, isAppTheme: false)
|
||||||
|
|
||||||
let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
|
let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
|
||||||
let title = NSString.localizedStringWithFormat(localizedTitleText as NSString, theme.name, theme.creatorName) as String
|
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")
|
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
|
||||||
alertController.addAction(UIAlertAction(title: cancelTitle, style: .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 visitSiteTitle = NSLocalizedString("Show Website", comment: "Show Website")
|
||||||
let visitSiteAction = UIAlertAction(title: visitSiteTitle, style: .default) { action in
|
let visitSiteAction = UIAlertAction(title: visitSiteTitle, style: .default) { action in
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(websiteURL)
|
||||||
try? Self.importTheme(controller: controller, filename: filename)
|
try? Self.importTheme(controller: controller, url: url)
|
||||||
}
|
}
|
||||||
alertController.addAction(visitSiteAction)
|
alertController.addAction(visitSiteAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func importTheme() {
|
func importTheme() {
|
||||||
|
|
||||||
|
_ = url.startAccessingSecurityScopedResource()
|
||||||
|
defer {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try ArticleThemesManager.shared.importTheme(filename: filename)
|
try ArticleThemesManager.shared.importTheme(filename: url.path)
|
||||||
confirmImportSuccess(controller: controller, themeName: theme.name)
|
confirmImportSuccess(controller: controller, themeName: theme.name)
|
||||||
} catch {
|
} catch {
|
||||||
controller.presentError(error)
|
controller.presentError(error)
|
||||||
@@ -45,7 +51,7 @@ struct ArticleThemeImporter {
|
|||||||
let installThemeTitle = NSLocalizedString("Install Theme", comment: "Install Theme")
|
let installThemeTitle = NSLocalizedString("Install Theme", comment: "Install Theme")
|
||||||
let installThemeAction = UIAlertAction(title: installThemeTitle, style: .default) { action in
|
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 title = NSLocalizedString("Duplicate Theme", comment: "Duplicate Theme")
|
||||||
let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
|
let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
|
||||||
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
|
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
|
||||||
|
|||||||
@@ -117,11 +117,18 @@ extension ArticleThemesTableViewController: UIDocumentPickerDelegate {
|
|||||||
|
|
||||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||||
guard let url = urls.first else { return }
|
guard let url = urls.first else { return }
|
||||||
do {
|
|
||||||
try ArticleThemeImporter.importTheme(controller: self, filename: url.standardizedFileURL.path)
|
if url.startAccessingSecurityScopedResource() {
|
||||||
} catch {
|
|
||||||
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
defer {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try ArticleThemeImporter.importTheme(controller: self, url: url)
|
||||||
|
} catch {
|
||||||
|
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user