Skip to content

Commit 1b56ad2

Browse files
committed
fix: Added missing handling of root container identifier in enumeration of materialized items.
Housekeeping: - Added support for NSError in unified logging output. - More consistent code formatting. - Updated identifier names for clarity. - Added documentation comments. - Removed dead code. Signed-off-by: Iva Horn <iva.horn@nextcloud.com>
1 parent 863cefb commit 1b56ad2

11 files changed

Lines changed: 180 additions & 176 deletions

Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager.swift

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,21 @@ public final class FilesDatabaseManager: Sendable {
205205
// changes in the db.
206206
//
207207
// Let's therefore create a copy
208-
if let itemMetadata = itemMetadatas.where({ $0.ocId == ocId }).first {
209-
return SendableItemMetadata(value: itemMetadata)
210-
}
208+
if let itemMetadata = itemMetadatas
209+
.where({
210+
$0.ocId == ocId
211+
})
212+
.first {
213+
return SendableItemMetadata(value: itemMetadata)
214+
}
215+
211216
return nil
212217
}
213218

219+
public func itemMetadata(_ identifier: NSFileProviderItemIdentifier) -> SendableItemMetadata? {
220+
return itemMetadata(ocId: identifier.rawValue)
221+
}
222+
214223
///
215224
/// Look up the item metadata by its account identifier and remote address.
216225
///
@@ -259,6 +268,19 @@ public final class FilesDatabaseManager: Sendable {
259268
return nil
260269
}
261270

271+
///
272+
/// Fetch the metadata object for the root container of the given account.
273+
///
274+
/// This is useful for when you have only the `NSFileProviderItemIdentifier.rootContainer` but no `ocId` to look up metadata by.
275+
///
276+
public func rootItemMetadata(account: Account) -> SendableItemMetadata? {
277+
guard let object = itemMetadatas.where({ $0.account == account.ncKitAccount && $0.directory && $0.path == Account.webDavFilesUrlSuffix }).first else {
278+
return nil
279+
}
280+
281+
return SendableItemMetadata(value: object)
282+
}
283+
262284
public func itemMetadatas(account: String) -> [SendableItemMetadata] {
263285
itemMetadatas
264286
.where { $0.account == account }
@@ -273,12 +295,6 @@ public final class FilesDatabaseManager: Sendable {
273295
.toUnmanagedResults()
274296
}
275297

276-
public func itemMetadataFromFileProviderItemIdentifier(
277-
_ identifier: NSFileProviderItemIdentifier
278-
) -> SendableItemMetadata? {
279-
itemMetadata(ocId: identifier.rawValue)
280-
}
281-
282298
private func processItemMetadatasToDelete(
283299
existingMetadatas: Results<RealmItemMetadata>,
284300
updatedMetadatas: [SendableItemMetadata]
@@ -607,13 +623,23 @@ public final class FilesDatabaseManager: Sendable {
607623
}
608624

609625
private func managedMaterialisedItemMetadatas(account: String) -> Results<RealmItemMetadata> {
610-
itemMetadatas
611-
.where {
612-
$0.account == account &&
613-
(($0.directory && $0.visitedDirectory) || (!$0.directory && $0.downloaded))
614-
}
626+
itemMetadatas.where { candidate in
627+
let belongsToAccount = candidate.account == account
628+
let isVisitedDirectory = candidate.directory && candidate.visitedDirectory
629+
let isDownloadedFile = candidate.directory == false && candidate.downloaded
630+
631+
return belongsToAccount && (isVisitedDirectory || isDownloadedFile)
632+
}
615633
}
616634

635+
///
636+
/// Return metadata for materialized file provider items.
637+
///
638+
/// - Parameters:
639+
/// - account: The account identifier to filter by.
640+
///
641+
/// - Returns: An array of sendable metadata objects.
642+
///
617643
public func materialisedItemMetadatas(account: String) -> [SendableItemMetadata] {
618644
managedMaterialisedItemMetadatas(account: account).toUnmanagedResults()
619645
}

Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import NextcloudKit
1010
public class Enumerator: NSObject, NSFileProviderEnumerator {
1111
let enumeratedItemIdentifier: NSFileProviderItemIdentifier
1212
private var enumeratedItemMetadata: SendableItemMetadata?
13-
private var enumeratingSystemIdentifier: Bool {
14-
Self.isSystemIdentifier(enumeratedItemIdentifier)
15-
}
1613

1714
let domain: NSFileProviderDomain?
1815
let dbManager: FilesDatabaseManager
@@ -51,7 +48,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator {
5148
serverUrl = account.davFilesUrl
5249
} else {
5350
logger.debug("Providing enumerator for item with identifier.", [.item: enumeratedItemIdentifier])
54-
enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(
51+
enumeratedItemMetadata = dbManager.itemMetadata(
5552
enumeratedItemIdentifier)
5653

5754
if let enumeratedItemMetadata {

Sources/NextcloudFileProviderKit/Enumeration/MaterialisedEnumerationObserver.swift

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
2+
// SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
import FileProvider
5+
import Foundation
6+
import RealmSwift
7+
8+
///
9+
/// The custom `NSFileProviderEnumerationObserver` implementation to process materialized items enumerated by the system.
10+
///
11+
public class MaterializedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver {
12+
let logger: FileProviderLogger
13+
public let account: Account
14+
let dbManager: FilesDatabaseManager
15+
private let completionHandler: (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void
16+
17+
///
18+
/// All materialized items enumerated by the system.
19+
///
20+
private var enumeratedItems = Set<NSFileProviderItemIdentifier>()
21+
22+
public required init(account: Account, dbManager: FilesDatabaseManager, log: any FileProviderLogging, completionHandler: @escaping (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void) {
23+
self.account = account
24+
self.dbManager = dbManager
25+
logger = FileProviderLogger(category: "MaterializedEnumerationObserver", log: log)
26+
self.completionHandler = completionHandler
27+
super.init()
28+
}
29+
30+
public func didEnumerate(_ updatedItems: [NSFileProviderItemProtocol]) {
31+
updatedItems
32+
.map(\.itemIdentifier)
33+
.forEach { enumeratedItems.insert($0) }
34+
}
35+
36+
public func finishEnumerating(upTo _: NSFileProviderPage?) {
37+
logger.debug("Handling enumerated materialized items.")
38+
handleEnumeratedItems(enumeratedItems, account: account, dbManager: dbManager, completionHandler: completionHandler)
39+
}
40+
41+
public func finishEnumeratingWithError(_ error: Error) {
42+
logger.error("Finishing enumeration with error.", [.error: error])
43+
handleEnumeratedItems(enumeratedItems, account: account, dbManager: dbManager, completionHandler: completionHandler)
44+
}
45+
46+
func handleEnumeratedItems(_ identifiers: Set<NSFileProviderItemIdentifier>, account: Account, dbManager: FilesDatabaseManager, completionHandler: @escaping (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void) {
47+
let metadataForMaterializedItems = dbManager.materialisedItemMetadatas(account: account.ncKitAccount)
48+
var metadataForMaterializedItemsByIdentifier = [NSFileProviderItemIdentifier: SendableItemMetadata]()
49+
var evictedItems = Set<NSFileProviderItemIdentifier>()
50+
var stillMaterializedItems = Set<NSFileProviderItemIdentifier>()
51+
52+
for metadata in metadataForMaterializedItems {
53+
let identifier = NSFileProviderItemIdentifier(metadata.ocId)
54+
metadataForMaterializedItemsByIdentifier[identifier] = metadata
55+
evictedItems.insert(identifier) // Assume the item related to the metadata object was evicted until proven otherwise below.
56+
}
57+
58+
for enumeratedIdentifier in identifiers {
59+
if evictedItems.contains(enumeratedIdentifier) {
60+
evictedItems.remove(enumeratedIdentifier) // The enumerated item cannot be assumed as evicted any longer.
61+
} else {
62+
stillMaterializedItems.insert(enumeratedIdentifier)
63+
64+
guard var metadata = if enumeratedIdentifier == .rootContainer {
65+
dbManager.rootItemMetadata(account: account)
66+
} else {
67+
dbManager.itemMetadata(enumeratedIdentifier)
68+
} else {
69+
logger.error("No metadata for enumerated item found.", [.item: enumeratedIdentifier])
70+
continue
71+
}
72+
73+
if metadata.directory {
74+
metadata.visitedDirectory = true
75+
} else {
76+
metadata.downloaded = true
77+
}
78+
79+
logger.info("Updating state for item to materialized.", [.item: enumeratedIdentifier, .name: metadata.fileName])
80+
dbManager.addItemMetadata(metadata)
81+
}
82+
}
83+
84+
for evictedItemIdentifier in evictedItems {
85+
guard var metadata = metadataForMaterializedItemsByIdentifier[evictedItemIdentifier] else {
86+
logger.error("No metadata found for apparently evicted identifier.", [.item: evictedItemIdentifier])
87+
continue
88+
}
89+
90+
logger.info("Updating item state to dataless.", [.name: metadata.fileName, .item: evictedItemIdentifier])
91+
92+
metadata.downloaded = false
93+
metadata.visitedDirectory = false
94+
dbManager.addItemMetadata(metadata)
95+
}
96+
97+
completionHandler(stillMaterializedItems, evictedItems)
98+
}
99+
}

Sources/NextcloudFileProviderKit/Item/Item.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ public class Item: NSObject, NSFileProviderItem {
374374
)
375375
}
376376

377-
guard let metadata = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else {
377+
guard let metadata = dbManager.itemMetadata(identifier) else {
378378
return nil
379379
}
380380

Sources/NextcloudFileProviderKit/Log/FileProviderLog.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ public actor FileProviderLog: FileProviderLogging {
223223
account.ncKitAccount
224224
case let date as Date:
225225
messageDateFormatter.string(from: date)
226+
case let error as NSError:
227+
error.debugDescription
226228
case let lock as NKLock:
227229
lock.token ?? "nil"
228230
case let item as NSFileProviderItem:

Sources/NextcloudFileProviderKit/Metadata/SendableItemMetadata+Array.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ extension [SendableItemMetadata] {
2222
return nil
2323
}
2424

25-
guard let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(
26-
itemMetadata
27-
) else {
25+
guard let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(itemMetadata) else {
2826
logger.error("Could not get valid parentItemIdentifier for item by ocId.", [.item: itemMetadata.ocId, .name: itemMetadata.fileName])
2927
let targetUrl = itemMetadata.serverUrl
3028
throw FilesDatabaseManager.parentMetadataNotFoundError(itemUrl: targetUrl)

Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ final class EnumeratorTests: NextcloudFileProviderKitTestCase {
223223

224224
// 3. Assert
225225
XCTAssertNil(observer.error, "Enumeration should complete without error.")
226-
XCTAssertEqual(observer.items.count, 2, "Should only enumerate the 2 materialised items.")
226+
XCTAssertEqual(observer.items.count, 2, "Should only enumerate the 2 materialized items.")
227227

228228
let enumeratedIds = Set(observer.items.map(\.itemIdentifier.rawValue))
229229
XCTAssertTrue(
@@ -247,7 +247,7 @@ final class EnumeratorTests: NextcloudFileProviderKitTestCase {
247247

248248
func testWorkingSetEnumerationWhenNoMaterialisedItems() async throws {
249249
// This test verifies that the enumerator behaves correctly when there are
250-
// no materialised items in the database.
250+
// no materialized items in the database.
251251

252252
// 1. Arrange
253253
let db = Self.dbManager.ncDatabase()

0 commit comments

Comments
 (0)