Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions Modules/Sources/PointOfSale/Utils/PreviewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -609,16 +609,12 @@ final class POSPreviewCatalogSettingsService: POSCatalogSettingsServiceProtocol
}

final class POSPreviewCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
func performFullSync(for siteID: Int64) async throws {
func performFullSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {
// Simulates a full sync operation with a 1 second delay.
try await Task.sleep(nanoseconds: 1_000_000_000)
}

func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) async -> Bool {
true
}

func performIncrementalSyncIfApplicable(for siteID: Int64, forceSync: Bool) async throws {
func performIncrementalSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {
// Simulates an incremental sync operation with a 0.5 second delay.
try await Task.sleep(nanoseconds: 500_000_000)
}
Expand Down
96 changes: 62 additions & 34 deletions Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,41 @@ import Storage
import GRDB

public protocol POSCatalogSyncCoordinatorProtocol {
/// Performs a full catalog sync for the specified site
/// - Parameter siteID: The site ID to sync catalog for
/// - Throws: POSCatalogSyncError.syncAlreadyInProgress if a sync is already running for this site
func performFullSync(for siteID: Int64) async throws

/// Determines if a full sync should be performed based on the age of the last sync
/// Performs a full catalog sync if applicable for the specified site
/// - Parameters:
/// - siteID: The site ID to check
/// - siteID: The site ID to sync catalog for
/// - maxAge: Maximum age before a sync is considered stale
/// - Returns: True if a sync should be performed
func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) async -> Bool
/// - Throws: POSCatalogSyncError.syncAlreadyInProgress if a sync is already running for this site
func performFullSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws

/// Performs an incremental sync if applicable based on sync conditions
/// - Parameters:
/// - siteID: The site ID to sync catalog for
/// - forceSync: Whether to bypass age checks and always sync
/// - maxAge: Maximum age before a sync is considered stale
/// - Throws: POSCatalogSyncError.syncAlreadyInProgress if a sync is already running for this site
//periphery:ignore - remove ignore comment when incremental sync is integrated with POS
func performIncrementalSyncIfApplicable(for siteID: Int64, forceSync: Bool) async throws
func performIncrementalSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws
}

public extension POSCatalogSyncCoordinatorProtocol {
func performFullSync(for siteID: Int64) async throws {
try await performFullSyncIfApplicable(for: siteID, maxAge: .zero)
}

func performIncrementalSync(for siteID: Int64) async throws {
try await performIncrementalSyncIfApplicable(for: siteID, maxAge: .zero)
}
}

public enum POSCatalogSyncError: Error, Equatable {
case syncAlreadyInProgress(siteID: Int64)
case negativeMaxAge
}

public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
private let fullSyncService: POSCatalogFullSyncServiceProtocol
private let incrementalSyncService: POSCatalogIncrementalSyncServiceProtocol
private let grdbManager: GRDBManagerProtocol
private let maxIncrementalSyncAge: TimeInterval
private let catalogSizeLimit: Int
private let catalogSizeChecker: POSCatalogSizeCheckerProtocol

Expand All @@ -45,18 +50,24 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
public init(fullSyncService: POSCatalogFullSyncServiceProtocol,
incrementalSyncService: POSCatalogIncrementalSyncServiceProtocol,
grdbManager: GRDBManagerProtocol,
maxIncrementalSyncAge: TimeInterval = 300,
catalogSizeLimit: Int? = nil,
catalogSizeChecker: POSCatalogSizeCheckerProtocol) {
self.fullSyncService = fullSyncService
self.incrementalSyncService = incrementalSyncService
self.grdbManager = grdbManager
self.maxIncrementalSyncAge = maxIncrementalSyncAge
self.catalogSizeLimit = catalogSizeLimit ?? Constants.defaultSizeLimitForPOSCatalog
self.catalogSizeChecker = catalogSizeChecker
}

public func performFullSync(for siteID: Int64) async throws {
public func performFullSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {
guard maxAge >= 0 else {
throw POSCatalogSyncError.negativeMaxAge
}

guard await shouldPerformFullSync(for: siteID, maxAge: maxAge) else {
return
}

if ongoingSyncs.contains(siteID) {
DDLogInfo("⚠️ POSCatalogSyncCoordinator: Sync already in progress for site \(siteID)")
throw POSCatalogSyncError.syncAlreadyInProgress(siteID: siteID)
Expand All @@ -82,8 +93,8 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
/// - siteID: The site ID to check
/// - maxAge: Maximum age before a sync is considered stale
/// - Returns: True if a sync should be performed
public func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) async -> Bool {
return await shouldPerformFullSync(for: siteID, maxAge: maxAge, maxCatalogSize: catalogSizeLimit)
private func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) async -> Bool {
await shouldPerformFullSync(for: siteID, maxAge: maxAge, maxCatalogSize: catalogSizeLimit)
}

private func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval, maxCatalogSize: Int) async -> Bool {
Expand Down Expand Up @@ -118,33 +129,28 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
/// Performs an incremental sync if applicable based on sync conditions
/// - Parameters:
/// - siteID: The site ID to sync catalog for
/// - forceSync: Whether to bypass age checks and always sync
/// - maxAge: Maximum age before a sync is considered stale
/// - Throws: POSCatalogSyncError.syncAlreadyInProgress if a sync is already running for this site
public func performIncrementalSyncIfApplicable(for siteID: Int64, forceSync: Bool) async throws {
try await performIncrementalSyncIfApplicable(for: siteID, forceSync: forceSync, maxCatalogSize: catalogSizeLimit)
public func performIncrementalSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {
try await performIncrementalSyncIfApplicable(for: siteID, maxAge: maxAge, maxCatalogSize: catalogSizeLimit)
}

private func performIncrementalSyncIfApplicable(for siteID: Int64, forceSync: Bool, maxCatalogSize: Int) async throws {
if ongoingIncrementalSyncs.contains(siteID) {
DDLogInfo("⚠️ POSCatalogSyncCoordinator: Incremental sync already in progress for site \(siteID)")
throw POSCatalogSyncError.syncAlreadyInProgress(siteID: siteID)
private func performIncrementalSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval, maxCatalogSize: Int) async throws {
guard maxAge >= 0 else {
throw POSCatalogSyncError.negativeMaxAge
}

guard await isCatalogSizeWithinLimit(for: siteID, maxCatalogSize: maxCatalogSize) else {
guard await shouldPerformIncrementalSync(for: siteID, maxAge: maxAge, maxCatalogSize: maxCatalogSize) else {
return
}

guard let lastFullSyncDate = await lastFullSyncDate(for: siteID) else {
DDLogInfo("📋 POSCatalogSyncCoordinator: No full sync performed yet for site \(siteID), skipping incremental sync")
return
if ongoingIncrementalSyncs.contains(siteID) {
DDLogInfo("⚠️ POSCatalogSyncCoordinator: Incremental sync already in progress for site \(siteID)")
throw POSCatalogSyncError.syncAlreadyInProgress(siteID: siteID)
}

if !forceSync, let lastIncrementalSyncDate = await lastIncrementalSyncDate(for: siteID) {
let age = Date().timeIntervalSince(lastIncrementalSyncDate)

if age <= maxIncrementalSyncAge {
return DDLogInfo("📋 POSCatalogSyncCoordinator: Last incremental sync for site \(siteID) was \(Int(age))s ago, sync not needed")
}
guard let lastFullSyncDate = await lastFullSyncDate(for: siteID) else {
return
}

ongoingIncrementalSyncs.insert(siteID)
Expand All @@ -162,6 +168,28 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
DDLogInfo("✅ POSCatalogSyncCoordinator completed incremental sync for site \(siteID)")
}

private func shouldPerformIncrementalSync(for siteID: Int64, maxAge: TimeInterval, maxCatalogSize: Int) async -> Bool {
guard await isCatalogSizeWithinLimit(for: siteID, maxCatalogSize: maxCatalogSize) else {
return false
}

guard await lastFullSyncDate(for: siteID) != nil else {
DDLogInfo("📋 POSCatalogSyncCoordinator: No full sync performed yet for site \(siteID), skipping incremental sync")
return false
}

if maxAge > 0, let lastIncrementalSyncDate = await lastIncrementalSyncDate(for: siteID) {
let age = Date().timeIntervalSince(lastIncrementalSyncDate)

if age <= maxAge {
DDLogInfo("📋 POSCatalogSyncCoordinator: Last incremental sync for site \(siteID) was \(Int(age))s ago, sync not needed")
return false
}
}

return true
}

// MARK: - Private

/// Checks if the catalog size is within the specified sync limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ final class MockPOSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
var performFullSyncResult: Result<Void, Error> = .success(())
var shouldDelayResponse = false

func shouldPerformFullSync(for siteID: Int64, maxAge: TimeInterval) async -> Bool {
true
}

func performFullSync(for siteID: Int64) async throws {
func performFullSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {
if shouldDelayResponse {
try await Task.sleep(for: .milliseconds(100))
}
Expand All @@ -27,5 +23,5 @@ final class MockPOSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
}
}

func performIncrementalSyncIfApplicable(for siteID: Int64, forceSync: Bool) async throws {}
func performIncrementalSyncIfApplicable(for siteID: Int64, maxAge: TimeInterval) async throws {}
}
Loading