You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
4.7 KiB
181 lines
4.7 KiB
//
|
|
// StoredCollection.swift
|
|
// LeStorage
|
|
//
|
|
// Created by Laurent Morvillier on 02/02/2024.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public protocol Storable : Codable, Identifiable where ID : Hashable {
|
|
static var resourceName: String { get }
|
|
}
|
|
|
|
public class StoredCollection<T : Storable> : RandomAccessCollection, ObservableObject {
|
|
|
|
let synchronized: Bool
|
|
|
|
@Published public fileprivate(set) var items: [T] = []
|
|
|
|
fileprivate var _serviceProvider: ServiceProvider
|
|
|
|
fileprivate var _hasChanged: Bool = false {
|
|
didSet {
|
|
if self._hasChanged == true {
|
|
self.objectWillChange.send()
|
|
self._scheduleWrite()
|
|
self._hasChanged = false
|
|
}
|
|
}
|
|
}
|
|
|
|
init(synchronized: Bool, serviceProvider: ServiceProvider) {
|
|
self.synchronized = synchronized
|
|
self._serviceProvider = serviceProvider
|
|
self._load()
|
|
}
|
|
|
|
fileprivate var _fileName: String {
|
|
return T.resourceName + ".json"
|
|
}
|
|
|
|
public func addOrUpdate(instance: T) {
|
|
defer {
|
|
self._hasChanged = true
|
|
}
|
|
|
|
if let index = self.items.firstIndex(where: { $0.id == instance.id }) {
|
|
self.items[index] = instance
|
|
self._sendUpdateIfNecessary(instance)
|
|
} else {
|
|
self.items.append(instance)
|
|
self._sendInsertionIfNecessary(instance)
|
|
}
|
|
|
|
}
|
|
|
|
public func delete(instance: T) {
|
|
defer {
|
|
self._hasChanged = true
|
|
}
|
|
|
|
self.items.removeAll { $0.id == instance.id }
|
|
self._sendDeletionIfNecessary(instance)
|
|
}
|
|
|
|
fileprivate func _scheduleWrite() {
|
|
self._write()
|
|
}
|
|
|
|
fileprivate func _write() {
|
|
DispatchQueue(label: "lestorage.queue.write", qos: .background).async {
|
|
do {
|
|
let jsonString = try self.items.jsonString()
|
|
let _ = try FileUtils.writeToDocumentDirectory(content: jsonString, fileName: self._fileName)
|
|
} catch {
|
|
Logger.error(error) // TODO how to notify the main project
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate func _load() {
|
|
|
|
do {
|
|
let url = try FileUtils.directoryURLForFileName(self._fileName)
|
|
if FileManager.default.fileExists(atPath: url.path()) {
|
|
self._loadAsync()
|
|
}
|
|
} catch {
|
|
Logger.log(error)
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate func _loadAsync() {
|
|
DispatchQueue(label: "lestorage.queue.read", qos: .background).async {
|
|
|
|
do {
|
|
let jsonString = try FileUtils.readDocumentFile(fileName: self._fileName)
|
|
if let decoded: [T] = try jsonString.decodeArray() {
|
|
DispatchQueue.main.sync {
|
|
Logger.log("loaded \(self._fileName) with \(decoded.count) items")
|
|
self.items = decoded
|
|
self.objectWillChange.send()
|
|
}
|
|
}
|
|
|
|
} catch {
|
|
Logger.error(error) // TODO how to notify the main project
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Synchronization
|
|
|
|
fileprivate func _sendInsertionIfNecessary(_ instance: T) {
|
|
guard self.synchronized else {
|
|
return
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let _ = try await self._serviceProvider.service?.insert(instance)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate func _sendUpdateIfNecessary(_ instance: T) {
|
|
guard self.synchronized else {
|
|
return
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let _ = try await self._serviceProvider.service?.insert(instance)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate func _sendDeletionIfNecessary(_ instance: T) {
|
|
guard self.synchronized else {
|
|
return
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let _ = try await self._serviceProvider.service?.delete(instance)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - RandomAccessCollection
|
|
|
|
public var startIndex: Int { return self.items.startIndex }
|
|
|
|
public var endIndex: Int { return self.items.endIndex }
|
|
|
|
public func index(after i: Int) -> Int {
|
|
return self.items.index(after: i)
|
|
}
|
|
|
|
open subscript(index: Int) -> T {
|
|
get {
|
|
return self.items[index]
|
|
}
|
|
set(newValue) {
|
|
self.items[index] = newValue
|
|
self._hasChanged = true
|
|
}
|
|
}
|
|
|
|
}
|
|
|