From ee16c1b1c53e9e6cfaada27d63ca4be1e9d7a45e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:30:52 +0000 Subject: [PATCH 1/6] Initial plan From de61e846e6b727fdc0cac48403a52f65b0ae940d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:36:07 +0000 Subject: [PATCH 2/6] feat: re-enable WebDAV pagination for Nextcloud 31+ servers Co-authored-by: i2h3 <142165879+i2h3@users.noreply.github.com> --- .../Enumeration/Enumerator.swift | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift index c605751d5f73e..a94613e90593e 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift @@ -130,20 +130,43 @@ public final class Enumerator: NSObject, NSFileProviderEnumerator, Sendable { // Do not pass in the NSFileProviderPage default pages, these are not valid Nextcloud // pagination tokens var pageTotal: Int? = nil + var pageIndex = 0 + var parsedPage: NSFileProviderPage? = nil if page != NSFileProviderPage.initialPageSortedByName as NSFileProviderPage, page != NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage { if let enumPageResponse = try? JSONDecoder().decode(EnumeratorPageResponse.self, from: page.rawValue) { if let total = enumPageResponse.total { pageTotal = total } + pageIndex = enumPageResponse.index + parsedPage = page } else { logger.error("Could not parse page") } } + // Check server version to determine if pagination should be enabled. + // Pagination was fixed in Nextcloud 31 (server bug: https://github.com/nextcloud/server/issues/53674) + // For older servers, we fall back to non-paginated requests. + let (_, capabilities, _, _) = await remoteInterface.currentCapabilities( + account: account, + options: .init(), + taskHandler: { _ in } + ) + + let serverMajorVersion = capabilities?.version.major ?? 0 + let supportsPagination = serverMajorVersion >= 31 + + // Enable pagination by passing page settings if server supports it + let pageSettings: (page: NSFileProviderPage?, index: Int, size: Int)? = supportsPagination ? ( + page: parsedPage, + index: pageIndex, + size: pageItemCount + ) : nil + let readResult = await Self.readServerUrl( serverUrl, - pageSettings: nil, + pageSettings: pageSettings, account: account, remoteInterface: remoteInterface, dbManager: dbManager, @@ -180,7 +203,8 @@ public final class Enumerator: NSObject, NSFileProviderEnumerator, Sendable { nextPage = nil } - nextPage = nil + // Note: Removed unconditional `nextPage = nil` that was disabling pagination + // This enables proper pagination for large folders (1500+ files) when server supports it logger.info( """ From 8a878f07b36426ca82f17e7eea51c64bcf764fc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:40:05 +0000 Subject: [PATCH 3/6] test: add pagination tests for server version detection Co-authored-by: i2h3 <142165879+i2h3@users.noreply.github.com> --- .../EnumeratorTests.swift | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift index ed7826fbf54b7..f9903ccf5978e 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift @@ -1742,4 +1742,230 @@ final class EnumeratorTests: NextcloudFileProviderKitTestCase { XCTAssertTrue(workingSetIds2.contains(notVisitedFolder.ocId), "Newly visited folder should now be in working set") } + + // MARK: - Pagination Tests (Server Version Detection) + + /// Test that pagination is enabled when server is Nextcloud 31 or newer + func testPaginationEnabledForNC31Plus() async throws { + let db = Self.dbManager.ncDatabase() + debugPrint(db) + + // Setup a folder with many children to trigger pagination + remoteFolder.children = [] + for i in 0..<25 { + let childItem = MockRemoteItem( + identifier: "paginatedChild\(i)", + name: "file_\(i).pdf", + remotePath: Self.account.davFilesUrl + "/folder/file_\(i).pdf", + data: Data(repeating: UInt8(i % 256), count: 100), + account: Self.account.ncKitAccount, + username: Self.account.username, + userId: Self.account.id, + serverUrl: Self.account.serverUrl + ) + childItem.parent = remoteFolder + remoteFolder.children.append(childItem) + } + + // Create remote interface with pagination enabled and NC31+ capabilities + let remoteInterface = MockRemoteInterface( + account: Self.account, rootItem: rootItem, pagination: true + ) + // Override capabilities to simulate NC31+ + remoteInterface.capabilities = ##""" + { + "ocs": { + "data": { + "version": { + "major": 31, + "minor": 0, + "micro": 0, + "string": "31.0.0" + } + } + } + } + """## + + Self.dbManager.addItemMetadata(remoteFolder.toItemMetadata(account: Self.account)) + + let enumerator = Enumerator( + enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, + remoteInterface: remoteInterface, + dbManager: Self.dbManager, + pageSize: 5, // Small page size to force multiple pages + log: FileProviderLogMock() + ) + let observer = MockEnumerationObserver(enumerator: enumerator) + try await observer.enumerateItems() + + // With pagination enabled, all items should be enumerated + XCTAssertEqual( + observer.items.count, + 26, // folder + 25 children + "Pagination should enumerate all items for NC31+" + ) + + // Verify all items are in database + for i in 0..<25 { + XCTAssertNotNil( + Self.dbManager.itemMetadata(ocId: "paginatedChild\(i)"), + "Child item paginatedChild\(i) should be in DB with pagination enabled" + ) + } + } + + /// Test that pagination is disabled when server is older than Nextcloud 31 + func testPaginationDisabledForOldServers() async throws { + let db = Self.dbManager.ncDatabase() + debugPrint(db) + + // Setup a folder with children + remoteFolder.children = [] + for i in 0..<10 { + let childItem = MockRemoteItem( + identifier: "oldServerChild\(i)", + name: "file_\(i).txt", + remotePath: Self.account.davFilesUrl + "/folder/file_\(i).txt", + data: Data(repeating: UInt8(i % 256), count: 50), + account: Self.account.ncKitAccount, + username: Self.account.username, + userId: Self.account.id, + serverUrl: Self.account.serverUrl + ) + childItem.parent = remoteFolder + remoteFolder.children.append(childItem) + } + + // Create remote interface with pagination NOT enabled (simulates old server) + let remoteInterface = MockRemoteInterface( + account: Self.account, rootItem: rootItem, pagination: false + ) + // Override capabilities to simulate NC30 + remoteInterface.capabilities = ##""" + { + "ocs": { + "data": { + "version": { + "major": 30, + "minor": 0, + "micro": 5, + "string": "30.0.5" + } + } + } + } + """## + + Self.dbManager.addItemMetadata(remoteFolder.toItemMetadata(account: Self.account)) + + let enumerator = Enumerator( + enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, + remoteInterface: remoteInterface, + dbManager: Self.dbManager, + pageSize: 5, // Page size doesn't matter when pagination is disabled + log: FileProviderLogMock() + ) + let observer = MockEnumerationObserver(enumerator: enumerator) + try await observer.enumerateItems() + + // With pagination disabled, all items should still be enumerated (in single request) + XCTAssertEqual( + observer.items.count, + 11, // folder + 10 children + "Non-paginated enumeration should work for older servers" + ) + + // Verify all items are in database + for i in 0..<10 { + XCTAssertNotNil( + Self.dbManager.itemMetadata(ocId: "oldServerChild\(i)"), + "Child item oldServerChild\(i) should be in DB even without pagination" + ) + } + } + + /// Test that pagination works correctly for large folders on NC31+ + func testLargeFolderPaginationOnNC31() async throws { + let db = Self.dbManager.ncDatabase() + debugPrint(db) + + // Setup a folder with 50 children to test pagination across multiple pages + remoteFolder.children = [] + for i in 0..<50 { + let childItem = MockRemoteItem( + identifier: "largeChild\(i)", + name: "document_\(String(format: "%03d", i)).pdf", + remotePath: Self.account.davFilesUrl + "/folder/document_\(String(format: "%03d", i)).pdf", + data: Data(repeating: UInt8(i % 256), count: 100), + account: Self.account.ncKitAccount, + username: Self.account.username, + userId: Self.account.id, + serverUrl: Self.account.serverUrl + ) + childItem.parent = remoteFolder + remoteFolder.children.append(childItem) + } + + // Create remote interface with pagination enabled and NC31+ capabilities + let remoteInterface = MockRemoteInterface( + account: Self.account, rootItem: rootItem, pagination: true + ) + remoteInterface.capabilities = ##""" + { + "ocs": { + "data": { + "version": { + "major": 31, + "minor": 0, + "micro": 0, + "string": "31.0.0" + } + } + } + } + """## + + Self.dbManager.addItemMetadata(remoteFolder.toItemMetadata(account: Self.account)) + + let enumerator = Enumerator( + enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, + remoteInterface: remoteInterface, + dbManager: Self.dbManager, + pageSize: 10, // 50 items = 5 pages + log: FileProviderLogMock() + ) + let observer = MockEnumerationObserver(enumerator: enumerator) + try await observer.enumerateItems() + + // All 50 children + folder should be enumerated + XCTAssertEqual( + observer.items.count, + 51, + "Large folder with 50 items should be fully enumerated with pagination" + ) + + // Verify multiple pages were used + XCTAssertGreaterThan( + observer.observedPages.count, + 1, + "Large folder enumeration should use multiple pages" + ) + + // Verify all items are in database + var itemsInDb = 0 + for i in 0..<50 { + if Self.dbManager.itemMetadata(ocId: "largeChild\(i)") != nil { + itemsInDb += 1 + } + } + XCTAssertEqual( + itemsInDb, + 50, + "All 50 items should be saved to database with pagination" + ) + } } From 35814e77300f5e7149b14bf604813887c60bbafb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:41:30 +0000 Subject: [PATCH 4/6] docs: clarify capabilities caching for performance Co-authored-by: i2h3 <142165879+i2h3@users.noreply.github.com> --- .../NextcloudFileProviderKit/Enumeration/Enumerator.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift index a94613e90593e..3847c85e6b1bb 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift @@ -148,6 +148,8 @@ public final class Enumerator: NSObject, NSFileProviderEnumerator, Sendable { // Check server version to determine if pagination should be enabled. // Pagination was fixed in Nextcloud 31 (server bug: https://github.com/nextcloud/server/issues/53674) // For older servers, we fall back to non-paginated requests. + // Note: currentCapabilities uses RetrievedCapabilitiesActor which caches capabilities + // for 30 minutes, so this call is efficient and doesn't make a network request on every enumeration. let (_, capabilities, _, _) = await remoteInterface.currentCapabilities( account: account, options: .init(), From e4c4fb6f9d6c0f742ffd2b4ea74aba4ec20f1008 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Wed, 11 Feb 2026 09:49:27 +0100 Subject: [PATCH 5/6] fix: Updated for NextcloudCapabilitiesKit 2.4.6 which now exposes server version information Signed-off-by: Iva Horn --- .../NextcloudFileProviderKit/Enumeration/Enumerator.swift | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift index 3847c85e6b1bb..71338109c3b96 100644 --- a/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift +++ b/shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift @@ -156,7 +156,7 @@ public final class Enumerator: NSObject, NSFileProviderEnumerator, Sendable { taskHandler: { _ in } ) - let serverMajorVersion = capabilities?.version.major ?? 0 + let serverMajorVersion = capabilities?.major ?? 0 let supportsPagination = serverMajorVersion >= 31 // Enable pagination by passing page settings if server supports it diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ca6381ffd72a4..42f4f740055f7 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nextcloud/NextcloudCapabilitiesKit.git", "state" : { - "revision" : "07a481be18943cfa3ce17c91b83d6017cdbb5846", - "version" : "2.4.5" + "revision" : "296f28db1bb02c51f215e7ac34430885f5046058", + "version" : "2.4.6" } }, { From 0d5eeba396c0b0ab1f7cf3bb0388abb9280fd2c8 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Wed, 11 Feb 2026 09:49:50 +0100 Subject: [PATCH 6/6] fix: Mention iOS and Android as irrelevant in AGENTS.md Signed-off-by: Iva Horn --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index e84ee1ad9ec56..d493378c6acab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,7 @@ You are an experienced engineer specialized on C++ and Qt and familiar with the The Nextcloud Desktop Client is a tool to synchronize files from Nextcloud Server with your computer. Qt, C++, CMake and KDE Craft are the key technologies used for building the app on Windows, macOS and Linux. Beyond that, there are platform-specific extensions of the multi-platform app in the `./shell_integration` directory. +Other platforms like iOS and Android are irrelevant for this project. ## Project Structure: AI Agent Handling Guidelines