@@ -15,6 +15,14 @@ public protocol POSCatalogSyncCoordinatorProtocol {
1515 /// - maxAge: Maximum age before a sync is considered stale
1616 /// - Returns: True if a sync should be performed
1717 func shouldPerformFullSync( for siteID: Int64 , maxAge: TimeInterval ) async -> Bool
18+
19+ /// Performs an incremental sync if applicable based on sync conditions
20+ /// - Parameters:
21+ /// - siteID: The site ID to sync catalog for
22+ /// - forceSync: Whether to bypass age checks and always sync
23+ /// - Throws: POSCatalogSyncError.syncAlreadyInProgress if a sync is already running for this site
24+ //periphery:ignore - remove ignore comment when incremental sync is integrated with POS
25+ func performIncrementalSyncIfApplicable( for siteID: Int64 , forceSync: Bool ) async throws
1826}
1927
2028public enum POSCatalogSyncError : Error , Equatable {
@@ -23,26 +31,23 @@ public enum POSCatalogSyncError: Error, Equatable {
2331
2432public actor POSCatalogSyncCoordinator : POSCatalogSyncCoordinatorProtocol {
2533 private let fullSyncService : POSCatalogFullSyncServiceProtocol
26- private let persistenceService : POSCatalogPersistenceServiceProtocol
34+ private let incrementalSyncService : POSCatalogIncrementalSyncServiceProtocol
2735 private let grdbManager : GRDBManagerProtocol
36+ private let maxIncrementalSyncAge : TimeInterval
2837
29- /// Tracks ongoing syncs by site ID to prevent duplicates
38+ /// Tracks ongoing full syncs by site ID to prevent duplicates
3039 private var ongoingSyncs : Set < Int64 > = [ ]
40+ /// Tracks ongoing incremental syncs by site ID to prevent duplicates
41+ private var ongoingIncrementalSyncs : Set < Int64 > = [ ]
3142
3243 public init ( fullSyncService: POSCatalogFullSyncServiceProtocol ,
33- grdbManager: GRDBManagerProtocol ) {
34- self . fullSyncService = fullSyncService
35- self . persistenceService = POSCatalogPersistenceService ( grdbManager: grdbManager)
36- self . grdbManager = grdbManager
37- }
38-
39- //periphery:ignore - used for tests to inject persistence service
40- init ( fullSyncService: POSCatalogFullSyncServiceProtocol ,
41- persistenceService: POSCatalogPersistenceServiceProtocol ,
42- grdbManager: GRDBManagerProtocol ) {
44+ incrementalSyncService: POSCatalogIncrementalSyncServiceProtocol ,
45+ grdbManager: GRDBManagerProtocol ,
46+ maxIncrementalSyncAge: TimeInterval = 300 ) {
4347 self . fullSyncService = fullSyncService
44- self . persistenceService = persistenceService
48+ self . incrementalSyncService = incrementalSyncService
4549 self . grdbManager = grdbManager
50+ self . maxIncrementalSyncAge = maxIncrementalSyncAge
4651 }
4752
4853 public func performFullSync( for siteID: Int64 ) async throws {
@@ -61,7 +66,7 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
6166
6267 DDLogInfo ( " 🔄 POSCatalogSyncCoordinator starting full sync for site \( siteID) " )
6368
64- let catalog = try await fullSyncService. startFullSync ( for: siteID)
69+ _ = try await fullSyncService. startFullSync ( for: siteID)
6570
6671 DDLogInfo ( " ✅ POSCatalogSyncCoordinator completed full sync for site \( siteID) " )
6772 }
@@ -89,6 +94,40 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
8994 return shouldSync
9095 }
9196
97+ public func performIncrementalSyncIfApplicable( for siteID: Int64 , forceSync: Bool ) async throws {
98+ if ongoingIncrementalSyncs. contains ( siteID) {
99+ DDLogInfo ( " ⚠️ POSCatalogSyncCoordinator: Incremental sync already in progress for site \( siteID) " )
100+ throw POSCatalogSyncError . syncAlreadyInProgress ( siteID: siteID)
101+ }
102+
103+ guard let lastFullSyncDate = await lastFullSyncDate ( for: siteID) else {
104+ DDLogInfo ( " 📋 POSCatalogSyncCoordinator: No full sync performed yet for site \( siteID) , skipping incremental sync " )
105+ return
106+ }
107+
108+ if !forceSync, let lastIncrementalSyncDate = await lastIncrementalSyncDate ( for: siteID) {
109+ let age = Date ( ) . timeIntervalSince ( lastIncrementalSyncDate)
110+
111+ if age <= maxIncrementalSyncAge {
112+ return DDLogInfo ( " 📋 POSCatalogSyncCoordinator: Last incremental sync for site \( siteID) was \( Int ( age) ) s ago, sync not needed " )
113+ }
114+ }
115+
116+ ongoingIncrementalSyncs. insert ( siteID)
117+
118+ defer {
119+ ongoingIncrementalSyncs. remove ( siteID)
120+ }
121+
122+ DDLogInfo ( " 🔄 POSCatalogSyncCoordinator starting incremental sync for site \( siteID) " )
123+
124+ try await incrementalSyncService. startIncrementalSync ( for: siteID,
125+ lastFullSyncDate: lastFullSyncDate,
126+ lastIncrementalSyncDate: lastIncrementalSyncDate ( for: siteID) )
127+
128+ DDLogInfo ( " ✅ POSCatalogSyncCoordinator completed incremental sync for site \( siteID) " )
129+ }
130+
92131 // MARK: - Private
93132
94133 private func lastFullSyncDate( for siteID: Int64 ) async -> Date ? {
@@ -102,6 +141,17 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
102141 }
103142 }
104143
144+ private func lastIncrementalSyncDate( for siteID: Int64 ) async -> Date ? {
145+ do {
146+ return try await grdbManager. databaseConnection. read { db in
147+ return try PersistedSite . filter ( key: siteID) . fetchOne ( db) ? . lastCatalogIncrementalSyncDate
148+ }
149+ } catch {
150+ DDLogError ( " ⛔️ POSCatalogSyncCoordinator: Error loading site \( siteID) for incremental sync date: \( error) " )
151+ return nil
152+ }
153+ }
154+
105155 private func siteExistsInDatabase( siteID: Int64 ) -> Bool {
106156 do {
107157 return try grdbManager. databaseConnection. read { db in
0 commit comments