Skip to content

Commit f5e791d

Browse files
improve locking for attachment syncing
1 parent ec1eba1 commit f5e791d

File tree

13 files changed

+294
-207
lines changed

13 files changed

+294
-207
lines changed

Demo/PowerSyncExample/Components/TodoListView.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ struct TodoListView: View {
6969
}
7070
.onDelete { indexSet in
7171
Task {
72-
await delete(at: indexSet)
72+
if let toDelete = indexSet.map({ todos[$0] }).first {
73+
await delete(todo: toDelete)
74+
}
7375
}
7476
}
7577
}
@@ -124,12 +126,10 @@ struct TodoListView: View {
124126
}
125127
}
126128

127-
func delete(at offset: IndexSet) async {
129+
func delete(todo: Todo) async {
128130
do {
129131
error = nil
130-
let todosToDelete = offset.map { todos[$0] }
131-
132-
try await system.deleteTodo(id: todosToDelete[0].id)
132+
try await system.deleteTodo(todo: todo)
133133

134134
} catch {
135135
self.error = error

Demo/PowerSyncExample/PowerSync/Schema.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ let todos = Table(
3636
]
3737
)
3838

39-
let AppSchema = Schema(lists, todos, createAttachmentsTable(name: "attachments"))
39+
let AppSchema = Schema(
40+
lists,
41+
todos,
42+
createAttachmentTable(name: "attachments")
43+
)

Demo/PowerSyncExample/PowerSync/SystemManager.swift

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class SystemManager {
7979

8080
func watchLists(_ callback: @escaping (_ lists: [ListContent]) -> Void) async {
8181
do {
82-
for try await lists in try db.watch<ListContent>(
82+
for try await lists in try db.watch(
8383
options: WatchOptions(
8484
sql: "SELECT * FROM \(LISTS_TABLE)",
8585
mapper: { cursor in
@@ -100,7 +100,7 @@ class SystemManager {
100100
}
101101

102102
func insertList(_ list: NewListContent) async throws {
103-
let result = try await db.execute(
103+
_ = try await db.execute(
104104
sql: "INSERT INTO \(LISTS_TABLE) (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?)",
105105
parameters: [list.name, connector.currentUserID]
106106
)
@@ -112,6 +112,9 @@ class SystemManager {
112112
sql: "DELETE FROM \(LISTS_TABLE) WHERE id = ?",
113113
parameters: [id]
114114
)
115+
116+
// Attachments linked to these will be archived and deleted eventually
117+
// Attachments should be deleted explicitly if required
115118
_ = try transaction.execute(
116119
sql: "DELETE FROM \(TODOS_TABLE) WHERE list_id = ?",
117120
parameters: [id]
@@ -177,12 +180,30 @@ class SystemManager {
177180
}
178181
}
179182

180-
func deleteTodo(id: String) async throws {
181-
_ = try await db.writeTransaction(callback: { transaction in
182-
try transaction.execute(
183-
sql: "DELETE FROM \(TODOS_TABLE) WHERE id = ?",
184-
parameters: [id]
185-
)
186-
})
183+
func deleteTodo(todo: Todo) async throws {
184+
if let attachments, let photoId = todo.photoId {
185+
try await attachments.deleteFile(
186+
attachmentId: photoId
187+
) { (tx, _) in
188+
try self.deleteTodoInTX(
189+
id: todo.id,
190+
tx: tx
191+
)
192+
}
193+
} else {
194+
try await db.writeTransaction { tx in
195+
try self.deleteTodoInTX(
196+
id: todo.id,
197+
tx: tx
198+
)
199+
}
200+
}
201+
}
202+
203+
func deleteTodoInTX(id: String, tx: ConnectionContext) throws {
204+
_ = try tx.execute(
205+
sql: "DELETE FROM \(TODOS_TABLE) WHERE id = ?",
206+
parameters: [id]
207+
)
187208
}
188209
}

Sources/PowerSync/Kotlin/KotlinTypes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ public typealias JsonParam = PowerSyncKotlin.JsonParam
99
public typealias CrudTransaction = PowerSyncKotlin.CrudTransaction
1010
typealias KotlinPowerSyncCredentials = PowerSyncKotlin.PowerSyncCredentials
1111
typealias KotlinPowerSyncDatabase = PowerSyncKotlin.PowerSyncDatabase
12+
public typealias Transaction = PowerSyncKotlin.PowerSyncTransaction
13+
public typealias ConnectionContext = PowerSyncKotlin.ConnectionContext

Sources/PowerSync/QueriesProtocol.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Combine
22
import Foundation
3-
import PowerSyncKotlin
43

54
public let DEFAULT_WATCH_THROTTLE_MS = Int64(30)
65

@@ -90,10 +89,10 @@ public protocol Queries {
9089
) throws -> AsyncThrowingStream<[RowType], Error>
9190

9291
/// Execute a write transaction with the given callback
93-
func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R
92+
func writeTransaction<R>(callback: @escaping (any Transaction) throws -> R) async throws -> R
9493

9594
/// Execute a read transaction with the given callback
96-
func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R
95+
func readTransaction<R>(callback: @escaping (any Transaction) throws -> R) async throws -> R
9796
}
9897

9998
public extension Queries {

Sources/PowerSync/attachments/AttachmentsService.swift renamed to Sources/PowerSync/attachments/AttachmentContext.swift

Lines changed: 27 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
import Foundation
22

3-
// TODO: should not need this
4-
import PowerSyncKotlin
5-
6-
/**
7-
* Service for interacting with the local attachment records.
8-
*/
9-
public class AttachmentService {
3+
/// Context which performs actions on the attachment records
4+
public class AttachmentContext {
105
private let db: any PowerSyncDatabaseProtocol
116
private let tableName: String
127
private let logger: any LoggerProtocol
138
private let logTag = "AttachmentService"
149
private let maxArchivedCount: Int64
1510

16-
/**
17-
* Table used for storing attachments in the attachment queue.
18-
*/
11+
/// Table used for storing attachments in the attachment queue.
1912
private var table: String {
2013
return tableName
2114
}
2215

16+
/// Initializes a new `AttachmentContext`.
2317
public init(
2418
db: PowerSyncDatabaseProtocol,
2519
tableName: String,
@@ -32,44 +26,34 @@ public class AttachmentService {
3226
self.maxArchivedCount = maxArchivedCount
3327
}
3428

35-
/**
36-
* Delete the attachment from the attachment queue.
37-
*/
29+
/// Deletes the attachment from the attachment queue.
3830
public func deleteAttachment(id: String) async throws {
3931
_ = try await db.execute(sql: "DELETE FROM \(table) WHERE id = ?", parameters: [id])
4032
}
4133

42-
/**
43-
* Set the state of the attachment to ignore.
44-
*/
34+
/// Sets the state of the attachment to ignored (archived).
4535
public func ignoreAttachment(id: String) async throws {
4636
_ = try await db.execute(
4737
sql: "UPDATE \(table) SET state = ? WHERE id = ?",
4838
parameters: [AttachmentState.archived.rawValue, id]
4939
)
5040
}
5141

52-
/**
53-
* Get the attachment from the attachment queue using an ID.
54-
*/
42+
/// Gets the attachment from the attachment queue using an ID.
5543
public func getAttachment(id: String) async throws -> Attachment? {
5644
return try await db.getOptional(sql: "SELECT * FROM \(table) WHERE id = ?", parameters: [id], mapper: { cursor in
5745
try Attachment.fromCursor(cursor)
5846
})
5947
}
6048

61-
/**
62-
* Save the attachment to the attachment queue.
63-
*/
49+
/// Saves the attachment to the attachment queue.
6450
public func saveAttachment(attachment: Attachment) async throws -> Attachment {
6551
return try await db.writeTransaction { ctx in
6652
try self.upsertAttachment(attachment, context: ctx)
6753
}
6854
}
6955

70-
/**
71-
* Save the attachments to the attachment queue.
72-
*/
56+
/// Saves multiple attachments to the attachment queue.
7357
public func saveAttachments(attachments: [Attachment]) async throws {
7458
if attachments.isEmpty {
7559
return
@@ -82,9 +66,7 @@ public class AttachmentService {
8266
}
8367
}
8468

85-
/**
86-
* Get all the ID's of attachments in the attachment queue.
87-
*/
69+
/// Gets all the IDs of attachments in the attachment queue.
8870
public func getAttachmentIds() async throws -> [String] {
8971
return try await db.getAll(
9072
sql: "SELECT id FROM \(table) WHERE id IS NOT NULL",
@@ -95,6 +77,7 @@ public class AttachmentService {
9577
)
9678
}
9779

80+
/// Gets all attachments in the attachment queue.
9881
public func getAttachments() async throws -> [Attachment] {
9982
return try await db.getAll(
10083
sql: """
@@ -114,9 +97,7 @@ public class AttachmentService {
11497
)
11598
}
11699

117-
/**
118-
* Gets all the active attachments which require an operation to be performed.
119-
*/
100+
/// Gets all active attachments that require an operation to be performed.
120101
public func getActiveAttachments() async throws -> [Attachment] {
121102
return try await db.getAll(
122103
sql: """
@@ -141,52 +122,18 @@ public class AttachmentService {
141122
}
142123
}
143124

144-
/**
145-
* Watcher for changes to attachments table.
146-
* Once a change is detected it will initiate a sync of the attachments
147-
*/
148-
public func watchActiveAttachments() throws -> AsyncThrowingStream<[String], Error> {
149-
logger.info("Watching attachments...", tag: logTag)
150-
151-
return try db.watch(
152-
sql: """
153-
SELECT
154-
id
155-
FROM
156-
\(table)
157-
WHERE
158-
state = ?
159-
OR state = ?
160-
OR state = ?
161-
ORDER BY
162-
timestamp ASC
163-
""",
164-
parameters: [
165-
AttachmentState.queuedUpload.rawValue,
166-
AttachmentState.queuedDownload.rawValue,
167-
AttachmentState.queuedDelete.rawValue,
168-
]
169-
) { cursor in
170-
try cursor.getString(name: "id")
171-
}
172-
}
173-
174-
/**
175-
* Helper function to clear the attachment queue
176-
* Currently only used for testing purposes.
177-
*/
125+
/// Clears the attachment queue.
126+
///
127+
/// - Note: Currently only used for testing purposes.
178128
public func clearQueue() async throws {
179-
// logger.i("Clearing attachment queue...")
180129
_ = try await db.execute("DELETE FROM \(table)")
181130
}
182131

183-
/**
184-
* Delete attachments which have been archived
185-
* @returns true if all items have been deleted. Returns false if there might be more archived
186-
* items remaining.
187-
*/
132+
/// Deletes attachments that have been archived.
133+
///
134+
/// - Parameter callback: A callback invoked with the list of archived attachments before deletion.
135+
/// - Returns: `true` if all items have been deleted, `false` if there may be more archived items remaining.
188136
public func deleteArchivedAttachments(callback: @escaping ([Attachment]) async throws -> Void) async throws -> Bool {
189-
// First fetch the attachments in order to allow other cleanup
190137
let limit = 1000
191138
let attachments = try await db.getAll(
192139
sql: """
@@ -222,12 +169,15 @@ public class AttachmentService {
222169
return attachments.count < limit
223170
}
224171

225-
/**
226-
* Upserts an attachment record synchronously given a database connection context.
227-
*/
172+
/// Upserts an attachment record synchronously using a database transaction context.
173+
///
174+
/// - Parameters:
175+
/// - attachment: The attachment to upsert.
176+
/// - context: The database transaction context.
177+
/// - Returns: The original attachment.
228178
public func upsertAttachment(
229179
_ attachment: Attachment,
230-
context: PowerSyncTransaction
180+
context: ConnectionContext
231181
) throws -> Attachment {
232182
let timestamp = Int(Date().timeIntervalSince1970 * 1000)
233183
let updatedRecord = Attachment(
@@ -263,3 +213,4 @@ public class AttachmentService {
263213
return attachment
264214
}
265215
}
216+

0 commit comments

Comments
 (0)