Fixes sync issues

sync2
Laurent 9 months ago
parent a3f6ad618e
commit a638947ea6
  1. 19
      LeStorage/ApiCallCollection.swift
  2. 13
      LeStorage/Codables/GetSyncData.swift
  3. 2
      LeStorage/Codables/Settings.swift
  4. 11
      LeStorage/Services.swift
  5. 23
      LeStorage/StoreCenter.swift
  6. 2
      LeStorage/StoredCollection+Sync.swift
  7. 7
      LeStorage/Utils/Date+Extensions.swift
  8. 2
      LeStorage/WebSocketManager.swift

@ -170,13 +170,13 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
func rescheduleApiCallsIfNecessary() { func rescheduleApiCallsIfNecessary() {
if self.items.isNotEmpty && !self._isRescheduling { if self.items.isNotEmpty && !self._isRescheduling {
self._schedulingTask = Task { self._schedulingTask = Task {
await self._rescheduleApiCalls() await self._waitAndExecuteApiCalls()
} }
} }
} }
/// Reschedule the execution of API calls /// Reschedule the execution of API calls
fileprivate func _rescheduleApiCalls() async { fileprivate func _waitAndExecuteApiCalls() async {
// Logger.log("\(T.resourceName()) > RESCHED") // Logger.log("\(T.resourceName()) > RESCHED")
guard !self._isRescheduling, StoreCenter.main.collectionsCanSynchronize else { return } guard !self._isRescheduling, StoreCenter.main.collectionsCanSynchronize else { return }
@ -199,7 +199,6 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
if T.copyServerResponse { if T.copyServerResponse {
StoreCenter.main.updateLocalInstances(results) StoreCenter.main.updateLocalInstances(results)
} }
// self._attemptLoops = -1
} }
} catch { } catch {
Logger.error(error) Logger.error(error)
@ -208,7 +207,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
self._isRescheduling = false self._isRescheduling = false
if self.items.isNotEmpty { if self.items.isNotEmpty {
await self._rescheduleApiCalls() await self._waitAndExecuteApiCalls()
} }
// Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)") // Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)")
@ -218,7 +217,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
fileprivate func _wait() async { fileprivate func _wait() async {
#if DEBUG #if DEBUG
let seconds = 2 * self._attemptLoops let seconds = self._attemptLoops
#else #else
let delay = pow(2, self._attemptLoops) let delay = pow(2, self._attemptLoops)
let seconds = NSDecimalNumber(decimal: delay).intValue let seconds = NSDecimalNumber(decimal: delay).intValue
@ -236,10 +235,10 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
/// Returns an APICall instance for the Storable [instance] and an HTTP [method] /// Returns an APICall instance for the Storable [instance] and an HTTP [method]
/// The method updates existing calls or creates a new one /// The method updates existing calls or creates a new one
fileprivate func _call(method: HTTPMethod, instance: T? = nil) throws -> ApiCall<T>? { fileprivate func _call(method: HTTPMethod, instance: T? = nil) async throws -> ApiCall<T>? {
if let instance { if let instance {
return try self._callForInstance(method, instance: instance) return try await self._callForInstance(method, instance: instance)
} else { } else {
if self.items.contains(where: { $0.method == .get }) { if self.items.contains(where: { $0.method == .get }) {
return nil return nil
@ -249,7 +248,7 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} }
} }
fileprivate func _callForInstance(_ method: HTTPMethod, instance: T) throws -> ApiCall<T>? { fileprivate func _callForInstance(_ method: HTTPMethod, instance: T) async throws -> ApiCall<T>? {
if let existingCall = self.items.first(where: { $0.dataId == instance.stringId }) { if let existingCall = self.items.first(where: { $0.dataId == instance.stringId }) {
switch method { switch method {
@ -322,10 +321,8 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
/// Initiates the process of sending the data with the server /// Initiates the process of sending the data with the server
fileprivate func _sendServerRequest<V: Decodable>(_ method: HTTPMethod, instance: T? = nil) async throws -> V? { fileprivate func _sendServerRequest<V: Decodable>(_ method: HTTPMethod, instance: T? = nil) async throws -> V? {
if let apiCall = try self._call(method: method, instance: instance) { if let apiCall = try await self._call(method: method, instance: instance) {
return try await self._prepareAndSendCall(apiCall) return try await self._prepareAndSendCall(apiCall)
// try self._prepareCall(apiCall: apiCall)
// return try await self._executeApiCall(apiCall)
} else { } else {
return nil return nil
} }

@ -9,25 +9,26 @@ import Foundation
class GetSyncData: SyncedModelObject, SyncedStorable, URLParameterConvertible { class GetSyncData: SyncedModelObject, SyncedStorable, URLParameterConvertible {
var date: String = ""
static func tokenExemptedMethods() -> [HTTPMethod] { return [] } static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
static func resourceName() -> String { static func resourceName() -> String {
return "data" return "sync-requests"
} }
func copy(from other: any Storable) { func copy(from other: any Storable) {
guard let getSyncData = other as? GetSyncData else { return } guard let getSyncData = other as? GetSyncData else { return }
self.lastUpdate = getSyncData.lastUpdate self.date = getSyncData.date
} }
func queryParameters() -> [String : String] { func queryParameters() -> [String : String] {
return ["last_update" : self._formattedLastUpdate] return ["last_update" : self._formattedLastUpdate,
"device_id" : StoreCenter.main.deviceId()]
} }
fileprivate var _formattedLastUpdate: String { fileprivate var _formattedLastUpdate: String {
let formattedDate = Date.iso8601FractionalFormatter.string(from: self.lastUpdate) let encodedDate = self.date.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
let encodedDate =
formattedDate.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
return encodedDate.replacingOccurrences(of: "+", with: "%2B") return encodedDate.replacingOccurrences(of: "+", with: "%2B")
} }

@ -16,6 +16,6 @@ class Settings: MicroStorable {
var userId: String? = nil var userId: String? = nil
var username: String? = nil var username: String? = nil
var deviceId: String? = nil var deviceId: String? = nil
var lastSynchronization: Date = Date() var lastSynchronization: String = "2000-01-01T00:00:00.000000Z"
} }

@ -271,7 +271,7 @@ public class Services {
// print("sync POST \(String(describing: T.self)) => \(String(data: task.0, encoding: .utf8) ?? "")") // print("sync POST \(String(describing: T.self)) => \(String(data: task.0, encoding: .utf8) ?? "")")
var rescheduleApiCalls: Bool = false var rescheduleApiCalls: Bool = false
var success: [T] = [] var successes: [T] = []
if let response = task.1 as? HTTPURLResponse { if let response = task.1 as? HTTPURLResponse {
let statusCode = response.statusCode let statusCode = response.statusCode
@ -285,11 +285,14 @@ public class Services {
switch result.status { switch result.status {
case 200..<300: case 200..<300:
if let data = result.data { if let data = result.data {
success.append(data) successes.append(data)
} }
try await StoreCenter.main.deleteApiCallById(type: T.self, id: result.apiCallId) try await StoreCenter.main.deleteApiCallById(type: T.self, id: result.apiCallId)
default: default:
if let message = result.message {
print(message)
}
rescheduleApiCalls = true rescheduleApiCalls = true
} }
} }
@ -321,7 +324,7 @@ public class Services {
try? await StoreCenter.main.rescheduleApiCalls(type: T.self) try? await StoreCenter.main.rescheduleApiCalls(type: T.self)
} }
return success return successes
} }
/// Returns the URLRequest for an ApiCall /// Returns the URLRequest for an ApiCall
@ -596,7 +599,7 @@ public class Services {
let user: U = try await self._runRequest(postRequest) let user: U = try await self._runRequest(postRequest)
// StoreCenter.main.setUserUUID(uuidString: user.id) // StoreCenter.main.setUserUUID(uuidString: user.id)
// StoreCenter.main.setUserName(user.username) // StoreCenter.main.setUserName(user.username)
StoreCenter.main.userDidLog(user: user, date: loggingDate) StoreCenter.main.userDidLogIn(user: user, at: loggingDate)
return user return user
} }

@ -66,7 +66,7 @@ public class StoreCenter {
self._resumeApiCalls() self._resumeApiCalls()
// self._configureWebSocket() // self._configureWebSocket()
} }
Logger.log("device Id = \(self.deviceId())") // Logger.log("device Id = \(self.deviceId())")
} }
public func configureURLs(secureScheme: Bool, domain: String) { public func configureURLs(secureScheme: Bool, domain: String) {
@ -173,11 +173,15 @@ public class StoreCenter {
// MARK: - Settings // MARK: - Settings
/// Sets the user info given a user /// Sets the user info given a user
func userDidLog(user: UserBase, date: Date) { func userDidLogIn(user: UserBase, at date: Date) {
self._settingsStorage.update { settings in self._settingsStorage.update { settings in
settings.userId = user.id settings.userId = user.id
settings.username = user.username settings.username = user.username
settings.lastSynchronization = date
let date = Date.microSecondFormatter.string(from: date)
Logger.log("LOG date = \(date)")
settings.lastSynchronization = Date.microSecondFormatter.string(from: Date.distantPast)
self._configureWebSocket() self._configureWebSocket()
} }
} }
@ -210,7 +214,7 @@ public class StoreCenter {
self._settingsStorage.update { settings in self._settingsStorage.update { settings in
settings.username = nil settings.username = nil
settings.userId = nil settings.userId = nil
settings.lastSynchronization = Date() settings.lastSynchronization = Date.microSecondFormatter.string(from: Date())
self._webSocketManager = nil self._webSocketManager = nil
} }
@ -467,7 +471,7 @@ public class StoreCenter {
await syncGetCollection.rescheduleImmediately() await syncGetCollection.rescheduleImmediately()
} else { } else {
let getSyncData = GetSyncData() let getSyncData = GetSyncData()
getSyncData.lastUpdate = lastSync getSyncData.date = lastSync
try await syncGetCollection.sendGetRequest(instance: getSyncData) try await syncGetCollection.sendGetRequest(instance: getSyncData)
} }
@ -517,11 +521,10 @@ public class StoreCenter {
try self._parseSyncRevocations(revocations, parents: json["revocation_parents"] as? [[String: Any]]) try self._parseSyncRevocations(revocations, parents: json["revocation_parents"] as? [[String: Any]])
} }
if let dateString = json["date"] as? String, if let dateString = json["date"] as? String {
let date = Date.iso8601FractionalFormatter.date(from: dateString) { Logger.log("Sets sync date = \(dateString)")
Logger.log("Sets sync date = \(date)")
self._settingsStorage.update { settings in self._settingsStorage.update { settings in
settings.lastSynchronization = date settings.lastSynchronization = dateString
} }
} }
@ -546,7 +549,7 @@ public class StoreCenter {
Logger.w("Invalid update data for \(className)") Logger.w("Invalid update data for \(className)")
continue continue
} }
// Logger.log(">>> UPDATE \(updateArray.count) \(className)") Logger.log(">>> UPDATE \(updateArray.count) \(className)")
let type = try StoreCenter.classFromName(className) let type = try StoreCenter.classFromName(className)

@ -276,7 +276,7 @@ extension StoredCollection: SomeSyncedCollection where T : SyncedStorable {
if instance.lastUpdate > localInstance.lastUpdate { if instance.lastUpdate > localInstance.lastUpdate {
self.updateItem(instance, index: index) self.updateItem(instance, index: index)
} else { } else {
Logger.log("do not update: \(instance.lastUpdate.timeIntervalSince1970) / local: \(localInstance.lastUpdate.timeIntervalSince1970)") print("do not update \(T.resourceName()): \(instance.lastUpdate.timeIntervalSince1970) / local: \(localInstance.lastUpdate.timeIntervalSince1970)")
} }
} else { // insert } else { // insert
if shared { if shared {

@ -23,4 +23,11 @@ extension Date {
return iso8601Formatter return iso8601Formatter
} }
public static var microSecondFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'" // puts 000 for the last decimals
formatter.timeZone = TimeZone(abbreviation: "UTC")
return formatter
}()
} }

@ -61,7 +61,7 @@ class WebSocketManager: ObservableObject {
self._failure = false self._failure = false
switch message { switch message {
case .string(let deviceId): case .string(let deviceId):
print("device id = \(StoreCenter.main.deviceId()), origin id: \(deviceId)") // print("device id = \(StoreCenter.main.deviceId()), origin id: \(deviceId)")
guard StoreCenter.main.deviceId() != deviceId else { guard StoreCenter.main.deviceId() != deviceId else {
break break
} }

Loading…
Cancel
Save