Skip to content

Commit d9bfcb0

Browse files
authored
Merge pull request #63 from claucambra/bugfix/stored-props
Only request capabilities once every half-hour, rather than per required action
2 parents d836a6e + d74c044 commit d9bfcb0

11 files changed

Lines changed: 96 additions & 45 deletions

File tree

Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSessionWeb
139139
}
140140

141141
private func configureNotifyPush() async {
142-
let (_, capabilitiesData, error) = await remoteInterface.fetchCapabilities(
142+
let (_, capabilities, _, error) = await remoteInterface.currentCapabilities(
143143
account: account,
144144
options: .init(),
145145
taskHandler: { task in
@@ -165,8 +165,7 @@ public class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSessionWeb
165165
return
166166
}
167167

168-
guard let capabilitiesData = capabilitiesData,
169-
let capabilities = Capabilities(data: capabilitiesData),
168+
guard let capabilities,
170169
let websocketEndpoint = capabilities.notifyPush?.endpoints?.websocket
171170
else {
172171
logger.error(

Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift renamed to Sources/NextcloudFileProviderKit/Interface/NextcloudRemoteInterface.swift

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@
77

88
import Alamofire
99
import FileProvider
10-
import Foundation
10+
import NextcloudCapabilitiesKit
1111
import NextcloudKit
1212

13-
extension NextcloudKit: RemoteInterface {
13+
fileprivate let CapabilitiesFetchInterval: TimeInterval = 30 * 60 // 30mins
1414

15-
public func setDelegate(_ delegate: any NextcloudKitDelegate) {
16-
setup(delegate: delegate)
15+
public class NextcloudRemoteInterface: NextcloudKit, RemoteInterface {
16+
17+
public var delegate: NextcloudKitDelegate? {
18+
get { nkCommonInstance.delegate }
19+
set { setup(delegate: newValue) }
1720
}
1821

22+
public var capabilities: Capabilities?
23+
private var capabilitiesFetchDate: Date?
24+
1925
public func createFolder(
2026
remotePath: String,
2127
account: Account,
@@ -377,14 +383,33 @@ extension NextcloudKit: RemoteInterface {
377383
account: Account,
378384
options: NKRequestOptions = .init(),
379385
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
380-
) async -> (account: String, data: Data?, error: NKError) {
386+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
381387
return await withCheckedContinuation { continuation in
382388
getCapabilities(account: account.ncKitAccount, options: options, taskHandler: taskHandler) { account, data, error in
383-
continuation.resume(returning: (account, data?.data, error))
389+
let capabilities: Capabilities? = {
390+
guard let realData = data?.data else { return nil }
391+
return Capabilities(data: realData)
392+
}()
393+
self.capabilities = capabilities
394+
self.capabilitiesFetchDate = Date()
395+
continuation.resume(returning: (account, capabilities, data?.data, error))
384396
}
385397
}
386398
}
387399

400+
public func currentCapabilities(
401+
account: Account,
402+
options: NKRequestOptions = .init(),
403+
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
404+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
405+
guard let intervalSinceLastFetch = capabilitiesFetchDate?.timeIntervalSince(Date()),
406+
intervalSinceLastFetch < -CapabilitiesFetchInterval
407+
else {
408+
return (account.ncKitAccount, capabilities, nil, .success)
409+
}
410+
return await fetchCapabilities(account: account, options: options, taskHandler: taskHandler)
411+
}
412+
388413
public func fetchUserProfile(
389414
account: Account,
390415
options: NKRequestOptions = .init(),

Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import Alamofire
99
import FileProvider
10-
import Foundation
10+
import NextcloudCapabilitiesKit
1111
import NextcloudKit
1212

1313
public enum EnumerateDepth: String {
@@ -22,7 +22,8 @@ public enum AuthenticationAttemptResultState: Int {
2222

2323
public protocol RemoteInterface {
2424

25-
func setDelegate(_ delegate: NextcloudKitDelegate)
25+
var delegate: NextcloudKitDelegate? { get set }
26+
var capabilities: Capabilities? { get set }
2627

2728
func createFolder(
2829
remotePath: String,
@@ -154,7 +155,15 @@ public protocol RemoteInterface {
154155
account: Account,
155156
options: NKRequestOptions,
156157
taskHandler: @escaping (_ task: URLSessionTask) -> Void
157-
) async -> (account: String, data: Data?, error: NKError)
158+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError)
159+
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)
158167

159168
func fetchUserProfile(
160169
account: Account,

Sources/NextcloudFileProviderKit/Item/Item+CreateLockFile.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension Item {
2222
progress.totalUnitCount = 1
2323

2424
// Lock but don't upload, do not error
25-
let (_, capabilitiesData, capabilitiesError) = await remoteInterface.fetchCapabilities(
25+
let (_, capabilities, _, capabilitiesError) = await remoteInterface.currentCapabilities(
2626
account: account,
2727
options: .init(),
2828
taskHandler: { task in
@@ -36,15 +36,14 @@ extension Item {
3636
}
3737
)
3838
guard capabilitiesError == .success,
39-
let capabilitiesData,
40-
let capabilities = Capabilities(data: capabilitiesData),
39+
let capabilities,
4140
capabilities.files?.locking != nil
4241
else {
4342
uploadLogger.info(
4443
"""
4544
Received nil capabilities data.
4645
Received error: \(capabilitiesError.errorDescription, privacy: .public)
47-
Capabilities data: \(capabilitiesData == nil ? "YES" : "NO", privacy: .public)
46+
Capabilities nil: \(capabilities == nil ? "YES" : "NO", privacy: .public)
4847
(if capabilities are not nil the server may just not have files_lock enabled).
4948
Will not proceed with locking for \(itemTemplate.filename, privacy: .public)
5049
"""

Sources/NextcloudFileProviderKit/Item/Item+DeleteLockFile.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extension Item {
1212
func deleteLockFile(
1313
domain: NSFileProviderDomain? = nil, dbManager: FilesDatabaseManager
1414
) async -> Error? {
15-
let (_, capabilitiesData, capabilitiesError) = await remoteInterface.fetchCapabilities(
15+
let (_, capabilities, _, capabilitiesError) = await remoteInterface.currentCapabilities(
1616
account: account,
1717
options: .init(),
1818
taskHandler: { task in
@@ -26,15 +26,14 @@ extension Item {
2626
}
2727
)
2828
guard capabilitiesError == .success,
29-
let capabilitiesData,
30-
let capabilities = Capabilities(data: capabilitiesData),
29+
let capabilities,
3130
capabilities.files?.locking != nil
3231
else {
3332
uploadLogger.info(
3433
"""
3534
Received nil capabilities data.
3635
Received error: \(capabilitiesError.errorDescription, privacy: .public)
37-
Capabilities data: \(capabilitiesData == nil ? "YES" : "NO", privacy: .public)
36+
Capabilities nil: \(capabilities == nil ? "YES" : "NO", privacy: .public)
3837
(if capabilities are not nil the server may just not have files_lock enabled).
3938
Will not proceed with unlocking for \(self.filename, privacy: .public)
4039
"""

Sources/NextcloudFileProviderKit/Utilities/Upload.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ func upload(
4747
uploadLogger.info("Using provided chunkSize: \(chunkSize, privacy: .public)")
4848
return chunkSize
4949
}
50-
let (_, capabilitiesData, error) = await remoteInterface.fetchCapabilities(
50+
let (_, capabilities, _, error) = await remoteInterface.currentCapabilities(
5151
account: account, options: options, taskHandler: taskHandler
5252
)
53-
guard let capabilitiesData,
54-
let capabilities = Capabilities(data: capabilitiesData),
53+
guard error == .success,
54+
let capabilities,
5555
let serverChunkSize = capabilities.files?.chunkedUpload?.maxChunkSize,
5656
serverChunkSize > 0
5757
else {
5858
uploadLogger.info(
5959
"""
6060
Received nil capabilities data.
6161
Received error: \(error.errorDescription, privacy: .public)
62-
Capabilities data: \(capabilitiesData == nil ? "YES" : "NO", privacy: .public)
62+
Capabilities nil: \(capabilities == nil ? "YES" : "NO", privacy: .public)
6363
(if capabilities are not nil the server may just not provide chunk size data).
6464
Using default file chunk size: \(defaultFileChunkSize, privacy: .public)
6565
"""

Tests/Interface/MockRemoteInterface.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Alamofire
99
import Foundation
10+
import NextcloudCapabilitiesKit
1011
import NextcloudFileProviderKit
1112
import NextcloudKit
1213

@@ -558,9 +559,10 @@ fileprivate let mockCapabilities = ##"""
558559
"""##
559560

560561
public class MockRemoteInterface: RemoteInterface {
561-
public var capabilities = mockCapabilities
562+
public var capabilitiesString = mockCapabilities
562563
public var rootItem: MockRemoteItem?
563-
public var delegate: (any NextcloudKitDelegate)?
564+
public var capabilities: Capabilities?
565+
public var delegate: NextcloudKitDelegate?
564566
public var rootTrashItem: MockRemoteItem?
565567
public var currentChunks: [String: [RemoteFileChunk]] = [:]
566568
public var completedChunkTransferSize: [String: Int64] = [:]
@@ -1136,8 +1138,22 @@ public class MockRemoteInterface: RemoteInterface {
11361138
account: Account,
11371139
options: NKRequestOptions,
11381140
taskHandler: @escaping (URLSessionTask) -> Void
1139-
) async -> (account: String, data: Data?, error: NKError) {
1140-
return (account.ncKitAccount, capabilities.data(using: .utf8), .success)
1141+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
1142+
let capsData = capabilitiesString.data(using: .utf8)
1143+
let capabilities: Capabilities? = {
1144+
guard let capsData else { return nil }
1145+
return Capabilities(data: capsData)
1146+
}()
1147+
self.capabilities = capabilities
1148+
return (account.ncKitAccount, capabilities, capsData, .success)
1149+
}
1150+
1151+
public func currentCapabilities(
1152+
account: Account,
1153+
options: NKRequestOptions,
1154+
taskHandler: @escaping (URLSessionTask) -> Void
1155+
) async -> (account: String, capabilities: Capabilities?, data: Data?, error: NKError) {
1156+
return await fetchCapabilities(account: account, options: options, taskHandler: taskHandler)
11411157
}
11421158

11431159
public func fetchUserProfile(

Tests/NextcloudFileProviderKitTests/ItemCreateTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,11 @@ final class ItemCreateTests: XCTestCase {
634634

635635
func testCreateLockFileUnactionableWithoutCapabilities() async throws {
636636
let remoteInterface = MockRemoteInterface(rootItem: rootItem)
637-
XCTAssert(remoteInterface.capabilities.contains(##""locking": "1.0","##))
638-
remoteInterface.capabilities =
639-
remoteInterface.capabilities.replacingOccurrences(of: ##""locking": "1.0","##, with: "")
637+
XCTAssert(remoteInterface.capabilitiesString.contains(##""locking": "1.0","##))
638+
remoteInterface.capabilitiesString =
639+
remoteInterface.capabilitiesString.replacingOccurrences(
640+
of: ##""locking": "1.0","##, with: ""
641+
)
640642

641643
// Setup remote folder and file
642644
let folderRemote = MockRemoteItem(

Tests/NextcloudFileProviderKitTests/ItemDeleteTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,11 @@ final class ItemDeleteTests: XCTestCase {
270270

271271
func testDeleteLockFileWithoutCapabilitiesDoesNothing() async throws {
272272
let remoteInterface = MockRemoteInterface(rootItem: rootItem)
273-
XCTAssert(remoteInterface.capabilities.contains(##""locking": "1.0","##))
274-
remoteInterface.capabilities =
275-
remoteInterface.capabilities.replacingOccurrences(of: ##""locking": "1.0","##, with: "")
273+
XCTAssert(remoteInterface.capabilitiesString.contains(##""locking": "1.0","##))
274+
remoteInterface.capabilitiesString =
275+
remoteInterface.capabilitiesString.replacingOccurrences(
276+
of: ##""locking": "1.0","##, with: ""
277+
)
276278

277279
// Setup remote folder and file
278280
let folderRemote = MockRemoteItem(

Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ final class RemoteChangeObserverTests: XCTestCase {
4545

4646
func testAuthentication() async throws {
4747
let remoteInterface = MockRemoteInterface()
48-
remoteInterface.capabilities = mockCapabilities
48+
remoteInterface.capabilitiesString = mockCapabilities
4949

5050
var authenticated = false
5151

@@ -85,7 +85,7 @@ final class RemoteChangeObserverTests: XCTestCase {
8585
let incorrectAccount =
8686
Account(user: username, id: userId, serverUrl: serverUrl, password: "wrong!")
8787
let remoteInterface = MockRemoteInterface()
88-
remoteInterface.capabilities = mockCapabilities
88+
remoteInterface.capabilitiesString = mockCapabilities
8989
remoteChangeObserver = RemoteChangeObserver(
9090
account: incorrectAccount,
9191
remoteInterface: remoteInterface,
@@ -117,7 +117,7 @@ final class RemoteChangeObserverTests: XCTestCase {
117117
let incorrectAccount =
118118
Account(user: username, id: userId, serverUrl: serverUrl, password: "wrong!")
119119
let remoteInterface = MockRemoteInterface()
120-
remoteInterface.capabilities = mockCapabilities
120+
remoteInterface.capabilitiesString = mockCapabilities
121121
let remoteChangeObserver = RemoteChangeObserver(
122122
account: incorrectAccount,
123123
remoteInterface: remoteInterface,
@@ -142,7 +142,7 @@ final class RemoteChangeObserverTests: XCTestCase {
142142

143143
func testChangeRecognised() async throws {
144144
let remoteInterface = MockRemoteInterface()
145-
remoteInterface.capabilities = mockCapabilities
145+
remoteInterface.capabilitiesString = mockCapabilities
146146

147147
var authenticated = false
148148
var notified = false
@@ -182,7 +182,7 @@ final class RemoteChangeObserverTests: XCTestCase {
182182

183183
func testIgnoreNonFileNotifications() async throws {
184184
let remoteInterface = MockRemoteInterface()
185-
remoteInterface.capabilities = mockCapabilities
185+
remoteInterface.capabilitiesString = mockCapabilities
186186

187187
var authenticated = false
188188
var notified = false
@@ -225,7 +225,7 @@ final class RemoteChangeObserverTests: XCTestCase {
225225
func testPolling() async throws {
226226
var notified = false
227227
let remoteInterface = MockRemoteInterface()
228-
remoteInterface.capabilities = ""
228+
remoteInterface.capabilitiesString = ""
229229
let notificationInterface = MockChangeNotificationInterface()
230230
notificationInterface.changeHandler = { notified = true }
231231
remoteChangeObserver = RemoteChangeObserver(
@@ -264,7 +264,7 @@ final class RemoteChangeObserverTests: XCTestCase {
264264

265265
func testRetryOnRemoteClose() async throws {
266266
let remoteInterface = MockRemoteInterface()
267-
remoteInterface.capabilities = mockCapabilities
267+
remoteInterface.capabilitiesString = mockCapabilities
268268

269269
var authenticated = false
270270

@@ -304,7 +304,7 @@ final class RemoteChangeObserverTests: XCTestCase {
304304

305305
func testPinging() async throws {
306306
let remoteInterface = MockRemoteInterface()
307-
remoteInterface.capabilities = mockCapabilities
307+
remoteInterface.capabilitiesString = mockCapabilities
308308

309309
var authenticated = false
310310

@@ -347,7 +347,7 @@ final class RemoteChangeObserverTests: XCTestCase {
347347

348348
func testRetryOnConnectionLoss() async throws {
349349
let remoteInterface = MockRemoteInterface()
350-
remoteInterface.capabilities = mockCapabilities
350+
remoteInterface.capabilitiesString = mockCapabilities
351351

352352
var authenticated = false
353353
var notified = false

0 commit comments

Comments
 (0)