// // 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 var isDirty = false { didSet { queueSaveToDiskIfNeeded() } } private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5) init(filename: String, account: Account) { self.fileURL = URL(fileURLWithPath: filename) self.account = account } func markAsDirty() { isDirty = true } func load() { guard let fileData = opmlFileData(), let opmlItems = parsedOPMLItems(fileData: fileData) else { return } BatchUpdate.shared.perform { account.loadOPMLItems(opmlItems, parentFolder: nil) } } func save() { guard !account.isDeleted else { return } let opmlDocumentString = opmlDocument() do { try opmlDocumentString.write(to: fileURL, atomically: true, encoding: .utf8) } catch let error as NSError { os_log(.error, log: log, "OPML save to disk failed: %@.", error.localizedDescription) } } } private extension OPMLFile { func queueSaveToDiskIfNeeded() { saveQueue.add(self, #selector(saveToDiskIfNeeded)) } @objc func saveToDiskIfNeeded() { if isDirty { isDirty = false save() } } func opmlFileData() -> Data? { var fileData: Data? = nil do { fileData = try Data(contentsOf: fileURL) } catch { os_log(.error, log: log, "OPML read from disk 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.escapingSpecialXMLCharacters let openingText = """ \(escapedTitle) """ let middleText = account.OPMLString(indentLevel: 0, allowCustomAttributes: true) let closingText = """ """ let opml = openingText + middleText + closingText return opml } }