Skip to content

Commit b73ca99

Browse files
authored
Merge pull request #82 from claucambra/bugfix/caps-sync
Properly fix deadlocking when trying to access current capabilities
2 parents 153ac57 + f73f20d commit b73ca99

23 files changed

Lines changed: 943 additions & 126 deletions

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ let package = Package(
1919
dependencies: [
2020
.package(
2121
url: "https://github.com/claucambra/NextcloudCapabilitiesKit.git",
22-
.upToNextMajor(from: "2.1.2")
22+
.upToNextMajor(from: "2.3.0")
2323
),
2424
.package(url: "https://github.com/nextcloud/NextcloudKit", from: "5.0.4"),
2525
.package(url: "https://github.com/realm/realm-swift.git", exact: "20.0.1"),

Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ extension Enumerator {
3131
) {
3232
let results = await self.scanRecursively(
3333
Item.rootContainer(
34-
account: account, remoteInterface: remoteInterface, dbManager: dbManager
34+
account: account,
35+
remoteInterface: remoteInterface,
36+
dbManager: dbManager,
37+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
3538
).metadata,
3639
account: account,
3740
remoteInterface: remoteInterface,

Sources/NextcloudFileProviderKit/Enumeration/Enumerator+Trash.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension Enumerator {
4848
remoteInterface: RemoteInterface,
4949
dbManager: FilesDatabaseManager,
5050
trashItems: [NKTrash]
51-
) {
51+
) async {
5252
var newTrashedItems = [NSFileProviderItem]()
5353

5454
// NKTrash items do not have an etag ; we assume they cannot be modified while they are in
@@ -71,7 +71,8 @@ extension Enumerator {
7171
parentItemIdentifier: .trashContainer,
7272
account: account,
7373
remoteInterface: remoteInterface,
74-
dbManager: dbManager
74+
dbManager: dbManager,
75+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
7576
)
7677
newTrashedItems.append(item)
7778

Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator {
451451
return
452452
}
453453

454-
Self.completeChangesObserver(
454+
await Self.completeChangesObserver(
455455
observer,
456456
anchor: anchor,
457457
account: account,

Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,6 @@ import NextcloudCapabilitiesKit
1212
import NextcloudKit
1313
import OSLog
1414

15-
fileprivate let CapabilitiesFetchInterval: TimeInterval = 30 * 60 // 30mins
16-
17-
actor RetrievedCapabilitiesActor {
18-
static let shared: RetrievedCapabilitiesActor = {
19-
let instance = RetrievedCapabilitiesActor()
20-
return instance
21-
}()
22-
var data: [String: (capabilities: Capabilities, retrievedAt: Date)] = [:]
23-
24-
func setCapabilities(
25-
forAccount account: String,
26-
capabilities: Capabilities,
27-
retrievedAt: Date = Date()
28-
) async {
29-
self.data[account] = (capabilities: capabilities, retrievedAt: retrievedAt)
30-
}
31-
}
32-
3315
extension NextcloudKit: RemoteInterface {
3416

3517
public func setDelegate(_ delegate: any NextcloudKitDelegate) {
@@ -398,6 +380,10 @@ extension NextcloudKit: RemoteInterface {
398380
options: NKRequestOptions = .init(),
399381
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
400382
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
383+
let ncKitAccount = account.ncKitAccount
384+
await RetrievedCapabilitiesActor.shared.setOngoingFetch(
385+
forAccount: ncKitAccount, ongoing: true
386+
)
401387
let result = await withCheckedContinuation { continuation in
402388
getCapabilities(account: account.ncKitAccount, options: options, taskHandler: taskHandler) { account, data, error in
403389
let capabilities: Capabilities? = {
@@ -407,6 +393,9 @@ extension NextcloudKit: RemoteInterface {
407393
continuation.resume(returning: (account, capabilities, data?.data, error))
408394
}
409395
}
396+
await RetrievedCapabilitiesActor.shared.setOngoingFetch(
397+
forAccount: ncKitAccount, ongoing: false
398+
)
410399
if let capabilities = result.1 {
411400
await RetrievedCapabilitiesActor.shared.setCapabilities(
412401
forAccount: account.ncKitAccount, capabilities: capabilities
@@ -415,39 +404,6 @@ extension NextcloudKit: RemoteInterface {
415404
return result
416405
}
417406

418-
public func currentCapabilities(
419-
account: Account,
420-
options: NKRequestOptions = .init(),
421-
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
422-
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
423-
let ncKitAccount = account.ncKitAccount
424-
guard let lastRetrieval = await RetrievedCapabilitiesActor.shared.data[ncKitAccount],
425-
lastRetrieval.retrievedAt.timeIntervalSince(Date()) > -CapabilitiesFetchInterval
426-
else {
427-
return await fetchCapabilities(
428-
account: account, options: options, taskHandler: taskHandler
429-
)
430-
}
431-
return (account.ncKitAccount, lastRetrieval.capabilities, nil, .success)
432-
}
433-
434-
public func currentCapabilitiesSync(account: Account) -> Capabilities? {
435-
let semaphore = DispatchSemaphore(value: 0)
436-
var capabilities: Capabilities?
437-
Task {
438-
let (_, fetchedCapabilities, _, error) = await currentCapabilities(account: account)
439-
if error != .success {
440-
Logger
441-
.init(subsystem: Logger.subsystem, category: "NextcloudKitRemoteInterface")
442-
.error("Error during sync capabilities fetch: \(error.errorDescription, privacy: .public)")
443-
}
444-
capabilities = fetchedCapabilities
445-
semaphore.signal()
446-
}
447-
semaphore.wait()
448-
return capabilities
449-
}
450-
451407
public func fetchUserProfile(
452408
account: Account,
453409
options: NKRequestOptions = .init(),

Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import FileProvider
1010
import Foundation
1111
import NextcloudCapabilitiesKit
1212
import NextcloudKit
13+
import OSLog
14+
1315

1416
public enum EnumerateDepth: String {
1517
case target = "0"
@@ -157,16 +159,6 @@ public protocol RemoteInterface {
157159
taskHandler: @escaping (_ task: URLSessionTask) -> Void
158160
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError)
159161

160-
// This method should result in fetches only after a certain period of time.
161-
// Alternatively, it should only fetch when capabilities are guaranteed to have changed.
162-
func currentCapabilities(
163-
account: Account,
164-
options: NKRequestOptions,
165-
taskHandler: @escaping (_ task: URLSessionTask) -> Void
166-
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError)
167-
168-
func currentCapabilitiesSync(account: Account) -> Capabilities?
169-
170162
func fetchUserProfile(
171163
account: Account,
172164
options: NKRequestOptions,
@@ -179,3 +171,44 @@ public protocol RemoteInterface {
179171
taskHandler: @escaping (_ task: URLSessionTask) -> Void
180172
) async -> AuthenticationAttemptResultState
181173
}
174+
175+
public extension RemoteInterface {
176+
177+
private var logger: Logger {
178+
Logger(subsystem: Logger.subsystem, category: "RemoteInterface")
179+
}
180+
181+
func currentCapabilities(
182+
account: Account,
183+
options: NKRequestOptions = .init(),
184+
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
185+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
186+
let ncKitAccount = account.ncKitAccount
187+
await RetrievedCapabilitiesActor.shared.awaitFetchCompletion(forAccount: ncKitAccount)
188+
guard let lastRetrieval = await RetrievedCapabilitiesActor.shared.data[ncKitAccount],
189+
lastRetrieval.retrievedAt.timeIntervalSince(Date()) > -CapabilitiesFetchInterval
190+
else {
191+
return await fetchCapabilities(
192+
account: account, options: options, taskHandler: taskHandler
193+
)
194+
}
195+
return (account.ncKitAccount, lastRetrieval.capabilities, nil, .success)
196+
}
197+
198+
func supportsTrash(
199+
account: Account,
200+
options: NKRequestOptions = .init(),
201+
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
202+
) async -> Bool {
203+
var remoteSupportsTrash = false
204+
let (_, capabilities, _, _) = await currentCapabilities(
205+
account: account, options: .init(), taskHandler: { _ in }
206+
)
207+
if let filesCapabilities = capabilities?.files {
208+
remoteSupportsTrash = filesCapabilities.undelete
209+
} else {
210+
logger.warning("Could not get capabilities, will assume trash is unavailable.")
211+
}
212+
return remoteSupportsTrash
213+
}
214+
}

Sources/NextcloudFileProviderKit/Item/Item+Create.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ public extension Item {
9999
parentItemIdentifier: parentItemIdentifier,
100100
account: account,
101101
remoteInterface: remoteInterface,
102-
dbManager: dbManager
102+
dbManager: dbManager,
103+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
103104
)
104105

105106
return (fpItem, nil)
@@ -216,7 +217,8 @@ public extension Item {
216217
parentItemIdentifier: itemTemplate.parentItemIdentifier,
217218
account: account,
218219
remoteInterface: remoteInterface,
219-
dbManager: dbManager
220+
dbManager: dbManager,
221+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
220222
)
221223

222224
return (fpItem, nil)
@@ -412,7 +414,8 @@ public extension Item {
412414
parentItemIdentifier: rootItem.parentItemIdentifier,
413415
account: account,
414416
remoteInterface: remoteInterface,
415-
dbManager: dbManager
417+
dbManager: dbManager,
418+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
416419
)
417420
}
418421

@@ -495,7 +498,7 @@ public extension Item {
495498

496499
let relativePath = parentItemRelativePath + "/" + itemTemplate.filename
497500
guard ignoredFiles == nil || ignoredFiles?.isExcluded(relativePath) == false else {
498-
return Item.createIgnored(
501+
return await Item.createIgnored(
499502
basedOn: itemTemplate,
500503
parentItemRemotePath: parentItemRemotePath,
501504
contents: url,
@@ -604,7 +607,8 @@ public extension Item {
604607
parentItemIdentifier: parentItemIdentifier,
605608
account: account,
606609
remoteInterface: remoteInterface,
607-
dbManager: dbManager
610+
dbManager: dbManager,
611+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
608612
)
609613
}
610614

Sources/NextcloudFileProviderKit/Item/Item+Fetch.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ public extension Item {
304304
parentItemIdentifier: parentItemIdentifier,
305305
account: account,
306306
remoteInterface: remoteInterface,
307-
dbManager: dbManager
307+
dbManager: dbManager,
308+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
308309
)
309310

310311
return (localPath, fpItem, nil)

Sources/NextcloudFileProviderKit/Item/Item+Ignored.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension Item {
1717
remoteInterface: RemoteInterface,
1818
progress: Progress,
1919
dbManager: FilesDatabaseManager
20-
) -> (Item?, Error?) {
20+
) async -> (Item?, Error?) {
2121
let filename = itemTemplate.filename
2222
Self.logger.info(
2323
"""
@@ -60,7 +60,8 @@ extension Item {
6060
parentItemIdentifier: itemTemplate.parentItemIdentifier,
6161
account: account,
6262
remoteInterface: remoteInterface,
63-
dbManager: dbManager
63+
dbManager: dbManager,
64+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
6465
)
6566

6667
if #available(macOS 13.0, *) {

Sources/NextcloudFileProviderKit/Item/Item+LockFile.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ extension Item {
140140
parentItemIdentifier: parentItemIdentifier,
141141
account: account,
142142
remoteInterface: remoteInterface,
143-
dbManager: dbManager
143+
dbManager: dbManager,
144+
remoteSupportsTrash: await remoteInterface.supportsTrash(account: account)
144145
),
145146
returnError
146147
)
@@ -167,7 +168,7 @@ extension Item {
167168
)
168169
assert(isLockFileName(filename), "Should not handle non-lock files here.")
169170

170-
guard let modifiedItem = modifyUnuploaded(
171+
guard let modifiedItem = await modifyUnuploaded(
171172
itemTarget: itemTarget,
172173
baseVersion: baseVersion,
173174
changedFields: changedFields,

0 commit comments

Comments
 (0)