Compare commits

..

2 Commits

  1. 11
      LeStorage/Codables/Log.swift
  2. 2
      LeStorage/Storable.swift
  3. 55
      LeStorage/Store.swift
  4. 12
      LeStorage/StoreCenter.swift
  5. 40
      LeStorage/StoredCollection.swift
  6. 82
      LeStorage/SyncedCollection.swift

@ -23,13 +23,13 @@ class Log: SyncedModelObject, SyncedStorable {
}
var id: String = Store.randomId()
var date: Date = Date()
var user: String? = nil
var message: String = ""
init(message: String) {
init(message: String, user: String?) {
self.message = message
self.user = user
super.init()
}
@ -38,6 +38,7 @@ class Log: SyncedModelObject, SyncedStorable {
enum CodingKeys: String, CodingKey {
case id
case date
case user
case message
}
@ -45,6 +46,7 @@ class Log: SyncedModelObject, SyncedStorable {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
date = try container.decode(Date.self, forKey: .date)
user = try container.decodeIfPresent(String.self, forKey: .user)
message = try container.decode(String.self, forKey: .message)
try super.init(from: decoder)
}
@ -53,16 +55,17 @@ class Log: SyncedModelObject, SyncedStorable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(date, forKey: .date)
try container.encodeIfPresent(user, forKey: .user)
try container.encode(message, forKey: .message)
try super.encode(to: encoder)
}
func copy(from other: any Storable) {
guard let log = other as? Log else { return }
self.date = log.date
self.message = log.message
}
public func copyForUpdate(from other: any Storable) {
fatalError("should not happen")
}

@ -37,6 +37,8 @@ public protocol Storable: Codable, Identifiable, NSObjectProtocol {
static func parentRelationships() -> [Relationship]
static func childrenRelationships() -> [Relationship]
/// Denotes a data that own its own store
/// Effectively used to trigger directory creation when adding an item to the collection
static func storeParent() -> Bool
}

@ -142,13 +142,13 @@ final public class Store {
/// - Parameters:
/// - indexed: Creates an index to quickly access the data
/// - inMemory: Indicates if the collection should only live in memory, and not write into a file
public func registerSynchronizedCollection<T : SyncedStorable>(indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false) -> SyncedCollection<T> {
public func registerSynchronizedCollection<T : SyncedStorable>(indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) -> SyncedCollection<T> {
if let collection: SyncedCollection<T> = try? self.syncedCollection() {
return collection
}
let collection = SyncedCollection<T>(store: self, indexed: indexed, inMemory: inMemory, limit: limit, synchronousLoading: synchronousLoading, noLoad: noLoad)
let collection = SyncedCollection<T>(store: self, indexed: indexed, inMemory: inMemory, limit: limit, noLoad: noLoad)
self._collections[T.resourceName()] = collection
self._baseCollections[T.resourceName()] = collection.collection
@ -225,14 +225,6 @@ final public class Store {
throw StoreError.collectionNotRegistered(type: T.resourceName())
}
/// Returns a collection by type
// func collection<T: Storable>() throws -> BaseCollection<T> {
// if let collection = self._collections[T.resourceName()] as? BaseCollection<T> {
// return collection
// }
// throw StoreError.collectionNotRegistered(type: T.resourceName())
// }
func registerOrGetSyncedCollection<T: SyncedStorable>(_ type: T.Type) -> SyncedCollection<T> {
do {
return try self.syncedCollection()
@ -282,7 +274,7 @@ final public class Store {
// MARK: - Synchronization
fileprivate func _requestWrite<T: SyncedStorable>(type: T.Type) {
self._baseCollections[T.resourceName()]?.requestWrite()
self._baseCollections[T.resourceName()]?.requestWriteIfNecessary()
}
@MainActor
@ -331,16 +323,6 @@ final public class Store {
}
self._requestWrite(type: T.self)
// for identifier in identifiers {
// do {
// try self.deleteNoSyncNoCascade(type: type, id: identifier.modelId)
// } catch {
// Logger.error(error)
// }
// self.storeCenter.cleanupDataLog(dataId: identifier.modelId)
// }
// self._requestWrite(type: T.self)
}
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type) -> T? {
@ -348,31 +330,12 @@ final public class Store {
return self.findById(realId)
}
/// Calls deleteById from the collection corresponding to the instance
// func deleteNoSync<T: Storable>(instance: T) {
// do {
// let collection: BaseCollection<T> = try self.collection()
// collection.delete(instance: instance)
// } catch {
// Logger.error(error)
// }
// }
/// Calls deleteById from the collection corresponding to the instance
func deleteNoSyncNoCascadeNoWrite<T: SyncedStorable>(type: T.Type, id: String) throws {
let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
}
/// Calls deleteById from the collection corresponding to the instance
// func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int {
// var count: Int = 0
// for collection in self._collections.values {
// count += collection.referenceCount(type: type, id: id)
// }
// return count
// }
func isReferenced<T: Storable, S: Storable>(collectionType: S.Type, type: T.Type, id: String) -> Bool {
if let collection = self._baseCollections[S.resourceName()] {
return collection.hasParentReferences(type: type, id: id)
@ -462,18 +425,6 @@ final public class Store {
}
}
// public func deleteDependencies<T: Storable>(type: T.Type, actionOption: ActionOption, _ isIncluded: (T) -> Bool) {
//
// do {
// let collection: any SomeCollection = try self.someCollection(type: type)
// if let syncCollection = collection as? SyncedCollection<T> {
// collection.deleteDependencies(actionOption: actionOption, isIncluded)
// } catch {
// Logger.error(error)
// }
//
// }
// MARK: - Write
/// Returns the directory URL of the store

@ -1197,21 +1197,11 @@ public class StoreCenter {
/// Logs a message in the logs collection
public func log(message: String) {
DispatchQueue.main.async {
let log = Log(message: message)
let log = Log(message: message, user: self.userId)
self._logsCollection().addOrUpdate(instance: log)
}
}
// MARK: - Migration
/// Migrates the token from the provided service to the main Services instance
// public func migrateToken(_ services: Services) throws {
// guard let userName = self.userName() else {
// return
// }
// try self.service().migrateToken(services, userName: userName)
// }
deinit {
NotificationCenter.default.removeObserver(self)
}

@ -25,7 +25,7 @@ public protocol SomeCollection<Item>: Identifiable {
func deleteDependencies(actionOption: ActionOption, _ isIncluded: (Item) -> Bool)
func findById(_ id: Item.ID) -> Item?
func requestWrite()
func requestWriteIfNecessary()
}
protocol CollectionDelegate<Item> {
@ -81,7 +81,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Indicates whether the collection has changed, thus requiring a write operation
fileprivate var _triggerWrite: Bool = false {
didSet {
if self._triggerWrite == true && self.inMemory == false {
if self._triggerWrite == true {
self._scheduleWrite()
self._triggerWrite = false
}
@ -100,8 +100,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Sets a max number of items inside the collection
fileprivate(set) var limit: Int? = nil
fileprivate var _delegate: (any CollectionDelegate<T>)? = nil
init(store: Store, inMemory: Bool = false) async {
self.store = store
if self.inMemory == false {
@ -109,24 +107,19 @@ public class StoredCollection<T: Storable>: SomeCollection {
}
}
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false, delegate: (any CollectionDelegate<T>)? = nil) {
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) {
if indexed {
self._indexes = [:]
}
self.inMemory = inMemory
self.store = store
self.limit = limit
self._delegate = delegate
if noLoad {
self.hasLoaded = true
} else {
Task {
if synchronousLoading {
await self.loadFromFile()
} else {
await self.load()
}
await self.load()
}
}
@ -150,8 +143,10 @@ public class StoredCollection<T: Storable>: SomeCollection {
// MARK: - Loading
/// Sets the collection as changed to trigger a write
public func requestWrite() {
self._triggerWrite = true
public func requestWriteIfNecessary() {
if self.inMemory == false {
self._triggerWrite = true
}
}
/// Migrates if necessary and asynchronously decodes the json file
@ -159,14 +154,13 @@ public class StoredCollection<T: Storable>: SomeCollection {
if !self.inMemory {
await self.loadFromFile()
} else {
await self._delegate?.loadingForMemoryCollection()
await MainActor.run {
self.setAsLoaded()
}
}
}
/// Starts the JSON file decoding synchronously or asynchronously
/// Starts the JSON file decoding asynchronously
func loadFromFile() async {
do {
try await self._decodeJSONFile()
@ -222,7 +216,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
for item in items {
self._addItem(instance: item)
}
self.requestWrite()
}
@MainActor
@ -234,7 +227,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.setAsLoaded()
self.addOrUpdate(contentOfs: items)
}
self.requestWriteIfNecessary()
}
/// Updates the whole index with the items array
@ -250,7 +243,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Adds it if its id is not found, and otherwise updates it
@discardableResult public func addOrUpdate(instance: T) -> ActionResult<T> {
defer {
self.requestWrite()
self.requestWriteIfNecessary()
}
return self._rawAddOrUpdate(instance: instance)
}
@ -259,7 +252,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
public func addOrUpdate(contentOfs sequence: any Sequence<T>, _ handler: ((ActionResult<T>) -> ())? = nil) {
defer {
self.requestWrite()
self.requestWriteIfNecessary()
}
for instance in sequence {
@ -282,7 +275,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// A method the treat the collection as a single instance holder
func setSingletonNoSync(instance: T) {
defer {
self.requestWrite()
self.requestWriteIfNecessary()
}
self.items.removeAll()
self._addItem(instance: instance)
@ -432,7 +425,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.deleteItem(instance, actionOption: actionOption)
}
if actionOption.write {
self.requestWrite()
self.requestWriteIfNecessary()
}
}
@ -448,7 +441,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
/// Also removes related API calls
public func deleteDependencies(_ items: any Sequence<T>) {
defer {
self.requestWrite()
self.requestWriteIfNecessary()
}
let itemsArray = Array(items) // fix error if items is self.items
for item in itemsArray {
@ -503,7 +496,6 @@ public class StoredCollection<T: Storable>: SomeCollection {
self.deleteUnusedShared(data, actionOption: item.actionOption)
}
self._delegate?.itemMerged(item)
}
manager.reset()
@ -576,7 +568,7 @@ public class StoredCollection<T: Storable>: SomeCollection {
func updateLocalInstance(_ serverInstance: T) {
if let localInstance = self.findById(serverInstance.id) {
localInstance.copy(from: serverInstance)
self.requestWrite()
self.requestWriteIfNecessary()
}
}

@ -19,10 +19,10 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
let store: Store
let collection: StoredCollection<T>
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, synchronousLoading: Bool = false, noLoad: Bool = false) {
init(store: Store, indexed: Bool = false, inMemory: Bool = false, limit: Int? = nil, noLoad: Bool = false) {
self.store = store
self.collection = StoredCollection<T>(store: store, indexed: indexed, inMemory: inMemory, limit: limit, synchronousLoading: synchronousLoading, noLoad: noLoad)
self.collection = StoredCollection<T>(store: store, indexed: indexed, inMemory: inMemory, limit: limit, noLoad: noLoad)
}
@ -42,19 +42,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return SyncedCollection<T>(store: Store(storeCenter: StoreCenter.main))
}
/// Migrates if necessary and asynchronously decodes the json file
// override func load() async {
// do {
// if self.inMemory {
// try await self.loadDataFromServerIfAllowed()
// } else {
// await self.loadFromFile()
// }
// } catch {
// Logger.error(error)
// }
// }
/// Loads the collection using the server data only if the collection file doesn't exists
func loadCollectionsFromServerIfNoFile() async throws {
let fileURL: URL = try self.store.fileURL(type: T.self)
@ -190,41 +177,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return batch
}
/// Deletes an instance without writing, logs the operation and sends an API call
// fileprivate func _deleteNoWrite(instance: T) {
// self.deleteItem(instance, shouldBeSynchronized: true)
// self.storeCenter.createDeleteLog(instance)
//// await self._sendDeletion(instance)
// }
// public func deleteDependencies(_ items: any RandomAccessCollection<T>, actionOption: ActionOption) {
// guard items.isNotEmpty else { return }
// if actionOption.synchronize {
// self.delete(contentOfs: items)
// } else {
// self.deleteNoSync(contentOfs: items)
// }
// }
// public func deleteDependencies(_ items: any Sequence<T>) {
//
// self.collection.deleteDependencies(items)
//
//// super.deleteDependencies(items)
//
// let batch = OperationBatch<T>()
// batch.deletes = Array(items)
// Task { await self._sendOperationBatch(batch) }
// }
// public func deleteDependenciesAsync(_ items: any Sequence<T>) async {
// super.deleteDependencies(items)
//
// let batch = OperationBatch<T>()
// batch.deletes = Array(items)
// await self._sendOperationBatch(batch)
// }
fileprivate func _cleanUpSharedDependencies() {
for relationship in T.relationships() {
if let syncedType = relationship.type as? (any SyncedStorable.Type) {
@ -246,11 +198,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
case .child:
throw StoreError.invalidStoreLookup(from: type, to: relationship.type)
}
// if relationship.storeLookup {
// store = self.store.storeCenter.mainStore
// } else {
// store = self.store
// }
let collection: SyncedCollection<S> = try store.syncedCollection()
collection._deleteUnusedGrantedInstances(originStoreId: originStoreId)
@ -326,11 +273,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
self.collection.addOrUpdate(contentOfs: sequence)
}
/// Deletes the instance in the collection without synchronization
// func deleteNoSync(instance: T) {
// self.collection.delete(instance: instance)
// }
public func deleteNoSync(contentOfs sequence: any RandomAccessCollection<T>) {
self.collection.delete(contentOfs: sequence)
}
@ -346,16 +288,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
instance.deleteUnusedSharedDependencies(store: self.store)
}
/// Deletes the instance in the collection without synchronization
// func deleteNoSyncNoCascade(id: String) {
// self.collection.deleteByStringId(id, actionOption: .standard)
// }
//
// /// Deletes the instance in the collection without synchronization
// func deleteNoSyncNoCascadeNoWrite(id: String) {
// self.collection.deleteByStringId(id, actionOption: .noCascadeNoWrite)
// }
func deleteByStringId(_ id: String, actionOption: ActionOption = .standard) {
self.collection.deleteByStringId(id, actionOption: actionOption)
}
@ -431,10 +363,6 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
/// Adds or update an instance if it is newer than the local instance
func addOrUpdateIfNewer(_ instance: T, shared: SharingStatus?) {
// defer {
// self.triggerWrite()
// }
if let index = self.collection.items.firstIndex(where: { $0.id == instance.id }) {
let localInstance = self.collection.items[index]
@ -485,8 +413,8 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
return self.collection.items
}
public func requestWrite() {
self.collection.requestWrite()
public func requestWriteIfNecessary() {
self.collection.requestWriteIfNecessary()
}
}
@ -536,8 +464,6 @@ extension SyncedCollection: RandomAccessCollection {
}
set(newValue) {
self.collection.update(newValue, index: index, actionOption: .standard)
// self.collection.items[index] = newValue
// self._triggerWrite = true
}
}
}

Loading…
Cancel
Save