NetNewsWire/Shared/ArticleStyles/ArticleThemesManager.swift

192 lines
5.5 KiB
Swift

//
// ArticleThemesManager.sqift
// NetNewsWire
//
// Created by Brent Simmons on 9/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
public extension Notification.Name {
static let ArticleThemeNamesDidChangeNotification = Notification.Name("ArticleThemeNamesDidChangeNotification")
static let CurrentArticleThemeDidChangeNotification = Notification.Name("CurrentArticleThemeDidChangeNotification")
}
final class ArticleThemesManager: NSObject, NSFilePresenter {
static var shared: ArticleThemesManager!
public let folderPath: String
lazy var presentedItemOperationQueue = OperationQueue.main
var presentedItemURL: URL? {
return URL(fileURLWithPath: folderPath)
}
var currentThemeName: String {
get {
return AppDefaults.shared.currentThemeName ?? AppDefaults.defaultThemeName
}
set {
if newValue != currentThemeName {
AppDefaults.shared.currentThemeName = newValue
updateCurrentTheme()
}
}
}
var currentTheme: ArticleTheme {
didSet {
NotificationCenter.default.post(name: .CurrentArticleThemeDidChangeNotification, object: self)
}
}
var themeNames = [AppDefaults.defaultThemeName] {
didSet {
NotificationCenter.default.post(name: .ArticleThemeNamesDidChangeNotification, object: self)
}
}
init(folderPath: String) {
self.folderPath = folderPath
self.currentTheme = ArticleTheme.defaultTheme
super.init()
do {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
} catch {
assertionFailure("Could not create folder for Themes.")
abort()
}
let themeFilenames = Bundle.main.paths(forResourcesOfType: ArticleTheme.nnwThemeSuffix, inDirectory: nil)
let installedThemes = readInstalledThemes() ?? [String: Date]()
for themeFilename in themeFilenames {
let themeName = ArticleTheme.themeNameForPath(themeFilename)
if !installedThemes.keys.contains(themeName) {
try? importTheme(filename: themeFilename)
}
}
updateThemeNames()
updateCurrentTheme()
NSFileCoordinator.addFilePresenter(self)
}
func presentedSubitemDidChange(at url: URL) {
updateThemeNames()
updateCurrentTheme()
}
// MARK: API
func themeExists(filename: String) -> Bool {
let filenameLastPathComponent = (filename as NSString).lastPathComponent
let toFilename = (folderPath as NSString).appendingPathComponent(filenameLastPathComponent)
return FileManager.default.fileExists(atPath: toFilename)
}
func importTheme(filename: String) throws {
let filenameLastPathComponent = (filename as NSString).lastPathComponent
let toFilename = (folderPath as NSString).appendingPathComponent(filenameLastPathComponent)
if FileManager.default.fileExists(atPath: toFilename) {
try FileManager.default.removeItem(atPath: toFilename)
}
try FileManager.default.copyItem(atPath: filename, toPath: toFilename)
let themeName = ArticleTheme.themeNameForPath(filename)
var installedThemes = readInstalledThemes() ?? [String: Date]()
installedThemes[themeName] = Date()
writeInstalledThemes(installedThemes)
}
func deleteTheme(themeName: String) {
if let filename = pathForThemeName(themeName, folder: folderPath) {
try? FileManager.default.removeItem(atPath: filename)
}
}
}
// MARK : Private
private extension ArticleThemesManager {
func updateThemeNames() {
let updatedThemeNames = allThemePaths(folderPath).map { ArticleTheme.themeNameForPath($0) }
let sortedThemeNames = updatedThemeNames.sorted(by: { $0.compare($1, options: .caseInsensitive) == .orderedAscending })
if sortedThemeNames != themeNames {
themeNames = sortedThemeNames
}
}
func articleThemeWithThemeName(_ themeName: String) -> ArticleTheme? {
if themeName == AppDefaults.defaultThemeName {
return ArticleTheme.defaultTheme
}
guard let path = pathForThemeName(themeName, folder: folderPath) else {
return nil
}
do {
return try ArticleTheme(path: path)
} catch {
NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
return nil
}
}
func defaultArticleTheme() -> ArticleTheme {
return articleThemeWithThemeName(AppDefaults.defaultThemeName)!
}
func updateCurrentTheme() {
var themeName = currentThemeName
if !themeNames.contains(themeName) {
themeName = AppDefaults.defaultThemeName
currentThemeName = AppDefaults.defaultThemeName
}
var articleTheme = articleThemeWithThemeName(themeName)
if articleTheme == nil {
articleTheme = defaultArticleTheme()
currentThemeName = AppDefaults.defaultThemeName
}
if let articleTheme = articleTheme, articleTheme != currentTheme {
currentTheme = articleTheme
}
}
func allThemePaths(_ folder: String) -> [String] {
let filepaths = FileManager.default.filePaths(inFolder: folder)
return filepaths?.filter { $0.hasSuffix(ArticleTheme.nnwThemeSuffix) } ?? []
}
func pathForThemeName(_ themeName: String, folder: String) -> String? {
for onePath in allThemePaths(folder) {
if ArticleTheme.pathIsPathForThemeName(themeName, path: onePath) {
return onePath
}
}
return nil
}
func readInstalledThemes() -> [String: Date]? {
let filePath = (folderPath as NSString).appendingPathComponent("InstalledThemes.plist")
return NSDictionary(contentsOfFile: filePath) as? [String: Date]
}
func writeInstalledThemes(_ dict: [String: Date]) {
let filePath = (folderPath as NSString).appendingPathComponent("InstalledThemes.plist")
(dict as NSDictionary).write(toFile: filePath, atomically: true)
}
}