Skip to content

Commit e124ce3

Browse files
improve camera detection. Better support for cancellations.
1 parent f801ec1 commit e124ce3

File tree

6 files changed

+58
-25
lines changed

6 files changed

+58
-25
lines changed

Demo/PowerSyncExample/Components/TodoListView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,13 @@ struct TodoListView: View {
173173
private func checkCameraAvailability() {
174174
// https://developer.apple.com/forums/thread/748448
175175
// On MacOS MetalAPI validation needs to be disabled
176+
177+
#if targetEnvironment(simulator)
178+
// Camera does not work on the simulator
179+
isCameraAvailable = false
180+
#else
176181
isCameraAvailable = UIImagePickerController.isSourceTypeAvailable(.camera)
182+
#endif
177183
}
178184
}
179185

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,13 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
192192
let task = Task {
193193
do {
194194
var mapperError: Error?
195-
195+
// HACK!
196+
// SKIEE doesn't support custom exceptions in Flows
197+
// Exceptions which occur in the Flow itself cause runtime crashes.
198+
// The most probable crash would be the internal EXPLAIN statement.
199+
// This attempts to EXPLAIN the query before passing it to Kotlin
200+
// We could introduce an onChange API in Kotlin which we use to implement watches here.
201+
// This would prevent most issues with exceptions.
196202
// EXPLAIN statement to prevent crashes in SKIEE
197203
_ = try await self.kotlinDatabase.getAll(
198204
sql: "EXPLAIN \(options.sql)",
@@ -210,7 +216,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
210216
return try options.mapper(cursor)
211217
} catch {
212218
mapperError = error
213-
return nil as RowType?
219+
return ()
214220
}
215221
}
216222
) {

Sources/PowerSync/attachments/AttachmentQueue.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public class AttachmentQueue {
133133
}
134134

135135
// Verify initial state
136-
try await attachmentsService.withLock { context in
136+
try await attachmentsService.withContext { context in
137137
try await self.verifyAttachments(context: context)
138138
}
139139

@@ -146,6 +146,7 @@ public class AttachmentQueue {
146146
group.addTask {
147147
var previousConnected = self.db.currentStatus.connected
148148
for await status in self.db.currentStatus.asFlow() {
149+
try Task.checkCancellation()
149150
if !previousConnected && status.connected {
150151
try await self.syncingService.triggerSync()
151152
}
@@ -223,7 +224,7 @@ public class AttachmentQueue {
223224
public func processWatchedAttachments(items: [WatchedAttachmentItem]) async throws {
224225
// Need to get all the attachments which are tracked in the DB.
225226
// We might need to restore an archived attachment.
226-
try await attachmentsService.withLock { context in
227+
try await attachmentsService.withContext { context in
227228
let currentAttachments = try await context.getAttachments()
228229
var attachmentUpdates = [Attachment]()
229230

@@ -316,7 +317,7 @@ public class AttachmentQueue {
316317
// Write the file to the filesystem
317318
let fileSize = try await localStorage.saveFile(filePath: localUri, data: data)
318319

319-
return try await attachmentsService.withLock { context in
320+
return try await attachmentsService.withContext { context in
320321
// Start a write transaction. The attachment record and relevant local relationship
321322
// assignment should happen in the same transaction.
322323
try await self.db.writeTransaction { tx in
@@ -346,12 +347,11 @@ public class AttachmentQueue {
346347
attachmentId: String,
347348
updateHook: @escaping (ConnectionContext, Attachment) throws -> Void
348349
) async throws -> Attachment {
349-
try await attachmentsService.withLock { context in
350+
try await attachmentsService.withContext { context in
350351
guard let attachment = try await context.getAttachment(id: attachmentId) else {
351352
throw PowerSyncAttachmentError.notFound("Attachment record with id \(attachmentId) was not found.")
352353
}
353354

354-
self.logger.debug("Marking attachment as deleted", tag: nil)
355355
let result = try await self.db.writeTransaction { tx in
356356
try updateHook(tx, attachment)
357357

@@ -367,7 +367,6 @@ public class AttachmentQueue {
367367

368368
return try context.upsertAttachment(updatedAttachment, context: tx)
369369
}
370-
self.logger.debug("Marked attachment as deleted", tag: nil)
371370
return result
372371
}
373372
}
@@ -381,7 +380,7 @@ public class AttachmentQueue {
381380

382381
/// Removes all archived items
383382
public func expireCache() async throws {
384-
try await attachmentsService.withLock { context in
383+
try await attachmentsService.withContext { context in
385384
var done = false
386385
repeat {
387386
done = try await self.syncingService.deleteArchivedAttachments(context)
@@ -391,7 +390,7 @@ public class AttachmentQueue {
391390

392391
/// Clears the attachment queue and deletes all attachment files
393392
public func clearQueue() async throws {
394-
try await attachmentsService.withLock { context in
393+
try await attachmentsService.withContext { context in
395394
try await context.clearQueue()
396395
// Remove the attachments directory
397396
try await self.localStorage.rmDir(path: self.attachmentsDirectory)

Sources/PowerSync/attachments/AttachmentService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class AttachmentService {
5757
}
5858

5959
/// Executes a callback with exclusive access to the attachment context.
60-
public func withLock<R>(callback: @Sendable @escaping (AttachmentContext) async throws -> R) async throws -> R {
60+
public func withContext<R>(callback: @Sendable @escaping (AttachmentContext) async throws -> R) async throws -> R {
6161
try await lock.withLock {
6262
try await callback(context)
6363
}

Sources/PowerSync/attachments/LockActor.swift

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,46 @@
1+
import Foundation
12

2-
internal actor LockActor {
3+
actor LockActor {
34
private var isLocked = false
4-
private var queue: [CheckedContinuation<Void, Never>] = []
5-
6-
func withLock<T>(_ execute: @Sendable () async throws -> T) async throws -> T {
7-
if isLocked {
5+
private var waiters: [(id: UUID, continuation: CheckedContinuation<Void, Never>)] = []
6+
7+
func withLock<T>(_ operation: @Sendable () async throws -> T) async throws -> T {
8+
try await waitUntilUnlocked()
9+
10+
isLocked = true
11+
defer { unlockNext() }
12+
13+
try Task.checkCancellation() // cancellation check after acquiring lock
14+
return try await operation()
15+
}
16+
17+
private func waitUntilUnlocked() async throws {
18+
if !isLocked { return }
19+
20+
let id = UUID()
21+
22+
// Use withTaskCancellationHandler to manage cancellation
23+
await withTaskCancellationHandler {
824
await withCheckedContinuation { continuation in
9-
queue.append(continuation)
25+
waiters.append((id: id, continuation: continuation))
26+
}
27+
} onCancel: {
28+
// Cancellation logic: remove the waiter when cancelled
29+
Task {
30+
await self.removeWaiter(id: id)
1031
}
1132
}
12-
13-
isLocked = true
14-
defer { unlockNext() }
15-
return try await execute()
33+
}
34+
35+
private func removeWaiter(id: UUID) async {
36+
// Safely remove the waiter from the actor's waiters list
37+
waiters.removeAll { $0.id == id }
1638
}
1739

1840
private func unlockNext() {
19-
if let next = queue.first {
20-
queue.removeFirst()
21-
next.resume(returning: ())
41+
if let next = waiters.first {
42+
waiters.removeFirst()
43+
next.continuation.resume()
2244
} else {
2345
isLocked = false
2446
}

Sources/PowerSync/attachments/SyncingService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public class SyncingService {
155155
for await _ in syncTrigger {
156156
try Task.checkCancellation()
157157

158-
try await self.attachmentsService.withLock { context in
158+
try await self.attachmentsService.withContext { context in
159159
let attachments = try await context.getActiveAttachments()
160160
try await self.handleSync(context: context, attachments: attachments)
161161
_ = try await self.deleteArchivedAttachments(context)

0 commit comments

Comments
 (0)