NetNewsWire/Frameworks/Account/OPMLFile.swift

138 lines
3.4 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()
}
}
private extension OPMLFile {
func loadCallback() {
guard let opmlItems = parsedOPMLItems() else { return }
BatchUpdate.shared.perform {
account.topLevelFeeds.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 parsedOPMLItems() -> [RSOPMLItem]? {
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)
}
guard let opmlData = fileData else {
return nil
}
let parserData = ParserData(url: fileURL.absoluteString, data: opmlData)
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
}
}