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
37 changes: 19 additions & 18 deletions Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
CA6A1D242E68A0A600604D6A /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = CA6A1D232E68A0A600604D6A /* SQLiteData */; };
CAD001872D874F1F00FA977A /* DependenciesTestSupport in Frameworks */ = {isa = PBXBuildFile; productRef = CAD001862D874F1F00FA977A /* DependenciesTestSupport */; };
DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */ = {isa = PBXBuildFile; productRef = DC5FA7472D4C63D60082743E /* DependenciesMacros */; };
DC9A3DDE2E6A280700DE41FB /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DC9A3DDD2E6A280700DE41FB /* SQLiteData */; };
DC9A3DE02E6A280F00DE41FB /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = DC9A3DDF2E6A280F00DE41FB /* SQLiteData */; };
DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */ = {isa = PBXBuildFile; productRef = DCBE8A132D4842BF0071F499 /* CasePaths */; };
DCD9AC8B2E02176700FB20F8 /* SharingGRDB in Frameworks */ = {isa = PBXBuildFile; productRef = DCD9AC8A2E02176700FB20F8 /* SharingGRDB */; };
DCD9AC8F2E02177900FB20F8 /* SharingGRDB in Frameworks */ = {isa = PBXBuildFile; productRef = DCD9AC8E2E02177900FB20F8 /* SharingGRDB */; };
DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */ = {isa = PBXBuildFile; productRef = DCF267382D48437300B680BE /* SwiftUINavigation */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -147,7 +147,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DCD9AC8B2E02176700FB20F8 /* SharingGRDB in Frameworks */,
DC9A3DDE2E6A280700DE41FB /* SQLiteData in Frameworks */,
CA2908C92D4AF70E003F165F /* UIKitNavigation in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -175,7 +175,7 @@
files = (
DCF267392D48437300B680BE /* SwiftUINavigation in Frameworks */,
DC5FA7482D4C63D60082743E /* DependenciesMacros in Frameworks */,
DCD9AC8F2E02177900FB20F8 /* SharingGRDB in Frameworks */,
DC9A3DE02E6A280F00DE41FB /* SQLiteData in Frameworks */,
DCBE8A142D4842BF0071F499 /* CasePaths in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -289,7 +289,7 @@
name = CaseStudies;
packageProductDependencies = (
CA2908C82D4AF70E003F165F /* UIKitNavigation */,
DCD9AC8A2E02176700FB20F8 /* SharingGRDB */,
DC9A3DDD2E6A280700DE41FB /* SQLiteData */,
);
productName = Examples;
productReference = CAF836982D4735620047AEB5 /* CaseStudies.app */;
Expand Down Expand Up @@ -363,7 +363,7 @@
DCBE8A132D4842BF0071F499 /* CasePaths */,
DCF267382D48437300B680BE /* SwiftUINavigation */,
DC5FA7472D4C63D60082743E /* DependenciesMacros */,
DCD9AC8E2E02177900FB20F8 /* SharingGRDB */,
DC9A3DDF2E6A280F00DE41FB /* SQLiteData */,
);
productName = SyncUps;
productReference = DCBE89CC2D483FB90071F499 /* SyncUps.app */;
Expand Down Expand Up @@ -416,7 +416,7 @@
DCF267372D48437300B680BE /* XCRemoteSwiftPackageReference "swift-navigation" */,
DC5FA7462D4C63D60082743E /* XCRemoteSwiftPackageReference "swift-dependencies" */,
CA5E47052DECEF0F0069E0F8 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference "../../sharing-grdb" */,
DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference ".." */,
);
preferredProjectObjectVersion = 77;
productRefGroup = CAF836992D4735620047AEB5 /* Products */;
Expand Down Expand Up @@ -1004,9 +1004,9 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference "../../sharing-grdb" */ = {
DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference ".." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "../../sharing-grdb";
relativePath = "..";
};
/* End XCLocalSwiftPackageReference section */

Expand Down Expand Up @@ -1078,7 +1078,7 @@
};
CA6A1D232E68A0A600604D6A /* SQLiteData */ = {
isa = XCSwiftPackageProductDependency;
package = DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference "../../sharing-grdb" */;
package = DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference ".." */;
productName = SQLiteData;
};
CAD001862D874F1F00FA977A /* DependenciesTestSupport */ = {
Expand All @@ -1091,19 +1091,20 @@
package = DC5FA7462D4C63D60082743E /* XCRemoteSwiftPackageReference "swift-dependencies" */;
productName = DependenciesMacros;
};
DCBE8A132D4842BF0071F499 /* CasePaths */ = {
DC9A3DDD2E6A280700DE41FB /* SQLiteData */ = {
isa = XCSwiftPackageProductDependency;
package = DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */;
productName = CasePaths;
package = DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference ".." */;
productName = SQLiteData;
};
DCD9AC8A2E02176700FB20F8 /* SharingGRDB */ = {
DC9A3DDF2E6A280F00DE41FB /* SQLiteData */ = {
isa = XCSwiftPackageProductDependency;
productName = SharingGRDB;
package = DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference ".." */;
productName = SQLiteData;
};
DCD9AC8E2E02177900FB20F8 /* SharingGRDB */ = {
DCBE8A132D4842BF0071F499 /* CasePaths */ = {
isa = XCSwiftPackageProductDependency;
package = DCD9AC892E02176700FB20F8 /* XCLocalSwiftPackageReference "../../sharing-grdb" */;
productName = SharingGRDB;
package = DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */;
productName = CasePaths;
};
DCF267382D48437300B680BE /* SwiftUINavigation */ = {
isa = XCSwiftPackageProductDependency;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions Examples/Reminders/RemindersLists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ class RemindersListsModel {

struct RemindersListsView: View {
@Bindable var model: RemindersListsModel
@State var id = UUID()
@Dependency(\.defaultSyncEngine) var syncEngine

var body: some View {
Expand Down Expand Up @@ -319,13 +318,11 @@ struct RemindersListsView: View {
Button {
if syncEngine.isRunning {
syncEngine.stop()
id = UUID()
} else {
Task {
await withErrorReporting {
try await syncEngine.start()
}
id = UUID()
}
}
} label: {
Expand Down Expand Up @@ -387,7 +384,6 @@ struct RemindersListsView: View {
.navigationDestination(item: $model.destination.detail) { detailModel in
RemindersDetailView(model: detailModel)
}
.id(id)
}
}

Expand Down
36 changes: 15 additions & 21 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,11 @@ extension DependencyValues {
Tag.self,
ReminderTag.self
)
if context != .live {
try defaultDatabase.write { db in
try db.seedSampleData()
}
}
}
}

func appDatabase() throws -> any DatabaseWriter {
@Dependency(\.context) var context
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
configuration.prepareDatabase { db in
Expand All @@ -133,21 +127,17 @@ func appDatabase() throws -> any DatabaseWriter {
}
#endif
}
if context == .preview {
database = try DatabaseQueue(configuration: configuration)
} else {
let path =
context == .live
? URL.documentsDirectory.appending(component: "db.sqlite").path()
: URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
logger.debug(
"""
App database:
open "\(path)"
"""
)
database = try DatabasePool(path: path, configuration: configuration)
}
let path =
context == .live
? URL.documentsDirectory.appending(component: "db.sqlite").path()
: URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
let database = try DatabasePool(path: path, configuration: configuration)
logger.debug(
"""
App database:
open "\(path)"
"""
)
var migrator = DatabaseMigrator()
#if DEBUG
migrator.eraseDatabaseOnSchemaChange = true
Expand Down Expand Up @@ -326,6 +316,10 @@ func appDatabase() throws -> any DatabaseWriter {
}
)
.execute(db)

if context != .live {
try db.seedSampleData()
}
}

return database
Expand Down
2 changes: 1 addition & 1 deletion Examples/RemindersTests/Reminders.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"parallelizable" : true,
"target" : {
"containerPath" : "container:Examples.xcodeproj",
"identifier" : "CA9F99472DF9134D00934431",
"identifier" : "CA5E46952DEBFE410069E0F8",
"name" : "RemindersTests"
}
}
Expand Down
9 changes: 6 additions & 3 deletions Examples/RemindersTests/RemindersListsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ extension BaseTestSuite {
color: 1218047999,
position: 1,
title: "Personal"
)
),
share: nil
),
[1]: RemindersListsModel.ReminderListState(
remindersCount: 2,
Expand All @@ -33,7 +34,8 @@ extension BaseTestSuite {
color: 3985191935,
position: 2,
title: "Family"
)
),
share: nil
),
[2]: RemindersListsModel.ReminderListState(
remindersCount: 2,
Expand All @@ -42,7 +44,8 @@ extension BaseTestSuite {
color: 2992493567,
position: 3,
title: "Business"
)
),
share: nil
)
]
"""
Expand Down
27 changes: 17 additions & 10 deletions Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import GRDB
import OrderedCollections
import OSLog
import Observation
import StructuredQueriesCore
import SwiftData

/// An object that manages the synchronization of local and remote SQLite data.
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
public final class SyncEngine: Sendable {
public final class SyncEngine: Observable, Sendable {
package let userDatabase: UserDatabase
package let logger: Logger
package let metadatabase: any DatabaseWriter
Expand All @@ -27,6 +28,7 @@
-> (private: any SyncEngineProtocol, shared: any SyncEngineProtocol)
package let container: any CloudContainer
let dataManager = Dependency(\.dataManager)
private let observationRegistrar = ObservationRegistrar()

/// The error message used when a write occurs to a record for which the current user
/// does not have permission.
Expand Down Expand Up @@ -72,7 +74,7 @@
privateTables: repeat (each T2).Type,
containerIdentifier: String? = nil,
defaultZone: CKRecordZone = CKRecordZone(zoneName: "co.pointfree.SQLiteData.defaultZone"),
startImmediately: Bool = true,
startImmediately: Bool = !isTesting,
logger: Logger = isTesting
? Logger(.disabled) : Logger(subsystem: "SQLiteData", category: "CloudKit")
) throws
Expand Down Expand Up @@ -318,26 +320,31 @@
/// You must start the sync engine again using ``start()`` to synchronize the changes.
public func stop() {
guard isRunning else { return }
syncEngines.withValue {
$0 = SyncEngines()
observationRegistrar.withMutation(of: self, keyPath: \.isRunning) {
syncEngines.withValue {
$0 = SyncEngines()
}
}
}

/// Determines if the sync engine is currently running or not.
public var isRunning: Bool {
syncEngines.withValue {
observationRegistrar.access(self, keyPath: \.isRunning)
return syncEngines.withValue {
$0.isRunning
}
}

private func start() throws -> Task<Void, Never> {
guard !isRunning else { return Task {} }
let (privateSyncEngine, sharedSyncEngine) = defaultSyncEngines(metadatabase, self)
syncEngines.withValue {
$0 = SyncEngines(
private: privateSyncEngine,
shared: sharedSyncEngine
)
observationRegistrar.withMutation(of: self, keyPath: \.isRunning) {
syncEngines.withValue {
$0 = SyncEngines(
private: privateSyncEngine,
shared: sharedSyncEngine
)
}
}

let previousRecordTypes = try metadatabase.read { db in
Expand Down