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
Original file line number Diff line number Diff line change
Expand Up @@ -56,35 +56,35 @@ extension Enumerator {
return (metadatas, nil, nil, nil, error)
}

guard var (directoryMetadata, _, metadatas) = await files.toDirectoryReadMetadatas(account: account) else {
logger.error("Could not convert NKFiles to DirectoryReadMetadatas!")
guard var (directory, _, files) = await files.toSendableDirectoryMetadata(account: account, directoryToRead: serverUrl) else {
logger.error("Failed to convert array of NKFile to directory and files metadata objects!")
return (nil, nil, nil, nil, .invalidData)
}

// STORE DATA FOR CURRENTLY SCANNED DIRECTORY
guard directoryMetadata.directory else {
logger.error("Expected directory metadata but received file metadata for serverUrl: \(serverUrl)")
guard directory.directory else {
logger.error("Expected directory metadata but received file metadata!", [.url: serverUrl])
return (nil, nil, nil, nil, .invalidData)
}

if let existingMetadata = dbManager.itemMetadata(ocId: directoryMetadata.ocId) {
directoryMetadata.downloaded = existingMetadata.downloaded
directoryMetadata.keepDownloaded = existingMetadata.keepDownloaded
if let existingMetadata = dbManager.itemMetadata(ocId: directory.ocId) {
directory.downloaded = existingMetadata.downloaded
directory.keepDownloaded = existingMetadata.keepDownloaded
}

directoryMetadata.visitedDirectory = true
directory.visitedDirectory = true

metadatas.insert(directoryMetadata, at: 0)
files.insert(directory, at: 0)

let changedMetadatas = dbManager.depth1ReadUpdateItemMetadatas(
account: account.ncKitAccount,
serverUrl: serverUrl,
updatedMetadatas: metadatas,
updatedMetadatas: files,
keepExistingDownloadState: true
)

return (
metadatas,
files,
changedMetadatas.newMetadatas,
changedMetadatas.updatedMetadatas,
changedMetadatas.deletedMetadatas,
Expand Down Expand Up @@ -214,9 +214,7 @@ extension Enumerator {

return ([metadata], newMetadatas, updatedMetadatas, nil, nextPage, nil)
} else if depth == .targetAndDirectChildren {
let (
allMetadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError
) = await handleDepth1ReadFileOrFolder(
let (allMetadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError) = await handleDepth1ReadFileOrFolder(
serverUrl: serverUrl,
account: account,
dbManager: dbManager,
Expand Down
98 changes: 65 additions & 33 deletions Sources/NextcloudFileProviderKit/Extensions/NKFile+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@ extension NKFile {
func toItemMetadata(uploaded: Bool = true) -> SendableItemMetadata {
let creationDate = creationDate ?? date
let uploadDate = uploadDate ?? date
let classFile = (contentType == "text/markdown" || contentType == "text/x-markdown")
&& classFile == NKTypeClassFile.unknow.rawValue

let classFile = (contentType == "text/markdown" || contentType == "text/x-markdown") && classFile == NKTypeClassFile.unknow.rawValue
? NKTypeClassFile.document.rawValue
: classFile

// Support for finding the correct filename for e2ee files should go here

// Don't ask me why, NextcloudKit renames and moves the root folder details
// Also don't ask me why, but, NextcloudKit marks the NKFile for this as not a directory
let rootServerUrl = urlBase + Account.webDavFilesUrlSuffix + userId
let rootRequiresFixup = serverUrl == rootServerUrl && fileName == NextcloudKit.shared.nkCommonInstance.rootFileName
let ocId = rootRequiresFixup
? NSFileProviderItemIdentifier.rootContainer.rawValue
: self.ocId
let ocId = rootRequiresFixup ? NSFileProviderItemIdentifier.rootContainer.rawValue : self.ocId
let directory = rootRequiresFixup ? true : self.directory
let serverUrl = rootRequiresFixup ? rootServerUrl : self.serverUrl
let fileName = rootRequiresFixup ? "" : self.fileName
let fileName = rootRequiresFixup ? NextcloudKit.shared.nkCommonInstance.rootFileName : self.fileName

return SendableItemMetadata(
ocId: ocId,
Expand Down Expand Up @@ -94,46 +93,79 @@ extension NKFile {
}
}

///
/// Data container intended for use in combination with `concurrentChunkedForEach` to safely and concurrently convert a lot of metadata objects.
///
private final actor DirectoryMetadataContainer: Sendable {
let root: SendableItemMetadata
var directories: [SendableItemMetadata] = []
var files: [SendableItemMetadata] = []

init(for root: SendableItemMetadata) {
self.root = root
}

extension Array<NKFile> {
private final actor DirectoryReadConversionActor: Sendable {
let directoryMetadata: SendableItemMetadata
var childDirectoriesMetadatas: [SendableItemMetadata] = []
var metadatas: [SendableItemMetadata] = []

func convertedMetadatas() -> (
SendableItemMetadata, [SendableItemMetadata], [SendableItemMetadata]
) {
(directoryMetadata, childDirectoriesMetadatas, metadatas)
///
/// Insert a new item into the container.
///
func add(_ item: SendableItemMetadata) {
files.append(item)

if item.directory {
directories.append(item)
}
}

///
/// Return a tuple of the total current content.
///
func content() -> (SendableItemMetadata, [SendableItemMetadata], [SendableItemMetadata]) {
(root, directories, files)
}
}

init(target: SendableItemMetadata) {
self.directoryMetadata = target
extension Array<NKFile> {
///
/// Determine whether the given `NKFile` is the metadata object for the read remote directory.
///
func isDirectoryToRead(_ file: NKFile, directoryToRead: String) -> Bool {
if file.serverUrl == directoryToRead && file.fileName == NextcloudKit.shared.nkCommonInstance.rootFileName {
return true
}

func add(metadata: SendableItemMetadata) {
metadatas.append(metadata)
if metadata.directory {
childDirectoriesMetadatas.append(metadata)
}
if file.directory, "\(file.serverUrl)/\(file.fileName)" == directoryToRead {
return true
}

return false
}

func toDirectoryReadMetadatas(account: Account) async -> (
directoryMetadata: SendableItemMetadata,
childDirectoriesMetadatas: [SendableItemMetadata],
metadatas: [SendableItemMetadata]
)? {
guard var targetDirectoryMetadata = first?.toItemMetadata() else {
///
/// Convert an array of `NKFile` to `SendableItemMetadata`.
///
/// - Parameters:
/// - account: The account which the metadata belongs to.
/// - directoryToRead: The root path of the directory which this metadata comes from. This is required to distinguish the correct item for the metadata of the read directory itself from its children.
///
/// - Returns: A tuple consisting of the metadata for the read directory itself (`root`), any child directories (`directories`) and separately any directly containted files (`files`).
///
func toSendableDirectoryMetadata(account: Account, directoryToRead: String) async -> (root: SendableItemMetadata, directories: [SendableItemMetadata], files: [SendableItemMetadata])? {
guard let root = first(where: { isDirectoryToRead($0, directoryToRead: directoryToRead) })?.toItemMetadata() else {
return nil
}
let conversionActor = DirectoryReadConversionActor(target: targetDirectoryMetadata)

let container = DirectoryMetadataContainer(for: root)

if self.count > 1 {
await self[1...].concurrentChunkedForEach { file in
await conversionActor.add(metadata: file.toItemMetadata())
await concurrentChunkedForEach { file in
guard isDirectoryToRead(file, directoryToRead: directoryToRead) == false else {
return
}

await container.add(file.toItemMetadata())
}
}
return await conversionActor.convertedMetadatas()

return await container.content()
}
}
12 changes: 6 additions & 6 deletions Sources/NextcloudFileProviderKit/Item/Item+Create.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ public extension Item {
))
}

guard var (directoryMetadata, _, _) = await files.toDirectoryReadMetadatas(account: account)
else {
logger.error("Received nil directory read metadatas during conversion")
guard var (directory, _, _) = await files.toSendableDirectoryMetadata(account: account, directoryToRead: remotePath) else {
logger.error("Failed to resolve directory metadata on item conversion!")
return (nil, NSFileProviderError(.cannotSynchronize))
}
directoryMetadata.downloaded = true
dbManager.addItemMetadata(directoryMetadata)

directory.downloaded = true
dbManager.addItemMetadata(directory)

let fpItem = Item(
metadata: directoryMetadata,
metadata: directory,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
Expand Down
16 changes: 12 additions & 4 deletions Tests/Interface/MockRemoteInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ public class MockRemoteInterface: RemoteInterface {
}

let sanitisedPath = sanitisedPath(remotePath, account: account)

guard sanitisedPath != "/" else {
return remotePath.hasPrefix(account.trashUrl) ? rootTrashItem : rootItem
}
Expand All @@ -624,11 +625,15 @@ public class MockRemoteInterface: RemoteInterface {

while pathComponents?.isEmpty == false {
let component = pathComponents?.removeFirst()
guard component?.isEmpty == false,
let nextNode = currentNode?.children.first(where: { $0.name == component })
else { return nil }

guard pathComponents?.isEmpty == false else { return nextNode } // This is the target
guard component?.isEmpty == false, let nextNode = currentNode?.children.first(where: { $0.name == component }) else {
return nil
}

guard pathComponents?.isEmpty == false else {
return nextNode // This is the target
}

currentNode = nextNode
}

Expand Down Expand Up @@ -1031,12 +1036,15 @@ public class MockRemoteInterface: RemoteInterface {
taskHandler: @escaping (URLSessionTask) -> Void = { _ in }
) async -> (account: String, files: [NKFile], data: AFDataResponse<Data>?, error: NKError) {
var remotePath = remotePath

if remotePath.last == "." {
remotePath.removeLast()
}

if remotePath.last == "/" {
remotePath.removeLast()
}

print("Enumerating \(remotePath)")

// Call the enumerate call handler if it exists
Expand Down
8 changes: 3 additions & 5 deletions Tests/Interface/MockRemoteItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class MockRemoteItem: Equatable {
MockRemoteItem(
identifier: NSFileProviderItemIdentifier.rootContainer.rawValue,
versionIdentifier: "root",
name: "",
name: NextcloudKit.shared.nkCommonInstance.rootFileName,
remotePath: account.davFilesUrl,
directory: true,
account: account.ncKitAccount,
Expand Down Expand Up @@ -120,17 +120,15 @@ public class MockRemoteItem: Equatable {
let isRoot = identifier == NSFileProviderItemIdentifier.rootContainer.rawValue
var file = NKFile()
file.fileName = isRoot
? "__NC_ROOT__"
? NextcloudKit.shared.nkCommonInstance.rootFileName
: trashbinOriginalLocation?.split(separator: "/").last?.toString() ?? name
file.size = size
file.date = creationDate
file.directory = isRoot ? false : directory
file.etag = versionIdentifier
file.ocId = identifier
file.fileId = identifier.replacingOccurrences(of: trashedItemIdSuffix, with: "")
file.serverUrl = isRoot
? serverUrl + "/remote.php/dav/files/" + userId
: parent?.remotePath ?? remotePath
file.serverUrl = isRoot ? "\(serverUrl)/remote.php/dav/files/\(userId)" : parent?.remotePath ?? serverUrl
file.account = account
file.user = username
file.userId = userId
Expand Down
4 changes: 2 additions & 2 deletions Tests/InterfaceTests/MockRemoteInterfaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

import XCTest
import NextcloudKit
@testable import NextcloudFileProviderKit
@testable import NextcloudFileProviderKitMocks
@testable import TestInterface
Expand Down Expand Up @@ -503,8 +504,7 @@ final class MockRemoteInterfaceTests: XCTestCase {
let targetRootFile = result.files.first
let expectedRoot = remoteInterface.rootItem
XCTAssertEqual(targetRootFile?.ocId, expectedRoot?.identifier)
XCTAssertEqual(targetRootFile?.fileName, "__NC_ROOT__") // NextcloudKit gives the root dir this name
XCTAssertNotEqual(targetRootFile?.fileName, expectedRoot?.name)
XCTAssertEqual(targetRootFile?.fileName, NextcloudKit.shared.nkCommonInstance.rootFileName) // NextcloudKit gives the root dir this name
XCTAssertEqual(targetRootFile?.serverUrl, "https://mock.nc.com/remote.php/dav/files/testUserId") // NextcloudKit gives the root dir this url
XCTAssertEqual(targetRootFile?.date, expectedRoot?.creationDate)
XCTAssertEqual(targetRootFile?.etag, expectedRoot?.versionIdentifier)
Expand Down
Loading