From f375452194566a3601b273b47568851e2c45a7fa Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:18:42 -0500 Subject: [PATCH 01/14] Rough draft of sync status helper tool --- Package.swift | 12 ++++- .../DittoSyncSubscription+Status.swift | 48 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift diff --git a/Package.swift b/Package.swift index 54de2b4..42dbd7f 100644 --- a/Package.swift +++ b/Package.swift @@ -48,6 +48,9 @@ let package = Package( .library( name: "DittoPermissionsHealth", targets: ["DittoPermissionsHealth"]), + .library( + name: "DittoSyncStatus", + targets: ["DittoSyncStatus"]), .library( name: "DittoAllToolsMenu", targets: ["DittoAllToolsMenu"]), @@ -125,6 +128,12 @@ let package = Package( "DittoHealthMetrics" ] ), + .target( + name: "DittoSyncStatus", + dependencies: [ + .product(name: "DittoSwift", package: "DittoSwiftPackage") + ] + ), .target( name: "DittoAllToolsMenu", dependencies: [ @@ -137,7 +146,8 @@ let package = Package( "DittoExportData", "DittoPresenceDegradation", "DittoHeartbeat", - "DittoPermissionsHealth" + "DittoPermissionsHealth", + "DittoSyncStatus" ] ) diff --git a/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift b/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift new file mode 100644 index 0000000..f5544d5 --- /dev/null +++ b/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift @@ -0,0 +1,48 @@ +// +// DittoSyncSubscription+Status.swift +// DittoSwiftTools +// +// Created by Brian Plattenburg on 11/16/24. +// + + +import DittoSwift + +public enum DittoSyncSubscriptionStatus { + case idle + case syncing +} + +public class DittoSyncSubscriptionHelper { + public let idleTimeoutInterval: TimeInterval = 5 // 5 seconds by default + + private let subscriptions: [DittoSyncSubscription] + private var observers: [DittoStoreObserver] = [] + + private var lastUpdated: Date = .distantPast + + init(ditto: Ditto, subscriptions: [DittoSyncSubscription]) throws { + self.subscriptions = subscriptions + self.observers = try subscriptions.map { subscription in + try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) + } + } + + deinit { + observers.forEach { observer in + observer.cancel() + } + } + + public var status: DittoSyncSubscriptionStatus { + if Date().timeIntervalSince(lastUpdated) > idleTimeoutInterval { + return .idle + } else { + return .syncing + } + } + + private func handleObserver(_ result: DittoSwift.DittoQueryResult) { + lastUpdated = Date() + } +} From a58cf817d1ace77fc8fae17f274b9d32911de8c3 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:46:30 -0500 Subject: [PATCH 02/14] Add handler --- .../DittoSyncSubscription+Status.swift | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift b/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift index f5544d5..fdaa874 100644 --- a/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift +++ b/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift @@ -8,6 +8,8 @@ import DittoSwift +public typealias DittoSyncSubscriptionStatusHandler = (_ result: DittoSyncSubscriptionStatus) -> Void + public enum DittoSyncSubscriptionStatus { case idle case syncing @@ -15,14 +17,23 @@ public enum DittoSyncSubscriptionStatus { public class DittoSyncSubscriptionHelper { public let idleTimeoutInterval: TimeInterval = 5 // 5 seconds by default + public var status: DittoSyncSubscriptionStatus { + didSet { + guard oldValue != status else { return } + handler(status) + } + } private let subscriptions: [DittoSyncSubscription] + private let handler: DittoSyncSubscriptionStatusHandler private var observers: [DittoStoreObserver] = [] private var lastUpdated: Date = .distantPast - init(ditto: Ditto, subscriptions: [DittoSyncSubscription]) throws { + init(ditto: Ditto, subscriptions: [DittoSyncSubscription], handler: @escaping DittoSyncSubscriptionStatusHandler) throws { self.subscriptions = subscriptions + self.handler = handler + self.status = .idle self.observers = try subscriptions.map { subscription in try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) } @@ -34,15 +45,12 @@ public class DittoSyncSubscriptionHelper { } } - public var status: DittoSyncSubscriptionStatus { + private func handleObserver(_ result: DittoSwift.DittoQueryResult) { + lastUpdated = Date() if Date().timeIntervalSince(lastUpdated) > idleTimeoutInterval { - return .idle + status = .idle } else { - return .syncing + status = .syncing } } - - private func handleObserver(_ result: DittoSwift.DittoQueryResult) { - lastUpdated = Date() - } } From 6827c45bf8cd6b43e784eab91b64761acb378fd3 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:48:39 -0500 Subject: [PATCH 03/14] Naming is hard --- Package.swift | 8 ++++---- .../DittoSyncStatusHelper} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename Sources/{DittoSyncStatus/DittoSyncSubscription+Status.swift => DittoSyncStatusHelper/DittoSyncStatusHelper} (94%) diff --git a/Package.swift b/Package.swift index 42dbd7f..0346989 100644 --- a/Package.swift +++ b/Package.swift @@ -49,8 +49,8 @@ let package = Package( name: "DittoPermissionsHealth", targets: ["DittoPermissionsHealth"]), .library( - name: "DittoSyncStatus", - targets: ["DittoSyncStatus"]), + name: "DittoSyncStatusHelper", + targets: ["DittoSyncStatusHelper"]), .library( name: "DittoAllToolsMenu", targets: ["DittoAllToolsMenu"]), @@ -129,7 +129,7 @@ let package = Package( ] ), .target( - name: "DittoSyncStatus", + name: "DittoSyncStatusHelper", dependencies: [ .product(name: "DittoSwift", package: "DittoSwiftPackage") ] @@ -147,7 +147,7 @@ let package = Package( "DittoPresenceDegradation", "DittoHeartbeat", "DittoPermissionsHealth", - "DittoSyncStatus" + "DittoSyncStatusHelper" ] ) diff --git a/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper similarity index 94% rename from Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift rename to Sources/DittoSyncStatusHelper/DittoSyncStatusHelper index fdaa874..1ec0115 100644 --- a/Sources/DittoSyncStatus/DittoSyncSubscription+Status.swift +++ b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper @@ -1,5 +1,5 @@ // -// DittoSyncSubscription+Status.swift +// DittoSyncStatusHelper.swift // DittoSwiftTools // // Created by Brian Plattenburg on 11/16/24. @@ -15,7 +15,7 @@ public enum DittoSyncSubscriptionStatus { case syncing } -public class DittoSyncSubscriptionHelper { +public class DittoSyncStatusHelper { public let idleTimeoutInterval: TimeInterval = 5 // 5 seconds by default public var status: DittoSyncSubscriptionStatus { didSet { From 2361f7f8c3a24a5573d7493ecbcc9b5269a61494 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:13:09 -0500 Subject: [PATCH 04/14] Poll for updates --- Package.swift | 8 +++---- .../DittoSyncStatusHelper.swift} | 24 +++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) rename Sources/{DittoSyncStatusHelper/DittoSyncStatusHelper => DittoSyncStatus/DittoSyncStatusHelper.swift} (69%) diff --git a/Package.swift b/Package.swift index 0346989..42dbd7f 100644 --- a/Package.swift +++ b/Package.swift @@ -49,8 +49,8 @@ let package = Package( name: "DittoPermissionsHealth", targets: ["DittoPermissionsHealth"]), .library( - name: "DittoSyncStatusHelper", - targets: ["DittoSyncStatusHelper"]), + name: "DittoSyncStatus", + targets: ["DittoSyncStatus"]), .library( name: "DittoAllToolsMenu", targets: ["DittoAllToolsMenu"]), @@ -129,7 +129,7 @@ let package = Package( ] ), .target( - name: "DittoSyncStatusHelper", + name: "DittoSyncStatus", dependencies: [ .product(name: "DittoSwift", package: "DittoSwiftPackage") ] @@ -147,7 +147,7 @@ let package = Package( "DittoPresenceDegradation", "DittoHeartbeat", "DittoPermissionsHealth", - "DittoSyncStatusHelper" + "DittoSyncStatus" ] ) diff --git a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper b/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift similarity index 69% rename from Sources/DittoSyncStatusHelper/DittoSyncStatusHelper rename to Sources/DittoSyncStatus/DittoSyncStatusHelper.swift index 1ec0115..d7bc5e4 100644 --- a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper +++ b/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift @@ -16,7 +16,8 @@ public enum DittoSyncSubscriptionStatus { } public class DittoSyncStatusHelper { - public let idleTimeoutInterval: TimeInterval = 5 // 5 seconds by default + public var idleTimeoutInterval: TimeInterval = 5 + public var status: DittoSyncSubscriptionStatus { didSet { guard oldValue != status else { return } @@ -26,31 +27,44 @@ public class DittoSyncStatusHelper { private let subscriptions: [DittoSyncSubscription] private let handler: DittoSyncSubscriptionStatusHandler - private var observers: [DittoStoreObserver] = [] + private let pollingInterval: TimeInterval + private var timer: Timer? = nil + private var observers: [DittoStoreObserver] = [] private var lastUpdated: Date = .distantPast - init(ditto: Ditto, subscriptions: [DittoSyncSubscription], handler: @escaping DittoSyncSubscriptionStatusHandler) throws { + init(ditto: Ditto, + subscriptions: [DittoSyncSubscription], + pollingInterval: TimeInterval = 0.1, + handler: @escaping DittoSyncSubscriptionStatusHandler) throws { self.subscriptions = subscriptions self.handler = handler self.status = .idle + self.pollingInterval = pollingInterval + self.timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true, block: { [weak self] _ in + self?.updateStatus() + }) self.observers = try subscriptions.map { subscription in try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) } } deinit { + timer?.invalidate() observers.forEach { observer in observer.cancel() } } - private func handleObserver(_ result: DittoSwift.DittoQueryResult) { - lastUpdated = Date() + private func updateStatus() { if Date().timeIntervalSince(lastUpdated) > idleTimeoutInterval { status = .idle } else { status = .syncing } } + + private func handleObserver(_ result: DittoSwift.DittoQueryResult) { + lastUpdated = Date() + } } From 68ef8e3b0c2cc6e78721ef7f6e810f911e47456a Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:23:54 -0500 Subject: [PATCH 05/14] doc comments --- .../DittoSyncStatus/DittoSyncStatusHelper.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift index d7bc5e4..157dd0b 100644 --- a/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift +++ b/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift @@ -10,11 +10,19 @@ import DittoSwift public typealias DittoSyncSubscriptionStatusHandler = (_ result: DittoSyncSubscriptionStatus) -> Void +/// A status that describes whether a set of `DittoSyncSubscription`s is syncing or idle. +/// This can be combined with an online / offline check to provide an approximation of whether this subscription is up to date public enum DittoSyncSubscriptionStatus { case idle case syncing } +/** + A helper which provide the sync status of a set of DittoSyncSubscriptions, either idle or syncing. + This tells you if this peer is actively receiving data about this subscription from connected peers or idling + It can be used to provide an approximation of whether this peer is up to date with other connected peers. + It works by creating local store observers for each passed in subscription, then tracking when they fire and comparing against the `idleTimeoutInterval` + */ public class DittoSyncStatusHelper { public var idleTimeoutInterval: TimeInterval = 5 @@ -33,6 +41,15 @@ public class DittoSyncStatusHelper { private var observers: [DittoStoreObserver] = [] private var lastUpdated: Date = .distantPast + /** + Creates a new` DittoSyncStatusHelper` for a given set of `DittoSyncSubscription`s + - Parameters: + - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query.. + - idleTimeoutInterval: How long after the last update is received before this subscription is considered `idle`. Defaults to 5 seconds. + - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. + - pollingInterval: How often to provide status updates. Defaults to 0.1 seconds. + - handler: A closure called each time the `status` changes. + */ init(ditto: Ditto, subscriptions: [DittoSyncSubscription], pollingInterval: TimeInterval = 0.1, From 73972cee790822ad60ef065399fe56aede3664f6 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:34:12 -0500 Subject: [PATCH 06/14] Fix naming to align --- Package.swift | 8 ++++---- .../DittoSyncStatusHelper.swift | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Sources/{DittoSyncStatus => DittoSyncStatusHelper}/DittoSyncStatusHelper.swift (100%) diff --git a/Package.swift b/Package.swift index 42dbd7f..0346989 100644 --- a/Package.swift +++ b/Package.swift @@ -49,8 +49,8 @@ let package = Package( name: "DittoPermissionsHealth", targets: ["DittoPermissionsHealth"]), .library( - name: "DittoSyncStatus", - targets: ["DittoSyncStatus"]), + name: "DittoSyncStatusHelper", + targets: ["DittoSyncStatusHelper"]), .library( name: "DittoAllToolsMenu", targets: ["DittoAllToolsMenu"]), @@ -129,7 +129,7 @@ let package = Package( ] ), .target( - name: "DittoSyncStatus", + name: "DittoSyncStatusHelper", dependencies: [ .product(name: "DittoSwift", package: "DittoSwiftPackage") ] @@ -147,7 +147,7 @@ let package = Package( "DittoPresenceDegradation", "DittoHeartbeat", "DittoPermissionsHealth", - "DittoSyncStatus" + "DittoSyncStatusHelper" ] ) diff --git a/Sources/DittoSyncStatus/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift similarity index 100% rename from Sources/DittoSyncStatus/DittoSyncStatusHelper.swift rename to Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift From e6f5da0f7a52e2e75e2b7b74edbc0e7f413cc63d Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:53:09 -0500 Subject: [PATCH 07/14] Schedule timer on update instead of polling repeatedly --- .../DittoSyncStatusHelper.swift | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift index 157dd0b..ee6c9f4 100644 --- a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift @@ -35,7 +35,6 @@ public class DittoSyncStatusHelper { private let subscriptions: [DittoSyncSubscription] private let handler: DittoSyncSubscriptionStatusHandler - private let pollingInterval: TimeInterval private var timer: Timer? = nil private var observers: [DittoStoreObserver] = [] @@ -47,20 +46,14 @@ public class DittoSyncStatusHelper { - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query.. - idleTimeoutInterval: How long after the last update is received before this subscription is considered `idle`. Defaults to 5 seconds. - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. - - pollingInterval: How often to provide status updates. Defaults to 0.1 seconds. - handler: A closure called each time the `status` changes. */ init(ditto: Ditto, subscriptions: [DittoSyncSubscription], - pollingInterval: TimeInterval = 0.1, handler: @escaping DittoSyncSubscriptionStatusHandler) throws { self.subscriptions = subscriptions self.handler = handler self.status = .idle - self.pollingInterval = pollingInterval - self.timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true, block: { [weak self] _ in - self?.updateStatus() - }) self.observers = try subscriptions.map { subscription in try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) } @@ -73,15 +66,12 @@ public class DittoSyncStatusHelper { } } - private func updateStatus() { - if Date().timeIntervalSince(lastUpdated) > idleTimeoutInterval { - status = .idle - } else { - status = .syncing - } - } - private func handleObserver(_ result: DittoSwift.DittoQueryResult) { + status = .syncing lastUpdated = Date() + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: idleTimeoutInterval, repeats: false, block: { [weak self] _ in + self?.status = .idle + }) } } From 0460f93d968d0aa3015b3b82af0e01ac5158a9b4 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:55:14 -0500 Subject: [PATCH 08/14] Always send an initial handler callback for the default .idle status --- Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift index ee6c9f4..a87dde8 100644 --- a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift @@ -26,7 +26,7 @@ public enum DittoSyncSubscriptionStatus { public class DittoSyncStatusHelper { public var idleTimeoutInterval: TimeInterval = 5 - public var status: DittoSyncSubscriptionStatus { + public var status: DittoSyncSubscriptionStatus = .idle { didSet { guard oldValue != status else { return } handler(status) @@ -53,7 +53,7 @@ public class DittoSyncStatusHelper { handler: @escaping DittoSyncSubscriptionStatusHandler) throws { self.subscriptions = subscriptions self.handler = handler - self.status = .idle + handler(.idle) self.observers = try subscriptions.map { subscription in try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) } From 421b99f4b1302a2a86e5e7d2696de495d8818e0a Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:55:42 -0500 Subject: [PATCH 09/14] Update default to 1s --- Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift index a87dde8..276e2fa 100644 --- a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift @@ -24,7 +24,7 @@ public enum DittoSyncSubscriptionStatus { It works by creating local store observers for each passed in subscription, then tracking when they fire and comparing against the `idleTimeoutInterval` */ public class DittoSyncStatusHelper { - public var idleTimeoutInterval: TimeInterval = 5 + public var idleTimeoutInterval: TimeInterval = 1 public var status: DittoSyncSubscriptionStatus = .idle { didSet { @@ -44,7 +44,7 @@ public class DittoSyncStatusHelper { Creates a new` DittoSyncStatusHelper` for a given set of `DittoSyncSubscription`s - Parameters: - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query.. - - idleTimeoutInterval: How long after the last update is received before this subscription is considered `idle`. Defaults to 5 seconds. + - idleTimeoutInterval: How long after the last update is received before this subscription is considered `idle`. Defaults to 1 second. - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. - handler: A closure called each time the `status` changes. */ From 2874d45f8a3f8f7be4af45a0e1af5262ba6e9422 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:19:50 -0500 Subject: [PATCH 10/14] Cleanup --- ...sHelper.swift => DittoSubscriptionsStatusHelper.swift} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename Sources/DittoSyncStatusHelper/{DittoSyncStatusHelper.swift => DittoSubscriptionsStatusHelper.swift} (93%) diff --git a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift similarity index 93% rename from Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift rename to Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift index 276e2fa..57df0f3 100644 --- a/Sources/DittoSyncStatusHelper/DittoSyncStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift @@ -8,11 +8,11 @@ import DittoSwift -public typealias DittoSyncSubscriptionStatusHandler = (_ result: DittoSyncSubscriptionStatus) -> Void +public typealias DittoSyncSubscriptionStatusHandler = (_ result: DittoSyncSubscriptionsStatus) -> Void /// A status that describes whether a set of `DittoSyncSubscription`s is syncing or idle. /// This can be combined with an online / offline check to provide an approximation of whether this subscription is up to date -public enum DittoSyncSubscriptionStatus { +public enum DittoSyncSubscriptionsStatus: String { case idle case syncing } @@ -23,10 +23,10 @@ public enum DittoSyncSubscriptionStatus { It can be used to provide an approximation of whether this peer is up to date with other connected peers. It works by creating local store observers for each passed in subscription, then tracking when they fire and comparing against the `idleTimeoutInterval` */ -public class DittoSyncStatusHelper { +public class DittoSubscriptionsStatusHelper { public var idleTimeoutInterval: TimeInterval = 1 - public var status: DittoSyncSubscriptionStatus = .idle { + public var status: DittoSyncSubscriptionsStatus = .idle { didSet { guard oldValue != status else { return } handler(status) From 76763c059b5d79db85022166a6d068434fccd180 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:14:12 -0500 Subject: [PATCH 11/14] Make sure to use arguments from the subscription in the live query --- .../DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift index 57df0f3..fb3b1a9 100644 --- a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift @@ -55,7 +55,7 @@ public class DittoSubscriptionsStatusHelper { self.handler = handler handler(.idle) self.observers = try subscriptions.map { subscription in - try ditto.store.registerObserver(query: subscription.queryString, handler: handleObserver) + try ditto.store.registerObserver(query: subscription.queryString, arguments: subscription.queryArguments, handler: handleObserver) } } From b9b70982c2b0acba273c263ed934366c362f21cc Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:43:15 -0500 Subject: [PATCH 12/14] Doc comment cleanup, updated init paths to allow users to set this up based on the current complete set of subscriptions --- .../DittoSubscriptionsStatusHelper.swift | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift index fb3b1a9..ff8aae1 100644 --- a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift @@ -24,6 +24,7 @@ public enum DittoSyncSubscriptionsStatus: String { It works by creating local store observers for each passed in subscription, then tracking when they fire and comparing against the `idleTimeoutInterval` */ public class DittoSubscriptionsStatusHelper { + /// The interval after which a subscription is considered to be idle. Defaults to 1 second. public var idleTimeoutInterval: TimeInterval = 1 public var status: DittoSyncSubscriptionsStatus = .idle { @@ -33,7 +34,7 @@ public class DittoSubscriptionsStatusHelper { } } - private let subscriptions: [DittoSyncSubscription] + private let subscriptions: Set private let handler: DittoSyncSubscriptionStatusHandler private var timer: Timer? = nil @@ -43,13 +44,12 @@ public class DittoSubscriptionsStatusHelper { /** Creates a new` DittoSyncStatusHelper` for a given set of `DittoSyncSubscription`s - Parameters: - - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query.. - - idleTimeoutInterval: How long after the last update is received before this subscription is considered `idle`. Defaults to 1 second. + - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. - handler: A closure called each time the `status` changes. */ init(ditto: Ditto, - subscriptions: [DittoSyncSubscription], + subscriptions: Set, handler: @escaping DittoSyncSubscriptionStatusHandler) throws { self.subscriptions = subscriptions self.handler = handler @@ -59,6 +59,22 @@ public class DittoSubscriptionsStatusHelper { } } + /** + Creates a new` DittoSyncStatusHelper` for all of the currently active subscriptions on this Ditto instance *at the time this is created*. It will not update if those subscriptions change + - Parameters: + - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. + - handler: A closure called each time the `status` changes. + */ + init(ditto: Ditto, + handler: @escaping DittoSyncSubscriptionStatusHandler) throws { + self.subscriptions = ditto.sync.subscriptions + self.handler = handler + handler(.idle) + self.observers = try subscriptions.map { subscription in + try ditto.store.registerObserver(query: subscription.queryString, arguments: subscription.queryArguments, handler: handleObserver) + } + } + deinit { timer?.invalidate() observers.forEach { observer in From 14f90c9f39e2a1771baf2770841b1962d8dede57 Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:42:07 -0500 Subject: [PATCH 13/14] Fix comment whitespace --- .../DittoSubscriptionsStatusHelper.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift index ff8aae1..e36410e 100644 --- a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift @@ -42,7 +42,7 @@ public class DittoSubscriptionsStatusHelper { private var lastUpdated: Date = .distantPast /** - Creates a new` DittoSyncStatusHelper` for a given set of `DittoSyncSubscription`s + Creates a new `DittoSyncStatusHelper` for a given set of `DittoSyncSubscription`s - Parameters: - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. @@ -60,7 +60,7 @@ public class DittoSubscriptionsStatusHelper { } /** - Creates a new` DittoSyncStatusHelper` for all of the currently active subscriptions on this Ditto instance *at the time this is created*. It will not update if those subscriptions change + Creates a new `DittoSyncStatusHelper` for all of the currently active subscriptions on this Ditto instance *at the time this is created*. It will not update if those subscriptions change - Parameters: - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. - handler: A closure called each time the `status` changes. From fe7c0e519ab0ad948efb59ad392009dbdae5245c Mon Sep 17 00:00:00 2001 From: Brian Plattenburg <5767019+bplattenburg@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:51:03 -0500 Subject: [PATCH 14/14] Make status publish via Combine, cleanup duplicated logic, more doc comments --- .../DittoSubscriptionsStatusHelper.swift | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift index e36410e..938bafb 100644 --- a/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift +++ b/Sources/DittoSyncStatusHelper/DittoSubscriptionsStatusHelper.swift @@ -5,7 +5,7 @@ // Created by Brian Plattenburg on 11/16/24. // - +import Combine import DittoSwift public typealias DittoSyncSubscriptionStatusHandler = (_ result: DittoSyncSubscriptionsStatus) -> Void @@ -27,15 +27,17 @@ public class DittoSubscriptionsStatusHelper { /// The interval after which a subscription is considered to be idle. Defaults to 1 second. public var idleTimeoutInterval: TimeInterval = 1 - public var status: DittoSyncSubscriptionsStatus = .idle { + /// The current status for the total set of subscriptions monitored by this helper. This is both `@Published` + /// fired to `handler` via `didSet` when the value changes. + @Published public private(set) var status: DittoSyncSubscriptionsStatus = .idle { didSet { guard oldValue != status else { return } - handler(status) + handler?(status) } } private let subscriptions: Set - private let handler: DittoSyncSubscriptionStatusHandler + private let handler: DittoSyncSubscriptionStatusHandler? private var timer: Timer? = nil private var observers: [DittoStoreObserver] = [] @@ -46,14 +48,12 @@ public class DittoSubscriptionsStatusHelper { - Parameters: - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. - subscriptions: Which subscriptions to include for this status helper. The aggregate status for all of them will be tracked here, such that it is only considered `idle` if all subscriptions are `idle`. - - handler: A closure called each time the `status` changes. + - handler: An closure called each time the `status` changes. Defaults to `nil` */ - init(ditto: Ditto, - subscriptions: Set, - handler: @escaping DittoSyncSubscriptionStatusHandler) throws { + init(ditto: Ditto, subscriptions: Set, handler: DittoSyncSubscriptionStatusHandler? = nil) throws { self.subscriptions = subscriptions self.handler = handler - handler(.idle) + handler?(.idle) self.observers = try subscriptions.map { subscription in try ditto.store.registerObserver(query: subscription.queryString, arguments: subscription.queryArguments, handler: handleObserver) } @@ -65,14 +65,8 @@ public class DittoSubscriptionsStatusHelper { - ditto: A Ditto instance for which sync status is being checked. Used internally to create `DittoStoreObserver`s tracking each query. - handler: A closure called each time the `status` changes. */ - init(ditto: Ditto, - handler: @escaping DittoSyncSubscriptionStatusHandler) throws { - self.subscriptions = ditto.sync.subscriptions - self.handler = handler - handler(.idle) - self.observers = try subscriptions.map { subscription in - try ditto.store.registerObserver(query: subscription.queryString, arguments: subscription.queryArguments, handler: handleObserver) - } + convenience init(ditto: Ditto, handler: DittoSyncSubscriptionStatusHandler?) throws { + try self.init(ditto: ditto, subscriptions: ditto.sync.subscriptions, handler: handler) } deinit {