NetNewsWire/Frameworks/Account/OPMLFile.swift

154 lines
3.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OPMLFile.swift
// Account
//
// Created by Maurice Parker on 9/12/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import os.log
import RSCore
import RSParser
final class OPMLFile {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
private let fileURL: URL
private let account: Account
private lazy var managedFile = ManagedResourceFile(fileURL: fileURL, load: loadCallback, save: saveCallback)
init(filename: String, account: Account) {
self.fileURL = URL(fileURLWithPath: filename)
self.account = account
}
func markAsDirty() {
managedFile.markAsDirty()
}
func load() {
managedFile.load()
}
func save() {
managedFile.saveIfNecessary()
}
func suspend() {
managedFile.suspend()
}
func resume() {
managedFile.resume()
}
}
private extension OPMLFile {
func loadCallback() {
guard let fileData = opmlFileData() else {
return
}
// Don't rebuild the account if the OPML hasn't changed since the last save
guard let opml = String(data: fileData, encoding: .utf8), opml != opmlDocument() else {
return
}
guard let opmlItems = parsedOPMLItems(fileData: fileData) else { return }
BatchUpdate.shared.perform {
account.topLevelWebFeeds.removeAll()
account.loadOPMLItems(opmlItems, parentFolder: nil)
}
}
func saveCallback() {
guard !account.isDeleted else { return }
let opmlDocumentString = opmlDocument()
let errorPointer: NSErrorPointer = nil
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
fileCoordinator.coordinate(writingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { writeURL in
do {
try opmlDocumentString.write(to: writeURL, atomically: true, encoding: .utf8)
} catch let error as NSError {
os_log(.error, log: log, "OPML save to disk failed: %@.", error.localizedDescription)
}
})
if let error = errorPointer?.pointee {
os_log(.error, log: log, "OPML save to disk coordination failed: %@.", error.localizedDescription)
}
}
func opmlFileData() -> Data? {
var fileData: Data? = nil
let errorPointer: NSErrorPointer = nil
let fileCoordinator = NSFileCoordinator(filePresenter: managedFile)
fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in
do {
fileData = try Data(contentsOf: readURL)
} catch {
// Commented out because its not an error on first run.
// TODO: make it so we know if its first run or not.
//NSApplication.shared.presentError(error)
os_log(.error, log: log, "OPML read from disk failed: %@.", error.localizedDescription)
}
})
if let error = errorPointer?.pointee {
os_log(.error, log: log, "OPML read from disk coordination failed: %@.", error.localizedDescription)
}
return fileData
}
func parsedOPMLItems(fileData: Data) -> [RSOPMLItem]? {
let parserData = ParserData(url: fileURL.absoluteString, data: fileData)
var opmlDocument: RSOPMLDocument?
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
os_log(.error, log: log, "OPML Import failed: %@.", error.localizedDescription)
return nil
}
return opmlDocument?.children
}
func opmlDocument() -> String {
let escapedTitle = account.nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
let openingText =
"""
<?xml version="1.0" encoding="UTF-8"?>
<!-- OPML generated by NetNewsWire -->
<opml version="1.1">
<head>
<title>\(escapedTitle)</title>
</head>
<body>
"""
let middleText = account.OPMLString(indentLevel: 0, strictConformance: false)
let closingText =
"""
</body>
</opml>
"""
let opml = openingText + middleText + closingText
return opml
}
}