Adds network monitor to resume api calls

sync2
Laurent 1 year ago
parent f926a1fcbe
commit b5b32892dc
  1. 4
      LeStorage.xcodeproj/project.pbxproj
  2. 39
      LeStorage/ApiCallCollection.swift
  3. 59
      LeStorage/NetworkMonitor.swift
  4. 17
      LeStorage/StoreCenter.swift

@ -14,6 +14,7 @@
C425D4582B6D2519002A7B48 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = C425D4572B6D2519002A7B48 /* Store.swift */; };
C456EFE22BE52379007388E2 /* StoredSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C456EFE12BE52379007388E2 /* StoredSingleton.swift */; };
C45D35912C0A1DB5000F379F /* FailedAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D35902C0A1DB5000F379F /* FailedAPICall.swift */; };
C488C8802CCBDC210082001F /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C488C87F2CCBDC210082001F /* NetworkMonitor.swift */; };
C49B6E502C2089B6002BDE1B /* ApiCallCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49B6E4F2C2089B6002BDE1B /* ApiCallCollection.swift */; };
C49EF0242BD6BDC50077B5AA /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */; };
C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */; };
@ -59,6 +60,7 @@
C425D4572B6D2519002A7B48 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
C456EFE12BE52379007388E2 /* StoredSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredSingleton.swift; sourceTree = "<group>"; };
C45D35902C0A1DB5000F379F /* FailedAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAPICall.swift; sourceTree = "<group>"; };
C488C87F2CCBDC210082001F /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
C49B6E4F2C2089B6002BDE1B /* ApiCallCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCallCollection.swift; sourceTree = "<group>"; };
C49EF0232BD6BDC50077B5AA /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
C4A47D4E2B6D280200ADC637 /* StoredCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCollection.swift; sourceTree = "<group>"; };
@ -135,6 +137,7 @@
C4A47D9D2B7CFFF500ADC637 /* Codables */,
C49B6E4F2C2089B6002BDE1B /* ApiCallCollection.swift */,
C4A47D6C2B71364600ADC637 /* ModelObject.swift */,
C488C87F2CCBDC210082001F /* NetworkMonitor.swift */,
C4A47D602B6D3C1300ADC637 /* Services.swift */,
C425D4572B6D2519002A7B48 /* Store.swift */,
C4FC2E282C2B2EC30021F3BF /* StoreCenter.swift */,
@ -316,6 +319,7 @@
C456EFE22BE52379007388E2 /* StoredSingleton.swift in Sources */,
C4A47D652B6E92FE00ADC637 /* Storable.swift in Sources */,
C4D477972CB66EEA0077713D /* Date+Extensions.swift in Sources */,
C488C8802CCBDC210082001F /* NetworkMonitor.swift in Sources */,
C4A47D6D2B71364600ADC637 /* ModelObject.swift in Sources */,
C400D7232CC2AF560092237C /* GetSyncData.swift in Sources */,
C4A47D4F2B6D280200ADC637 /* StoredCollection.swift in Sources */,

@ -16,6 +16,7 @@ protocol SomeCallCollection {
func contentOfFile() async -> String?
func reset() async
func resumeApiCalls() async
}
@ -33,6 +34,8 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
/// Indicates if the collection is currently retrying ApiCalls
fileprivate var _isRescheduling: Bool = false
fileprivate var _schedulingTask: Task<(), Never>? = nil
/// Indicates whether the collection content has changed
/// Initiates a write when true
fileprivate var _hasChanged: Bool = false {
@ -140,23 +143,15 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
}
}
/// Wait for an exponentionnaly long time depending on the number of attemps
fileprivate func _wait() async {
let delay = pow(2, self._attemptLoops)
let seconds = NSDecimalNumber(decimal: delay).intValue
Logger.log("\(T.resourceName()): wait for \(seconds) sec")
do {
try await Task.sleep(until: .now + .seconds(seconds))
} catch {
Logger.w("*** WAITING CRASHED !!!")
Logger.error(error)
}
func resumeApiCalls() {
self._schedulingTask?.cancel()
self._attemptLoops = -1
self.rescheduleApiCallsIfNecessary()
}
/// Reschedule API calls if necessary
func rescheduleApiCallsIfNecessary() {
Task {
self._schedulingTask = Task {
await self._rescheduleApiCalls()
}
}
@ -195,8 +190,6 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
} else {
let _: [T] = try await self._executeApiCall(apiCall)
}
// process GET
// what if it is a sync GET
}
} catch {
// Logger.log("\(T.resourceName()) > API CALL RETRY ERROR:")
@ -204,8 +197,6 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
}
}
// Logger.log("\(T.resourceName()) > STOP RESCHED")
self._isRescheduling = false
if self.items.isNotEmpty {
await self._rescheduleApiCalls()
@ -214,6 +205,20 @@ actor ApiCallCollection<T: SyncedStorable>: SomeCallCollection {
// Logger.log("\(T.resourceName()) > isRescheduling = \(self._isRescheduling)")
}
/// Wait for an exponentionnaly long time depending on the number of attemps
fileprivate func _wait() async {
let delay = pow(2, self._attemptLoops)
let seconds = NSDecimalNumber(decimal: delay).intValue
Logger.log("\(T.resourceName()): wait for \(seconds) sec")
do {
try await Task.sleep(until: .now + .seconds(seconds))
} catch {
Logger.w("*** WAITING CRASHED !!!")
Logger.error(error)
}
}
// MARK: - Synchronization
/// Returns an APICall instance for the Storable [instance] and an HTTP [method]

@ -0,0 +1,59 @@
//
// NetworkMonitor.swift
// LeStorage
//
// Created by Laurent Morvillier on 25/10/2024.
//
import Network
import Foundation
public class NetworkMonitor {
public static let shared = NetworkMonitor()
private var monitor: NWPathMonitor
private var queue = DispatchQueue(label: "NetworkMonitor")
public var isConnected: Bool {
get {
return status == .satisfied
}
}
private(set) var status: NWPath.Status = .requiresConnection
// Closure to be called when connection is established
var onConnectionEstablished: (() -> Void)?
private init() {
monitor = NWPathMonitor()
self._startMonitoring()
}
private func _startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
// Update status
self.status = path.status
// Print status for debugging
Logger.log("Network Status: \(path.status)")
// Handle connection established
if path.status == .satisfied {
DispatchQueue.main.async {
self.onConnectionEstablished?()
}
}
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}

@ -56,6 +56,10 @@ public class StoreCenter {
init() {
self._dataLogs = Store.main.registerCollection()
self._setupNotifications()
NetworkMonitor.shared.onConnectionEstablished = {
self._resumeApiCalls()
}
}
/// Returns the service instance
@ -266,6 +270,19 @@ public class StoreCenter {
// MARK: - Api call rescheduling
/// Retry API calls immediately
fileprivate func _resumeApiCalls() {
guard self.collectionsCanSynchronize else {
return
}
Logger.log("_resumeApiCalls")
Task {
for collection in self._apiCallCollections.values {
await collection.resumeApiCalls()
}
}
}
/// Reschedule an ApiCall by id
func rescheduleApiCalls<T: SyncedStorable>(id: String, type: T.Type) async throws {
guard self.collectionsCanSynchronize else {

Loading…
Cancel
Save