Merge pull request #8237 from nextcloud/feature/disable-vfs-trash

feat(shell_integration/macOS/FileProviderExt): Add option to disable deletion of items in Trash
This commit is contained in:
Matthieu Gallien 2025-05-14 18:02:15 +02:00 committed by GitHub
commit 565f145fa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 120 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import Foundation
struct FileProviderConfig {
private enum ConfigKey: String {
case fastEnumerationEnabled = "fastEnumerationEnabled"
case trashDeletionEnabled = "trashDeletionEnabled"
}
let domainIdentifier: NSFileProviderDomainIdentifier
@ -32,6 +33,13 @@ struct FileProviderConfig {
}
}
var trashDeletionEnabled: Bool {
get { internalConfig[ConfigKey.trashDeletionEnabled.rawValue] as? Bool ?? true }
set { internalConfig[ConfigKey.trashDeletionEnabled.rawValue] = newValue }
}
lazy var trashDeletionSet = internalConfig[ConfigKey.trashDeletionEnabled.rawValue] != nil
var fastEnumerationEnabled: Bool {
get { internalConfig[ConfigKey.fastEnumerationEnabled.rawValue] as? Bool ?? true }
set { internalConfig[ConfigKey.fastEnumerationEnabled.rawValue] = newValue }

View File

@ -422,6 +422,17 @@ import OSLog
}
let progress = Progress(totalUnitCount: 1)
guard config.trashDeletionEnabled || item.parentItemIdentifier != .trashContainer else {
Logger.fileProviderExtension.warning(
"""
System requested deletion of item in trash, but deleting trash items is disabled.
item: \(item.filename, privacy: .public)
"""
)
completionHandler(NSError.fileProviderErrorForRejectedDeletion(of: item))
return progress
}
Task {
let error = await item.delete(dbManager: dbManager)
if error != nil {

View File

@ -20,6 +20,8 @@
- (void)createDebugLogStringWithCompletionHandler:(void(^)(NSString *debugLogString, NSError *error))completionHandler;
- (void)getFastEnumerationStateWithCompletionHandler:(void(^)(BOOL enabled, BOOL set))completionHandler;
- (void)setFastEnumerationEnabled:(BOOL)enabled;
- (void)getTrashDeletionEnabledStateWithCompletionHandler:(void(^)(BOOL enabled, BOOL set))completionHandler;
- (void)setTrashDeletionEnabled:(BOOL)enabled;
@end

View File

@ -101,4 +101,17 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
}
}
}
func getTrashDeletionEnabledState(completionHandler: @escaping (Bool, Bool) -> Void) {
let enabled = fpExtension.config.trashDeletionEnabled
let set = fpExtension.config.trashDeletionSet
completionHandler(enabled, set)
}
func setTrashDeletionEnabled(_ enabled: Bool) {
fpExtension.config.trashDeletionEnabled = enabled
Logger.fileProviderExtension.info(
"Trash deletion setting changed to: \(enabled, privacy: .public)"
)
}
}

View File

@ -37,6 +37,8 @@ public:
[[nodiscard]] Q_INVOKABLE float remoteStorageUsageGbForAccount(const QString &userIdAtHost) const;
[[nodiscard]] Q_INVOKABLE bool fastEnumerationEnabledForAccount(const QString &userIdAtHost) const;
[[nodiscard]] Q_INVOKABLE bool fastEnumerationSetForAccount(const QString &userIdAtHost) const;
[[nodiscard]] Q_INVOKABLE bool trashDeletionEnabledForAccount(const QString &userIdAtHost) const;
[[nodiscard]] Q_INVOKABLE bool trashDeletionSetForAccount(const QString &userIdAtHost) const;
[[nodiscard]] Q_INVOKABLE QAbstractListModel *materialisedItemsModelForAccount(const QString &userIdAtHost);
[[nodiscard]] Q_INVOKABLE FileProviderDomainSyncStatus *domainSyncStatusForAccount(const QString &userIdAtHost) const;
@ -44,6 +46,7 @@ public:
public slots:
void setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled);
void setFastEnumerationEnabledForAccount(const QString &userIdAtHost, const bool setEnabled);
void setTrashDeletionEnabledForAccount(const QString &userIdAtHost, const bool setEnabled);
void createEvictionWindowForAccount(const QString &userIdAtHost);
void refreshMaterialisedItemsForAccount(const QString &userIdAtHost);
@ -57,6 +60,8 @@ signals:
void materialisedItemsForAccountChanged(const QString &userIdAtHost);
void fastEnumerationEnabledForAccountChanged(const QString &userIdAtHost);
void fastEnumerationSetForAccountChanged(const QString &userIdAtHost);
void trashDeletionEnabledForAccountChanged(const QString &userIdAtHost);
void trashDeletionSetForAccountChanged(const QString &userIdAtHost);
private:
explicit FileProviderSettingsController(QObject *parent = nullptr);

View File

@ -412,6 +412,44 @@ void FileProviderSettingsController::setFastEnumerationEnabledForAccount(const Q
emit fastEnumerationSetForAccountChanged(userIdAtHost);
}
bool FileProviderSettingsController::trashDeletionEnabledForAccount(const QString &userIdAtHost) const
{
const auto xpc = FileProvider::instance()->xpc();
if (!xpc) {
return true;
}
if (const auto trashDeletionState = xpc->trashDeletionEnabledStateForExtension(userIdAtHost)) {
return trashDeletionState->first;
}
return true;
}
bool FileProviderSettingsController::trashDeletionSetForAccount(const QString &userIdAtHost) const
{
const auto xpc = FileProvider::instance()->xpc();
if (!xpc) {
return false;
}
if (const auto state = xpc->trashDeletionEnabledStateForExtension(userIdAtHost)) {
return state->second;
}
return false;
}
void FileProviderSettingsController::setTrashDeletionEnabledForAccount(const QString &userIdAtHost, const bool setEnabled)
{
const auto xpc = FileProvider::instance()->xpc();
if (!xpc) {
// Reset state of UI elements
emit trashDeletionEnabledForAccountChanged(userIdAtHost);
emit trashDeletionSetForAccountChanged(userIdAtHost);
return;
}
xpc->setTrashDeletionEnabledForExtension(userIdAtHost, setEnabled);
emit trashDeletionEnabledForAccountChanged(userIdAtHost);
emit trashDeletionSetForAccountChanged(userIdAtHost);
}
unsigned long long FileProviderSettingsController::localStorageUsageForAccount(const QString &userIdAtHost) const
{
return d->localStorageUsageForAccount(userIdAtHost);

View File

@ -31,6 +31,8 @@ public:
// Returns enabled and set state of fast enumeration for the given extension
[[nodiscard]] std::optional<std::pair<bool, bool>> fastEnumerationStateForExtension(const QString &extensionAccountId) const;
[[nodiscard]] std::optional<std::pair<bool, bool>> trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const;
public slots:
void connectToExtensions();
void configureExtensions();
@ -39,6 +41,7 @@ public slots:
void createDebugArchiveForExtension(const QString &extensionAccountId, const QString &filename);
void setFastEnumerationEnabledForExtension(const QString &extensionAccountId, bool enabled) const;
void setTrashDeletionEnabledForExtension(const QString &extensionAccountId, bool enabled) const;
private slots:
void slotAccountStateChanged(AccountState::State state) const;

View File

@ -235,4 +235,38 @@ void FileProviderXPC::setFastEnumerationEnabledForExtension(const QString &exten
[service setFastEnumerationEnabled:enabled];
}
std::optional<std::pair<bool, bool>> FileProviderXPC::trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const
{
qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for extension" << extensionAccountId;
const auto service = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
if (service == nil) {
qCWarning(lcFileProviderXPC) << "Could not get service for extension" << extensionAccountId;
return std::nullopt;
}
__block BOOL receivedTrashDeletionEnabled = YES; // What is the value of the setting being used by the extension?
__block BOOL receivedTrashDeletionEnabledSet = NO; // Has the setting been set by the user?
__block BOOL receivedResponse = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[service getTrashDeletionEnabledStateWithCompletionHandler:^(BOOL enabled, BOOL set) {
receivedTrashDeletionEnabled = enabled;
receivedTrashDeletionEnabledSet = set;
receivedResponse = YES;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, semaphoreWaitDelta));
if (!receivedResponse) {
qCWarning(lcFileProviderXPC) << "Did not receive response for fast enumeration state";
return std::nullopt;
}
return std::optional<std::pair<bool, bool>>{{receivedTrashDeletionEnabled, receivedTrashDeletionEnabledSet}};
}
void FileProviderXPC::setTrashDeletionEnabledForExtension(const QString &extensionAccountId, bool enabled) const
{
qCInfo(lcFileProviderXPC) << "Setting trash deletion enabled for extension" << extensionAccountId << "to" << enabled;
const auto service = (NSObject<ClientCommunicationProtocol> *)_clientCommServices.value(extensionAccountId);
[service setTrashDeletionEnabled:enabled];
}
} // namespace OCC::Mac

View File

@ -131,6 +131,12 @@ Page {
}
}
}
CheckBox {
text: qsTr("Allow deletion of items in Trash")
checked: root.controller.trashDeletionEnabledForAccount(root.accountUserIdAtHost)
onClicked: root.controller.setTrashDeletionEnabledForAccount(root.accountUserIdAtHost, checked)
}
}
}
}