diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderConfig.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderConfig.swift index 46d8724c2c..93c8caae76 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderConfig.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderConfig.swift @@ -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 } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 3e8e8ee4b5..2594f35b90 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -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 { diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h index 9c48841825..a4f11a5507 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h @@ -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 diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift index 8f8638bab0..03e261ee66 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift @@ -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)" + ) + } } diff --git a/src/gui/macOS/fileprovidersettingscontroller.h b/src/gui/macOS/fileprovidersettingscontroller.h index 40d04c528a..e6aac92979 100644 --- a/src/gui/macOS/fileprovidersettingscontroller.h +++ b/src/gui/macOS/fileprovidersettingscontroller.h @@ -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); diff --git a/src/gui/macOS/fileprovidersettingscontroller_mac.mm b/src/gui/macOS/fileprovidersettingscontroller_mac.mm index 7158346e41..174ef039dd 100644 --- a/src/gui/macOS/fileprovidersettingscontroller_mac.mm +++ b/src/gui/macOS/fileprovidersettingscontroller_mac.mm @@ -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); diff --git a/src/gui/macOS/fileproviderxpc.h b/src/gui/macOS/fileproviderxpc.h index 54e6dc1555..46dc8a8183 100644 --- a/src/gui/macOS/fileproviderxpc.h +++ b/src/gui/macOS/fileproviderxpc.h @@ -31,6 +31,8 @@ public: // Returns enabled and set state of fast enumeration for the given extension [[nodiscard]] std::optional> fastEnumerationStateForExtension(const QString &extensionAccountId) const; + [[nodiscard]] std::optional> 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; diff --git a/src/gui/macOS/fileproviderxpc_mac.mm b/src/gui/macOS/fileproviderxpc_mac.mm index dd7400919d..a4bf589da2 100644 --- a/src/gui/macOS/fileproviderxpc_mac.mm +++ b/src/gui/macOS/fileproviderxpc_mac.mm @@ -235,4 +235,38 @@ void FileProviderXPC::setFastEnumerationEnabledForExtension(const QString &exten [service setFastEnumerationEnabled:enabled]; } +std::optional> FileProviderXPC::trashDeletionEnabledStateForExtension(const QString &extensionAccountId) const +{ + qCInfo(lcFileProviderXPC) << "Checking if fast enumeration is enabled for extension" << extensionAccountId; + const auto service = (NSObject *)_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>{{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 *)_clientCommServices.value(extensionAccountId); + [service setTrashDeletionEnabled:enabled]; +} + } // namespace OCC::Mac diff --git a/src/gui/macOS/ui/FileProviderSettings.qml b/src/gui/macOS/ui/FileProviderSettings.qml index 52aba614a2..c5245e8d57 100644 --- a/src/gui/macOS/ui/FileProviderSettings.qml +++ b/src/gui/macOS/ui/FileProviderSettings.qml @@ -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) + } } } }