Skip to content

Commit d51ff90

Browse files
committed
Add sync progress APIs
1 parent 916211d commit d51ff90

File tree

6 files changed

+134
-9
lines changed

6 files changed

+134
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## unreleased
4+
5+
- Add sync progress information through `SyncStatusData.downloadProgress`.
6+
37
# 1.0.0
48

59
- Improved the stability of watched queries. Watched queries were previously susceptible to runtime crashes if an exception was thrown in the update stream. Errors are now gracefully handled.

Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"originHash" : "33297127250b66812faa920958a24bae46bf9e9d1c38ea6b84ca413efaf16afd",
2+
"originHash" : "2d885a1b46f17f9239b7876e3889168a6de98024718f2d7af03aede290c8a86a",
33
"pins" : [
44
{
55
"identity" : "anycodable",
@@ -10,6 +10,15 @@
1010
"version" : "0.6.7"
1111
}
1212
},
13+
{
14+
"identity" : "powersync-kotlin",
15+
"kind" : "remoteSourceControl",
16+
"location" : "https://github.com/powersync-ja/powersync-kotlin.git",
17+
"state" : {
18+
"revision" : "ccd2e595195c59d570eb93a878ad6a5cfca72ada",
19+
"version" : "1.0.1+SWIFT.0"
20+
}
21+
},
1322
{
1423
"identity" : "powersync-sqlite-core-swift",
1524
"kind" : "remoteSourceControl",

Demo/PowerSyncExample/Components/ListView.swift

+30-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import SwiftUI
22
import IdentifiedCollections
33
import SwiftUINavigation
4+
import PowerSync
45

56
struct ListView: View {
67
@Environment(SystemManager.self) private var system
@@ -9,18 +10,32 @@ struct ListView: View {
910
@State private var error: Error?
1011
@State private var newList: NewListContent?
1112
@State private var editing: Bool = false
12-
@State private var didSync: Bool = false
13+
@State private var status: SyncStatusData? = nil
1314

1415
var body: some View {
15-
if !didSync {
16-
Text("Busy with sync!").task {
17-
do {
18-
try await system.db.waitForFirstSync(priority: 1)
19-
didSync = true;
20-
} catch {}
16+
if status?.hasSynced != true {
17+
VStack {
18+
if let status = self.status {
19+
if status.hasSynced != true {
20+
Text("Busy with initial sync...")
21+
22+
if let progress = status.downloadProgress {
23+
ProgressView(value: progress.fraction)
24+
25+
if progress.downloadedOperations == progress.totalOperations {
26+
Text("Applying server-side changes...")
27+
} else {
28+
Text("Downloaded \(progress.downloadedOperations) out of \(progress.totalOperations)")
29+
}
30+
}
31+
}
32+
} else {
33+
ProgressView()
34+
.progressViewStyle(CircularProgressViewStyle())
35+
}
2136
}
2237
}
23-
38+
2439
List {
2540
if let error {
2641
ErrorText(error)
@@ -79,6 +94,13 @@ struct ListView: View {
7994
}
8095
}
8196
}
97+
.task {
98+
self.status = system.db.currentStatus
99+
100+
for await status in system.db.currentStatus.asFlow() {
101+
self.status = status
102+
}
103+
}
82104
}
83105

84106
func handleDelete(at offset: IndexSet) async {

Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift

+35
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ extension KotlinSyncStatusDataProtocol {
3737
)
3838
}
3939

40+
var downloadProgress: (any SyncDownloadProgress)? {
41+
guard let kotlinProgress = base.downloadProgress else { return nil }
42+
return KotlinSyncDownloadProgress(progress: kotlinProgress)
43+
}
44+
4045
var hasSynced: Bool? {
4146
base.hasSynced?.boolValue
4247
}
@@ -80,3 +85,33 @@ extension KotlinSyncStatusDataProtocol {
8085
)
8186
}
8287
}
88+
89+
protocol KotlinProgressWithOperationsProtocol: ProgressWithOperations {
90+
var base: any PowerSyncKotlin.ProgressWithOperations { get }
91+
}
92+
93+
extension KotlinProgressWithOperationsProtocol {
94+
var totalOperations: Int32 {
95+
return base.totalOperations
96+
}
97+
98+
var downloadedOperations: Int32 {
99+
return base.downloadedOperations
100+
}
101+
}
102+
103+
struct KotlinProgressWithOperations: KotlinProgressWithOperationsProtocol {
104+
let base: PowerSyncKotlin.ProgressWithOperations
105+
}
106+
107+
struct KotlinSyncDownloadProgress: KotlinProgressWithOperationsProtocol, SyncDownloadProgress {
108+
let progress: PowerSyncKotlin.SyncDownloadProgress
109+
110+
var base: any PowerSyncKotlin.ProgressWithOperations {
111+
progress
112+
}
113+
114+
func untilPriority(priority: BucketPriority) -> any ProgressWithOperations {
115+
return KotlinProgressWithOperations(base: progress.untilPriority(priority: priority.priorityCode))
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/// Information about a progressing download.
2+
///
3+
/// This reports the ``totalOperations`` amount of operations to download, how many of them
4+
/// have already been downloaded as ``downloadedOperations`` and finally a ``fraction`` indicating
5+
/// relative progress.
6+
///
7+
/// To obtain a ``ProgressWithOperations`` instance, either use ``SyncStatusData/downloadProgress``
8+
/// for global progress or ``SyncDownloadProgress/untilPriority``.
9+
public protocol ProgressWithOperations {
10+
/// How many operations need to be downloaded in total for the current download
11+
/// to complete.
12+
var totalOperations: Int32 { get }
13+
14+
/// How many operations, out of ``totalOperations``, have already been downloaded.
15+
var downloadedOperations: Int32 { get }
16+
}
17+
18+
public extension ProgressWithOperations {
19+
var fraction: Float {
20+
if (self.totalOperations == 0) {
21+
return 0.0
22+
}
23+
24+
return Float.init(self.downloadedOperations) / Float.init(self.totalOperations)
25+
}
26+
}
27+
28+
/// Provides realtime progress on how PowerSync is downloading rows.
29+
///
30+
/// This type reports progress by extending ``ProgressWithOperations``, meaning that the
31+
/// ``totalOperations``, ``downloadedOperations`` and ``fraction`` properties are available
32+
/// on this instance.
33+
/// Additionally, it's possible to obtain progress towards a specific priority only (instead
34+
/// of tracking progress for the entire download) by using ``untilPriority``.
35+
///
36+
/// The reported progress always reflects the status towards the end of a sync iteration (after
37+
/// which a consistent snapshot of all buckets is available locally).
38+
///
39+
/// In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
40+
/// operation takes place between syncs), it's possible for the returned numbers to be slightly
41+
/// inaccurate. For this reason, ``SyncDownloadProgress`` should be seen as an approximation of progress.
42+
/// The information returned is good enough to build progress bars, but not exaxt enough to track
43+
/// individual download counts.
44+
///
45+
/// Also note that data is downloaded in bulk, which means that individual counters are unlikely
46+
/// to be updated one-by-one.
47+
public protocol SyncDownloadProgress: ProgressWithOperations {
48+
func untilPriority(priority: BucketPriority) -> ProgressWithOperations
49+
}

Sources/PowerSync/Protocol/sync/SyncStatusData.swift

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ public protocol SyncStatusData {
1111
/// Indicates whether the system is actively downloading changes.
1212
var downloading: Bool { get }
1313

14+
/// Realtime progress information about downloaded operations during an active sync.
15+
///
16+
/// For more information on what progress is reported, see ``SyncDownloadProgress``.
17+
/// This value will be non-null only if ``downloading`` is `true`.
18+
var downloadProgress: SyncDownloadProgress? { get }
19+
1420
/// Indicates whether the system is actively uploading changes.
1521
var uploading: Bool { get }
1622

0 commit comments

Comments
 (0)