Create and use DataFile class. Start getting away from CoalescingQueue, since it’s very objc.

This commit is contained in:
Brent Simmons 2024-06-09 13:09:31 -07:00
parent e74c81518e
commit 9c23b1351d
3 changed files with 131 additions and 32 deletions

View File

@ -18,21 +18,19 @@ import Core
private let fileURL: URL
private let account: Account
private var isDirty = false {
didSet {
queueSaveToDiskIfNeeded()
}
}
private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
private let dataFile: DataFile
init(filename: String, account: Account) {
self.fileURL = URL(fileURLWithPath: filename)
self.account = account
self.fileURL = URL(fileURLWithPath: filename)
self.dataFile = DataFile(fileURL: self.fileURL)
self.dataFile.delegate = self
}
func markAsDirty() {
isDirty = true
dataFile.markAsDirty()
}
func load() {
@ -44,33 +42,15 @@ import Core
account.loadOPMLItems(opmlItems)
}
}
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)
}
dataFile.save()
}
}
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
@ -122,5 +102,29 @@ private extension OPMLFile {
let opml = openingText + middleText + closingText
return opml
}
}
extension OPMLFile: DataFileDelegate {
func data(for dataFile: DataFile) -> Data? {
guard !account.isDeleted else {
return nil
}
let opmlDocumentString = opmlDocument()
guard let data = opmlDocumentString.data(using: .utf8, allowLossyConversion: true) else {
assertionFailure("OPML String conversion to Data failed.")
os_log(.error, log: log, "OPML String conversion to Data failed.")
return nil
}
return data
}
func dataFileWriteToDiskDidFail(for dataFile: DataFile, error: Error) {
os_log(.error, log: log, "OPML save to disk failed: %@.", error.localizedDescription)
}
}

View File

@ -0,0 +1,96 @@
//
// DataFile.swift
//
//
// Created by Brent Simmons on 6/9/24.
//
import Foundation
import os
public protocol DataFileDelegate: AnyObject {
@MainActor func data(for dataFile: DataFile) -> Data?
@MainActor func dataFileWriteToDiskDidFail(for dataFile: DataFile, error: Error)
}
@MainActor public final class DataFile {
public weak var delegate: DataFileDelegate? = nil
private var isDirty = false {
didSet {
if isDirty {
restartTimer()
}
else {
invalidateTimer()
}
}
}
private let fileURL: URL
private let saveInterval: TimeInterval = 1.0
private var timer: Timer?
public init(fileURL: URL) {
self.fileURL = fileURL
}
public func markAsDirty() {
isDirty = true
}
public func save() {
assert(Thread.isMainThread)
isDirty = false
guard let data = delegate?.data(for: self) else {
return
}
do {
try data.write(to: fileURL)
} catch {
delegate?.dataFileWriteToDiskDidFail(for: self, error: error)
}
}
}
private extension DataFile {
func saveToDiskIfNeeded() {
assert(Thread.isMainThread)
if isDirty {
save()
}
}
func restartTimer() {
assert(Thread.isMainThread)
invalidateTimer()
timer = Timer.scheduledTimer(withTimeInterval: saveInterval, repeats: false) { timer in
MainActor.assumeIsolated {
self.saveToDiskIfNeeded()
}
}
}
func invalidateTimer() {
assert(Thread.isMainThread)
if let timer, timer.isValid {
timer.invalidate()
}
timer = nil
}
}

View File

@ -15,7 +15,6 @@ let package = Package(
.target(
name: "Web",
dependencies: [],
resources: [.copy("UTS46/uts46")],
swiftSettings: [
.define("SWIFT_PACKAGE"),
.enableExperimentalFeature("StrictConcurrency")