Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/nextcloud/NextcloudCapabilitiesKit.git", from: "2.3.0"),
.package(url: "https://github.com/nextcloud/NextcloudKit", from: "7.0.0"),
.package(url: "https://github.com/nextcloud/NextcloudKit", exact: "7.2.2"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.55.0"),
.package(url: "https://github.com/realm/realm-swift.git", from: "20.0.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,14 @@ public final class FilesDatabaseManager: Sendable {
if let itemMetadata = itemMetadatas.where({ $0.ocId == ocId }).first {
return SendableItemMetadata(value: itemMetadata)
}

return nil
}

public func itemMetadata(_ identifier: NSFileProviderItemIdentifier) -> SendableItemMetadata? {
itemMetadata(ocId: identifier.rawValue)
}

///
/// Look up the item metadata by its account identifier and remote address.
///
Expand Down Expand Up @@ -259,6 +264,19 @@ public final class FilesDatabaseManager: Sendable {
return nil
}

///
/// Fetch the metadata object for the root container of the given account.
///
/// This is useful for when you have only the `NSFileProviderItemIdentifier.rootContainer` but no `ocId` to look up metadata by.
///
public func rootItemMetadata(account: Account) -> SendableItemMetadata? {
guard let object = itemMetadatas.where({ $0.account == account.ncKitAccount && $0.directory && $0.path == Account.webDavFilesUrlSuffix }).first else {
return nil
}

return SendableItemMetadata(value: object)
}

public func itemMetadatas(account: String) -> [SendableItemMetadata] {
itemMetadatas
.where { $0.account == account }
Expand All @@ -273,12 +291,6 @@ public final class FilesDatabaseManager: Sendable {
.toUnmanagedResults()
}

public func itemMetadataFromFileProviderItemIdentifier(
_ identifier: NSFileProviderItemIdentifier
) -> SendableItemMetadata? {
itemMetadata(ocId: identifier.rawValue)
}

private func processItemMetadatasToDelete(
existingMetadatas: Results<RealmItemMetadata>,
updatedMetadatas: [SendableItemMetadata]
Expand Down Expand Up @@ -607,13 +619,23 @@ public final class FilesDatabaseManager: Sendable {
}

private func managedMaterialisedItemMetadatas(account: String) -> Results<RealmItemMetadata> {
itemMetadatas
.where {
$0.account == account &&
(($0.directory && $0.visitedDirectory) || (!$0.directory && $0.downloaded))
}
itemMetadatas.where { candidate in
let belongsToAccount = candidate.account == account
let isVisitedDirectory = candidate.directory && candidate.visitedDirectory
let isDownloadedFile = candidate.directory == false && candidate.downloaded

return belongsToAccount && (isVisitedDirectory || isDownloadedFile)
}
}

///
/// Return metadata for materialized file provider items.
///
/// - Parameters:
/// - account: The account identifier to filter by.
///
/// - Returns: An array of sendable metadata objects.
///
public func materialisedItemMetadatas(account: String) -> [SendableItemMetadata] {
managedMaterialisedItemMetadatas(account: account).toUnmanagedResults()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import NextcloudKit
public class Enumerator: NSObject, NSFileProviderEnumerator {
let enumeratedItemIdentifier: NSFileProviderItemIdentifier
private var enumeratedItemMetadata: SendableItemMetadata?
private var enumeratingSystemIdentifier: Bool {
Self.isSystemIdentifier(enumeratedItemIdentifier)
}

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

if let enumeratedItemMetadata {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-2.0-or-later

import FileProvider
import Foundation
import RealmSwift

///
/// The custom `NSFileProviderEnumerationObserver` implementation to process materialized items enumerated by the system.
///
public class MaterializedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver {
let logger: FileProviderLogger
public let account: Account
let dbManager: FilesDatabaseManager
private let completionHandler: (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void

///
/// All materialized items enumerated by the system.
///
private var enumeratedItems = Set<NSFileProviderItemIdentifier>()

public required init(account: Account, dbManager: FilesDatabaseManager, log: any FileProviderLogging, completionHandler: @escaping (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void) {
self.account = account
self.dbManager = dbManager
logger = FileProviderLogger(category: "MaterializedEnumerationObserver", log: log)
self.completionHandler = completionHandler
super.init()
}

public func didEnumerate(_ updatedItems: [NSFileProviderItemProtocol]) {
updatedItems
.map(\.itemIdentifier)
.forEach { enumeratedItems.insert($0) }
}

public func finishEnumerating(upTo _: NSFileProviderPage?) {
logger.debug("Handling enumerated materialized items.")
handleEnumeratedItems(enumeratedItems, account: account, dbManager: dbManager, completionHandler: completionHandler)
}

public func finishEnumeratingWithError(_ error: Error) {
logger.error("Finishing enumeration with error.", [.error: error])
handleEnumeratedItems(enumeratedItems, account: account, dbManager: dbManager, completionHandler: completionHandler)
}

func handleEnumeratedItems(_ identifiers: Set<NSFileProviderItemIdentifier>, account: Account, dbManager: FilesDatabaseManager, completionHandler: @escaping (_ materialized: Set<NSFileProviderItemIdentifier>, _ evicted: Set<NSFileProviderItemIdentifier>) -> Void) {
let metadataForMaterializedItems = dbManager.materialisedItemMetadatas(account: account.ncKitAccount)
var metadataForMaterializedItemsByIdentifier = [NSFileProviderItemIdentifier: SendableItemMetadata]()
var evictedItems = Set<NSFileProviderItemIdentifier>()
var stillMaterializedItems = Set<NSFileProviderItemIdentifier>()

for metadata in metadataForMaterializedItems {
let identifier = NSFileProviderItemIdentifier(metadata.ocId)
metadataForMaterializedItemsByIdentifier[identifier] = metadata
evictedItems.insert(identifier) // Assume the item related to the metadata object was evicted until proven otherwise below.
}

for enumeratedIdentifier in identifiers {
if evictedItems.contains(enumeratedIdentifier) {
evictedItems.remove(enumeratedIdentifier) // The enumerated item cannot be assumed as evicted any longer.
} else {
stillMaterializedItems.insert(enumeratedIdentifier)

guard var metadata = if enumeratedIdentifier == .rootContainer {
dbManager.rootItemMetadata(account: account)
} else {
dbManager.itemMetadata(enumeratedIdentifier)
} else {
logger.error("No metadata for enumerated item found.", [.item: enumeratedIdentifier])
continue
}

if metadata.directory {
metadata.visitedDirectory = true
} else {
metadata.downloaded = true
}

logger.info("Updating state for item to materialized.", [.item: enumeratedIdentifier, .name: metadata.fileName])
dbManager.addItemMetadata(metadata)
}
}

for evictedItemIdentifier in evictedItems {
guard var metadata = metadataForMaterializedItemsByIdentifier[evictedItemIdentifier] else {
logger.error("No metadata found for apparently evicted identifier.", [.item: evictedItemIdentifier])
continue
}

logger.info("Updating item state to dataless.", [.name: metadata.fileName, .item: evictedItemIdentifier])

metadata.downloaded = false
metadata.visitedDirectory = false
dbManager.addItemMetadata(metadata)
}

completionHandler(stillMaterializedItems, evictedItems)
}
}
Loading