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 @@ -18,7 +18,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/nextcloud/NextcloudCapabilitiesKit.git", from: "2.4.0"),
.package(url: "https://github.com/nextcloud/NextcloudCapabilitiesKit.git", from: "2.5.0"),
.package(url: "https://github.com/nextcloud/NextcloudKit", from: "7.2.3"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.55.0"),
.package(url: "https://github.com/realm/realm-swift.git", from: "20.0.1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,15 @@ public extension Item {
directory.downloaded = true
dbManager.addItemMetadata(directory)

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: directory.contentType)

let fpItem = await Item(
metadata: directory,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down Expand Up @@ -218,12 +221,15 @@ public extension Item {

dbManager.addItemMetadata(newMetadata)

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: newMetadata.contentType)

let fpItem = await Item(
metadata: newMetadata,
parentItemIdentifier: itemTemplate.parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down Expand Up @@ -422,12 +428,15 @@ public extension Item {

progress.completedUnitCount += 1

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: bundleRootMetadata.contentType)

return await Item(
metadata: bundleRootMetadata,
parentItemIdentifier: rootItem.parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down Expand Up @@ -597,12 +606,15 @@ public extension Item {
)
}

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: itemMetadata.contentType)

item = await Item(
metadata: itemMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,15 @@ public extension Item {
return (nil, nil, NSError.fileProviderErrorForNonExistentItem(withIdentifier: itemIdentifier))
}

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: updatedMetadata.contentType)

let fpItem = await Item(
metadata: updatedMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: logger.log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ extension Item {
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: false,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ extension Item {
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: false,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: log
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,15 @@ public extension Item {
)
}

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: newMetadata.contentType)

let modifiedItem = await Item(
metadata: newMetadata,
parentItemIdentifier: newParentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: logger.log
)
Expand Down Expand Up @@ -209,12 +212,15 @@ public extension Item {

dbManager.addItemMetadata(newMetadata)

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: newMetadata.contentType)

let modifiedItem = await Item(
metadata: newMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: logger.log
)
Expand Down Expand Up @@ -511,12 +517,15 @@ public extension Item {

progress.completedUnitCount += 1

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: bundleRootMetadata.contentType)

return await Item(
metadata: bundleRootMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: logger.log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ extension Item {
}

let dirtyChildren = dbManager.childItems(directoryMetadata: dirtyMetadata)
let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: modifiedItem.remoteInterface, candidate: dirtyMetadata.contentType)

let dirtyItem = await Item(
metadata: dirtyMetadata,
parentItemIdentifier: .trashContainer,
account: account,
remoteInterface: modifiedItem.remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: modifiedItem.remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down Expand Up @@ -86,6 +88,7 @@ extension Item {
account: account,
remoteInterface: modifiedItem.remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: modifiedItem.remoteInterface.supportsTrash(account: account),
log: log
)
Expand Down Expand Up @@ -169,13 +172,15 @@ extension Item {
}

dbManager.addItemMetadata(restoredItemMetadata)
let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: restoredItemMetadata.contentType)

return await (Item(
metadata: restoredItemMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: modifiedItem.remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: modifiedItem.remoteInterface.supportsTrash(account: account),
log: log
), nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,15 @@ extension Item {
modifiedMetadata.date = newModificationDate
}

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: modifiedMetadata.contentType)

return await Item(
metadata: modifiedMetadata,
parentItemIdentifier: modifiedParentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteInterface.supportsTrash(account: account),
log: logger.log
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: LGPL-3.0-or-later

extension Item {
///
/// Gets all MIME type filters from the server capabilities for comparison.
///
/// - Parameters:
/// - account: The account identifier for the server to check.
/// - remoteInterface: The server proxy object to use.
///
/// - Returns: An array of strings as provided by NextcloudCapabilitiesKit or an empty array in case of error.
///
static func getContextMenuItemTypeFilters(account: Account, remoteInterface: RemoteInterface) async -> [String] {
let (_, capabilities, _, capabilitiesError) = await remoteInterface.currentCapabilities(account: account, options: .init(), taskHandler: { _ in })

if capabilitiesError == .success {
if let capabilities {
if let apps = capabilities.clientIntegration?.apps {
return apps.flatMap(\.contextMenuItems).flatMap(\.filters)
}
}
}

return []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: LGPL-3.0-or-later

extension Item {
///
/// Convenience wrapper for ``getContextMenuItemTypeFilters(account:remoteInterface:)`` and ``typeHasApplicableContextMenuItems(filters:candidate:)``.
///
/// Depending on the call site, it might be more efficient to call both methods individually to avoid redundant capability checks or circumvent boundaries of synchronous and asynchronous contexts.
///
/// - Parameters:
/// - candidate: The MIME type of the file provider item to check.
///
/// - Returns: `true`, if the candidate MIME type is covered by the list of filters provided, otherwise `false`.
///
static func typeHasApplicableContextMenuItems(account: Account, remoteInterface: RemoteInterface, candidate: String) async -> Bool {
let filters = await getContextMenuItemTypeFilters(account: account, remoteInterface: remoteInterface)
return typeHasApplicableContextMenuItems(filters: filters, candidate: candidate)
}

///
/// Check whether the MIME type of an item matches any type filter of server-defined context menu items.
///
/// - Parameters:
/// - filters: A list of MIME type filter strings and prefixes as provided by the server.
/// - candidate: The MIME type of the file provider item to check.
///
/// - Returns: `true`, if the candidate MIME type is covered by the list of filters provided, otherwise `false`.
///
static func typeHasApplicableContextMenuItems(filters: [String], candidate: String) -> Bool {
filters.first(where: { candidate.hasPrefix($0) }) != nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
public let account: Account
public let remoteInterface: RemoteInterface

private let displayFileActions: Bool
private let remoteSupportsTrash: Bool

public var itemIdentifier: NSFileProviderItemIdentifier {
Expand Down Expand Up @@ -188,6 +189,8 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {

public var userInfo: [AnyHashable: Any]? {
var userInfoDict = [AnyHashable: Any]()
userInfoDict["displayFileActions"] = displayFileActions

if metadata.lock {
// Can be used to display lock/unlock context menu entries for FPUIActions
// Note that only files, not folders, should be lockable/unlockable
Expand All @@ -199,11 +202,10 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
userInfoDict["displayEvict"] = metadata.downloaded && !metadata.keepDownloaded

// https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html
if metadata.permissions.uppercased().contains("R"), // Shareable
![.rootContainer, .trashContainer].contains(itemIdentifier)
{
if metadata.permissions.uppercased().contains("R") /* Shareable */, ![.rootContainer, .trashContainer].contains(itemIdentifier) {
userInfoDict["displayShare"] = true
}

return userInfoDict
}

Expand Down Expand Up @@ -265,6 +267,7 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: false,
remoteSupportsTrash: remoteSupportsTrash,
log: log
)
Expand Down Expand Up @@ -308,6 +311,7 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: false,
remoteSupportsTrash: remoteSupportsTrash,
log: log
)
Expand All @@ -321,6 +325,7 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
account: Account,
remoteInterface: RemoteInterface,
dbManager: FilesDatabaseManager,
displayFileActions: Bool,
remoteSupportsTrash: Bool,
log: any FileProviderLogging
) {
Expand All @@ -330,6 +335,7 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
logger = FileProviderLogger(category: "Item", log: log)
self.remoteInterface = remoteInterface
self.dbManager = dbManager
self.displayFileActions = displayFileActions
self.remoteSupportsTrash = remoteSupportsTrash
super.init()
}
Expand Down Expand Up @@ -382,12 +388,17 @@ public final class Item: NSObject, NSFileProviderItem, Sendable {
return nil
}

// Display File Actions

let displayFileActions = await Item.typeHasApplicableContextMenuItems(account: account, remoteInterface: remoteInterface, candidate: metadata.contentType)

return Item(
metadata: metadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteSupportsTrash,
log: log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extension [SendableItemMetadata] {
func toFileProviderItems(account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, log: any FileProviderLogging) async throws -> [Item] {
let logger = FileProviderLogger(category: "toFileProviderItems", log: log)
let remoteSupportsTrash = await remoteInterface.supportsTrash(account: account)
let allFilters = await Item.getContextMenuItemTypeFilters(account: account, remoteInterface: remoteInterface)

return try await concurrentChunkedCompactMap { (itemMetadata: SendableItemMetadata) -> Item? in
guard !itemMetadata.e2eEncrypted else {
Expand All @@ -28,12 +29,15 @@ extension [SendableItemMetadata] {
throw FilesDatabaseManager.parentMetadataNotFoundError(itemUrl: targetUrl)
}

let displayFileActions = Item.typeHasApplicableContextMenuItems(filters: allFilters, candidate: itemMetadata.contentType)

let item = Item(
metadata: itemMetadata,
parentItemIdentifier: parentItemIdentifier,
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: displayFileActions,
remoteSupportsTrash: remoteSupportsTrash,
log: log
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public extension Item {
account: account,
remoteInterface: remoteInterface,
dbManager: dbManager,
displayFileActions: false,
remoteSupportsTrash: true,
log: FileProviderLogMock()
)
Expand Down
Loading
Loading