Makes insert work + Store provides ids to match django ids

multistore
Laurent 2 years ago
parent daa34132c4
commit 9cfa848a11
  1. 4
      LeStorage.xcodeproj/project.pbxproj
  2. 21
      LeStorage/Services.swift
  3. 20
      LeStorage/Storable.swift
  4. 37
      LeStorage/Store.swift
  5. 27
      LeStorage/StoredCollection.swift

@ -17,6 +17,7 @@
C4A47D532B6D2C5F00ADC637 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D522B6D2C5F00ADC637 /* Logger.swift */; }; C4A47D532B6D2C5F00ADC637 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D522B6D2C5F00ADC637 /* Logger.swift */; };
C4A47D552B6D2DBF00ADC637 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D542B6D2DBF00ADC637 /* FileUtils.swift */; }; C4A47D552B6D2DBF00ADC637 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D542B6D2DBF00ADC637 /* FileUtils.swift */; };
C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D602B6D3C1300ADC637 /* Services.swift */; }; C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D602B6D3C1300ADC637 /* Services.swift */; };
C4A47D652B6E92FE00ADC637 /* Storable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D642B6E92FE00ADC637 /* Storable.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -41,6 +42,7 @@
C4A47D522B6D2C5F00ADC637 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; }; C4A47D522B6D2C5F00ADC637 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
C4A47D542B6D2DBF00ADC637 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; }; C4A47D542B6D2DBF00ADC637 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
C4A47D602B6D3C1300ADC637 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; }; C4A47D602B6D3C1300ADC637 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
C4A47D642B6E92FE00ADC637 /* Storable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storable.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -87,6 +89,7 @@
C425D4382B6D24E1002A7B48 /* LeStorage.docc */, C425D4382B6D24E1002A7B48 /* LeStorage.docc */,
C4A47D602B6D3C1300ADC637 /* Services.swift */, C4A47D602B6D3C1300ADC637 /* Services.swift */,
C425D4572B6D2519002A7B48 /* Store.swift */, C425D4572B6D2519002A7B48 /* Store.swift */,
C4A47D642B6E92FE00ADC637 /* Storable.swift */,
C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */, C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */,
C4A47D582B6D352900ADC637 /* Utils */, C4A47D582B6D352900ADC637 /* Utils */,
); );
@ -225,6 +228,7 @@
C425D4392B6D24E1002A7B48 /* LeStorage.docc in Sources */, C425D4392B6D24E1002A7B48 /* LeStorage.docc in Sources */,
C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */, C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */,
C4A47D552B6D2DBF00ADC637 /* FileUtils.swift in Sources */, C4A47D552B6D2DBF00ADC637 /* FileUtils.swift in Sources */,
C4A47D652B6E92FE00ADC637 /* Storable.swift in Sources */,
C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */, C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */,
C425D4582B6D2519002A7B48 /* Store.swift in Sources */, C425D4582B6D2519002A7B48 /* Store.swift in Sources */,
); );

@ -38,6 +38,13 @@ class Services {
fileprivate func runRequest<T : Decodable>(_ request: URLRequest) async throws -> T { fileprivate func runRequest<T : Decodable>(_ request: URLRequest) async throws -> T {
let task: (Data, URLResponse) = try await URLSession.shared.data(for: request) let task: (Data, URLResponse) = try await URLSession.shared.data(for: request)
if let response = task.1 as? HTTPURLResponse {
let statusCode = response.statusCode
Logger.log("status code = \(statusCode)")
}
Logger.log("response = \(String(data: task.0, encoding: .utf8))")
return try jsonDecoder.decode(T.self, from: task.0) return try jsonDecoder.decode(T.self, from: task.0)
} }
@ -64,6 +71,7 @@ class Services {
} }
var request = URLRequest(url: url) var request = URLRequest(url: url)
request.httpMethod = method.rawValue request.httpMethod = method.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return request return request
} }
@ -75,18 +83,21 @@ class Services {
} }
func insert<T : Storable>(_ instance: T) async throws -> T { func insert<T : Storable>(_ instance: T) async throws -> T {
let postRequest = try postRequest(servicePath: T.resourceName + "/") var postRequest = try postRequest(servicePath: T.resourceName + "/")
postRequest.httpBody = try instance.jsonData()
return try await self.runRequest(postRequest) return try await self.runRequest(postRequest)
} }
func update<T : Storable>(_ instance: T) async throws -> T { func update<T : Storable>(_ instance: T) async throws -> T {
let postRequest = try putRequest(servicePath: T.resourceName + "/") var putRequest = try putRequest(servicePath: T.resourceName + "/")
return try await self.runRequest(postRequest) putRequest.httpBody = try instance.jsonData()
return try await self.runRequest(putRequest)
} }
func delete<T : Storable>(_ instance: T) async throws -> T { func delete<T : Storable>(_ instance: T) async throws -> T {
let postRequest = try deleteRequest(servicePath: T.resourceName + "/") var deleteRequest = try deleteRequest(servicePath: T.resourceName + "/")
return try await self.runRequest(postRequest) deleteRequest.httpBody = try instance.jsonData()
return try await self.runRequest(deleteRequest)
} }
} }

@ -0,0 +1,20 @@
//
// Storable.swift
// LeStorage
//
// Created by Laurent Morvillier on 03/02/2024.
//
import Foundation
public protocol Storable : Codable, Identifiable where ID : StringProtocol {
static var resourceName: String { get }
}
extension Storable {
public func findById<T : Storable>(_ id: String) -> T? {
return Store.main.findById(id)
}
}

@ -7,28 +7,43 @@
import Foundation import Foundation
protocol ServiceProvider { public class Store {
var service: Services? { get }
}
public class Store: ServiceProvider { public static let main = Store()
fileprivate var _synchronizationApiURL: String? public static func randomId() -> String {
fileprivate var _services: Services? return UUID().uuidString.lowercased()
}
public init(synchronizationApiURL: String? = nil) { public var synchronizationApiURL: String? {
self._synchronizationApiURL = synchronizationApiURL didSet {
if let url = synchronizationApiURL { if let url = synchronizationApiURL {
self._services = Services(url: url) self._services = Services(url: url)
}
} }
} }
fileprivate var _services: Services?
fileprivate var collections: [String : any SomeCollection] = [:]
public init() { }
public func registerCollection<T : Storable>(synchronized: Bool) -> StoredCollection<T> { public func registerCollection<T : Storable>(synchronized: Bool) -> StoredCollection<T> {
return StoredCollection(synchronized: synchronized, serviceProvider: self) let collection = StoredCollection<T>(synchronized: synchronized, store: self)
self.collections[T.resourceName] = collection
return collection
} }
var service: Services? { var service: Services? {
return self._services return self._services
} }
func findById<T : Storable>(_ id: String) -> T? {
guard let collection = self.collections[T.resourceName] as? StoredCollection<T> else {
Logger.w("Collection \(T.resourceName) not registered")
return nil
}
return collection.findById(id)
}
} }

@ -7,17 +7,17 @@
import Foundation import Foundation
public protocol Storable : Codable, Identifiable where ID : Hashable { protocol SomeCollection : Identifiable {
static var resourceName: String { get }
} }
public class StoredCollection<T : Storable> : RandomAccessCollection, ObservableObject { public class StoredCollection<T : Storable> : RandomAccessCollection, SomeCollection, ObservableObject {
let synchronized: Bool let synchronized: Bool
@Published public fileprivate(set) var items: [T] = [] @Published public fileprivate(set) var items: [T] = []
fileprivate var _serviceProvider: ServiceProvider fileprivate var _store: Store
fileprivate var _hasChanged: Bool = false { fileprivate var _hasChanged: Bool = false {
didSet { didSet {
@ -29,9 +29,9 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, Observable
} }
} }
init(synchronized: Bool, serviceProvider: ServiceProvider) { init(synchronized: Bool, store: Store) {
self.synchronized = synchronized self.synchronized = synchronized
self._serviceProvider = serviceProvider self._store = store
self._load() self._load()
} }
@ -63,6 +63,12 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, Observable
self._sendDeletionIfNecessary(instance) self._sendDeletionIfNecessary(instance)
} }
public func findById(_ id: String) -> T? {
return self.items.first(where: { $0.id == id })
}
// MARK: - File access
fileprivate func _scheduleWrite() { fileprivate func _scheduleWrite() {
self._write() self._write()
} }
@ -114,13 +120,14 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, Observable
// MARK: - Synchronization // MARK: - Synchronization
fileprivate func _sendInsertionIfNecessary(_ instance: T) { fileprivate func _sendInsertionIfNecessary(_ instance: T) {
Logger.log("_sendInsertionIfNecessary...")
guard self.synchronized else { guard self.synchronized else {
return return
} }
Logger.log("Call service...")
Task { Task {
do { do {
let _ = try await self._serviceProvider.service?.insert(instance) let _ = try await self._store.service?.insert(instance)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -135,7 +142,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, Observable
Task { Task {
do { do {
let _ = try await self._serviceProvider.service?.insert(instance) let _ = try await self._store.service?.insert(instance)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -150,7 +157,7 @@ public class StoredCollection<T : Storable> : RandomAccessCollection, Observable
Task { Task {
do { do {
let _ = try await self._serviceProvider.service?.delete(instance) let _ = try await self._store.service?.delete(instance)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }

Loading…
Cancel
Save