diff --git a/doc/test-cases/regression/macos-file-provider/TC-0007-file-provider-mode-switching.md b/doc/test-cases/regression/macos-file-provider/TC-0007-file-provider-mode-switching.md
new file mode 100644
index 0000000000000..4134ec1288c35
--- /dev/null
+++ b/doc/test-cases/regression/macos-file-provider/TC-0007-file-provider-mode-switching.md
@@ -0,0 +1,38 @@
+
+
+# TC-0007: Switching the app-level File Provider mode
+
+- **Platform:** macOS
+- **Related Issues:** None yet
+
+## Description
+
+The File Provider integration and classic sync folders (FinderSync extension) cannot run at the same time on macOS: as soon as any file provider domain exists, the system deactivates the FinderSync extension. The client therefore offers one app-level File Provider switch in the General settings. Enabling it discards all classic sync folder connections (local files are kept); disabling it removes all file provider domains without recreating classic folders. Both directions require explicit confirmation.
+
+## Preconditions
+
+- A client build with the file provider module (`Nextcloud-vfs-…`) on macOS 14 or later.
+- At least one configured account with one or more classic sync folders containing synced files.
+
+## Test Steps
+
+| Step | Action | Expected Result |
+|------|--------|-----------------|
+| 1 | Open Settings → General | A "Use macOS File Provider Integration" switch is shown with an explanatory text; it is off |
+| 2 | Open the account's settings page | The "Classic sync" panel with the folder list is shown; no File Provider panel exists |
+| 3 | Turn the File Provider switch on | A confirmation dialog appears, listing the affected accounts and their classic sync folder connections, stating that files stay on disk |
+| 4 | Click "Cancel" | Nothing changes: switch stays off, classic folders keep syncing |
+| 5 | Turn the switch on again and click "Enable File Provider" | The switch is disabled while setting up; afterwards every account appears in Finder under "Locations", all classic sync folder connections are removed, the local folders and their files remain on disk, FinderSync badges disappear, and the "Classic sync" panel is hidden in every account's settings |
+| 6 | Turn the switch off | A confirmation dialog explains that files leave Finder "Locations", unsynced items are preserved, and classic sync folders are not recreated automatically |
+| 7 | Click "Disable File Provider" | All file provider domains are removed; no classic folders are created; the "Classic sync" panel (with the "Add Folder Sync Connection" button) reappears in the account settings |
+| 8 | Add a new folder sync connection and restart the client | The classic folder syncs again and FinderSync badges work |
+
+## Expected Results
+
+- Classic sync folders and File Provider are never active at the same time.
+- No local files are deleted by either switch direction.
+- A deployment updating with both configured (or a client killed mid-switch) shows a "Choose how to sync your files" dialog at startup offering "Keep File Provider", "Keep classic sync folders" and "Decide later"; "Decide later" keeps both working, shows a warning banner in the affected account settings, and asks again on the next launch.
+- New accounts added while the mode is on automatically get a file provider domain; the account wizard skips the sync options step. With the mode off, the wizard offers classic folder setup only.
diff --git a/resources.qrc b/resources.qrc
index 940b9cc0ff4bf..711cb8dbea766 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -80,7 +80,6 @@
src/gui/wizard/qml/WizardComboBox.qml
src/gui/wizard/qml/WizardDialogFrame.qml
src/gui/wizard/qml/WizardTextField.qml
- src/gui/macOS/ui/FileProviderSettings.qml
src/gui/macOS/ui/FileProviderFileDelegate.qml
src/gui/integration/FileActionsWindow.qml
src/gui/GovernanceLabelsDialog.qml
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index 8f9d82dbf04b9..b2b0b9304729f 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -196,16 +196,14 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
connect(connectionSettingsButton, &QPushButton::clicked, this, &AccountSettings::showConnectionSettingsDialog);
_ui->verticalLayout_2->removeWidget(_ui->accountStatusPanel);
- _ui->verticalLayout_2->removeWidget(_ui->fileProviderPanel);
_ui->verticalLayout_2->removeWidget(_ui->syncFoldersPanel);
_ui->verticalLayout_2->removeWidget(_ui->connectionSettingsPanel);
_ui->verticalLayout_2->removeWidget(_ui->accountActionsPanel);
_ui->connectionSettingsPanel->hide();
- _ui->verticalLayout_2->insertWidget(0, _ui->fileProviderPanel);
- _ui->verticalLayout_2->insertWidget(1, _ui->syncFoldersPanel);
- _ui->verticalLayout_2->insertWidget(2, _encryptionPanel);
- _ui->verticalLayout_2->insertWidget(3, _ui->accountStatusPanel);
- _ui->verticalLayout_2->insertWidget(4, _ui->accountActionsPanel);
+ _ui->verticalLayout_2->insertWidget(0, _ui->syncFoldersPanel);
+ _ui->verticalLayout_2->insertWidget(1, _encryptionPanel);
+ _ui->verticalLayout_2->insertWidget(2, _ui->accountStatusPanel);
+ _ui->verticalLayout_2->insertWidget(3, _ui->accountActionsPanel);
_model->setAccountState(_accountState);
_model->setParent(this);
@@ -215,9 +213,6 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
_ui->syncFoldersPanelContents->setAutoFillBackground(false);
_ui->syncFoldersPanelContents->setAttribute(Qt::WA_StyledBackground, false);
_ui->syncFoldersPanelContents->setContentsMargins(0, 0, 0, 0);
- _ui->fileProviderPanelContents->setAutoFillBackground(false);
- _ui->fileProviderPanelContents->setAttribute(Qt::WA_StyledBackground, false);
- _ui->fileProviderPanelContents->setContentsMargins(0, 0, 0, 0);
_ui->connectionSettingsPanelContents->setAutoFillBackground(false);
_ui->connectionSettingsPanelContents->setAttribute(Qt::WA_StyledBackground, false);
_ui->connectionSettingsPanelContents->setContentsMargins(0, 0, 0, 0);
@@ -244,30 +239,30 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
new ToolTipUpdater(_ui->_folderList);
#if defined(BUILD_FILE_PROVIDER_MODULE)
- if (Mac::FileProvider::available()) {
- const auto fileProviderPanelContents = _ui->fileProviderPanelContents;
- const auto fpSettingsLayout = new QVBoxLayout(fileProviderPanelContents);
- const auto fpAccountUserIdAtHost = _accountState->account()->userIdAtHostWithPort();
- const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
- const auto fpSettingsWidget = fpSettingsController->settingsViewWidget(fpAccountUserIdAtHost, fileProviderPanelContents,
- QQuickWidget::SizeRootObjectToView);
- fpSettingsLayout->setContentsMargins(0, 0, 0, 0);
- fpSettingsLayout->setSpacing(0);
-
- fpSettingsWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- if (const auto fpSettingsWidgetLayout = fpSettingsWidget->layout()) {
- fpSettingsWidgetLayout->setContentsMargins(0, 0, 0, 0);
- }
- fpSettingsLayout->addWidget(fpSettingsWidget, 1);
- fileProviderPanelContents->setLayout(fpSettingsLayout);
- } else {
- // macOS 13 Ventura: the file provider feature is unsupported there.
- // This branch can be removed once Ventura is no longer supported.
- _ui->fileProviderPanel->setVisible(false);
- }
-#else
- _ui->fileProviderPanel->setVisible(false);
+ // The File Provider integration is an app-level mode, toggled in the General
+ // settings. While it is enabled, classic sync folders are unavailable: hide the
+ // "Classic sync" panel, except when the account still has classic folders
+ // configured (a conflict the banner asks the user to resolve).
+ const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
+ connect(fpSettingsController, &Mac::FileProviderSettingsController::fileProviderModeEnabledChanged,
+ this, &AccountSettings::updateSyncFoldersPanelVisibility);
+ connect(fpSettingsController, &Mac::FileProviderSettingsController::fileProviderModeEnabledChanged,
+ this, &AccountSettings::refreshE2eEncryptionMessage);
+ // The reconciliation "Keep File Provider" path applies the mode without emitting
+ // fileProviderModeEnabledChanged (the flag was already on), so also refresh on the
+ // bulk-apply completion to update a page that is open during the transition.
+ connect(fpSettingsController, &Mac::FileProviderSettingsController::fileProviderModeApplyFinished,
+ this, [this](bool, const QStringList &) {
+ refreshE2eEncryptionMessage();
+ updateSyncFoldersPanelVisibility();
+ });
+ connect(FolderMan::instance(), &FolderMan::folderListChanged,
+ this, &AccountSettings::updateSyncFoldersPanelVisibility);
+ connect(_ui->fileProviderConflictResolveButton, &QPushButton::clicked, this, [fpSettingsController] {
+ fpSettingsController->performStartupReconciliation();
+ });
#endif
+ updateSyncFoldersPanelVisibility();
const auto mouseCursorChanger = new MouseCursorChanger(this);
mouseCursorChanger->folderList = _ui->_folderList;
@@ -329,7 +324,10 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
// Connect E2E stuff
if (_accountState->isConnected()) {
setupE2eEncryption();
+ _e2eEncryptionSetupDone = true;
} else {
+ // Not connected yet: setupE2eEncryption() (which builds the File-Provider-aware
+ // message) runs on the first Connected transition in slotAccountStateChanged().
_ui->encryptionMessageLabel->setText(tr("End-to-end encryption has not been initialized on this account."));
}
_ui->encryptionMessageLabel->setTextFormat(Qt::RichText);
@@ -940,6 +938,13 @@ void AccountSettings::slotFolderWizardAccepted()
QStringList() << QLatin1String("/"));
folderMan->scheduleAllFolders();
emit folderChanged();
+ } else {
+ // addFolder can refuse (e.g. classic sync folders are unavailable while the
+ // File Provider integration is enabled). Don't leave the user believing the
+ // wizard succeeded.
+ QMessageBox::warning(this, tr("Folder creation failed"),
+ tr("
Could not add the folder sync connection for %1.
")
+ .arg(Utility::escape(QDir::toNativeSeparators(definition.localPath))));
}
}
@@ -1560,6 +1565,15 @@ void AccountSettings::slotAccountStateChanged()
refreshSelectiveSyncStatus();
if (state == AccountState::State::Connected) {
+ // Build the encryption message on the first connect if the account was still
+ // connecting when this page was constructed — otherwise the panel would be
+ // revealed below still showing the not-connected placeholder instead of the
+ // File-Provider-aware text. Guarded so e2e initialization is set up only once;
+ // File-Provider-mode changes are handled separately via the controller signals.
+ if (!_e2eEncryptionSetupDone) {
+ setupE2eEncryption();
+ _e2eEncryptionSetupDone = true;
+ }
checkClientSideEncryptionState();
}
}
@@ -1936,19 +1950,40 @@ QAction *AccountSettings::addActionToEncryptionMessage(const QString &actionTitl
void AccountSettings::setupE2eEncryptionMessage()
{
-#ifdef BUILD_FILE_PROVIDER_MODULE
- const auto encryptionMessage = tr("This account supports end-to-end encryption, but it needs to be set up first.") + QStringLiteral(" ") + tr("The File Provider extension does not support end-to-end encryption yet.");
-#else
- const auto encryptionMessage = tr("This account supports end-to-end encryption, but it needs to be set up first.");
-#endif
- _ui->encryptionMessageLabel->setText(encryptionMessage);
setEncryptionMessageIcon(Theme::createColorAwareIcon(QStringLiteral(":/client/theme/info.svg")));
setEncryptionPanelVisible(false);
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ if (Mac::FileProvider::available() && Mac::FileProviderSettingsController::instance()->fileProviderModeEnabled()) {
+ // The File Provider extension does not support end-to-end encryption yet, so it
+ // cannot be set up while File Provider mode is enabled. Replace the whole setup
+ // prompt with an informational message and offer no "Set up encryption" action.
+ _ui->encryptionMessageLabel->setText(tr("The File Provider extension does not support end-to-end encryption yet."));
+ return;
+ }
+#endif
+
+ _ui->encryptionMessageLabel->setText(tr("This account supports end-to-end encryption, but it needs to be set up first."));
+
auto *const actionSetupE2e = addActionToEncryptionMessage(tr("Set up encryption"), e2EeUiActionSetupEncryptionId);
connect(actionSetupE2e, &QAction::triggered, this, &AccountSettings::slotE2eEncryptionGenerateKeys);
}
+void AccountSettings::refreshE2eEncryptionMessage()
+{
+ // Only the not-yet-initialized setup prompt depends on the File Provider mode; once
+ // encryption is initialized the message shown is independent of it.
+ if (_accountState->account()->e2e()->isInitialized()) {
+ return;
+ }
+
+ // Rebuild from scratch so a stale "Set up encryption" action cannot linger (and to
+ // avoid re-connecting the same action twice).
+ removeActionFromEncryptionMessage(e2EeUiActionSetupEncryptionId);
+ setupE2eEncryptionMessage();
+ checkClientSideEncryptionState();
+}
+
void AccountSettings::setEncryptionPanelVisible(bool visible)
{
_ui->encryptionMessage->setVisible(visible);
@@ -1957,6 +1992,39 @@ void AccountSettings::setEncryptionPanelVisible(bool visible)
}
}
+void AccountSettings::updateSyncFoldersPanelVisibility()
+{
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+ const auto fpModeOn = Mac::FileProvider::available()
+ && Mac::FileProviderSettingsController::instance()->fileProviderModeEnabled();
+
+ auto hasClassicFolders = false;
+ const auto folderMap = FolderMan::instance()->map();
+
+ for (const auto folder : folderMap) {
+ if (folder->accountState() == _accountState) {
+ hasClassicFolders = true;
+ break;
+ }
+ }
+
+ // Hide the classic sync settings while File Provider mode is on — except when this
+ // account still has classic folders configured (mixed state): those must stay
+ // visible, together with the banner asking the user to resolve the conflict.
+ _ui->syncFoldersPanel->setVisible(!fpModeOn || hasClassicFolders);
+ _ui->fileProviderConflictBanner->setVisible(fpModeOn && hasClassicFolders);
+
+ if (fpModeOn && hasClassicFolders && _ui->fileProviderConflictBannerIcon->pixmap().isNull()) {
+ const auto iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
+ const auto warningIcon = Theme::createColorAwareIcon(QStringLiteral(":/client/theme/black/warning.svg"));
+ _ui->fileProviderConflictBannerIcon->setPixmap(warningIcon.pixmap(iconSize, iconSize));
+ }
+#else
+ _ui->syncFoldersPanel->setVisible(true);
+ _ui->fileProviderConflictBanner->setVisible(false);
+#endif
+}
+
void AccountSettings::setEncryptionMessageIcon(const QIcon &icon)
{
if (icon.isNull()) {
diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h
index b863ec2f43858..62dbfee3cb9b0 100644
--- a/src/gui/accountsettings.h
+++ b/src/gui/accountsettings.h
@@ -141,12 +141,14 @@ private slots:
void checkClientSideEncryptionState();
void removeActionFromEncryptionMessage(const QString &actionId);
void setEncryptionPanelVisible(bool visible);
+ void updateSyncFoldersPanelVisibility();
private:
bool event(QEvent *) override;
QAction *addActionToEncryptionMessage(const QString &actionTitle, const QString &actionId);
void setupE2eEncryptionMessage();
+ void refreshE2eEncryptionMessage();
void setEncryptionMessageIcon(const QIcon &icon);
void updateEncryptionMessageActions();
@@ -164,6 +166,7 @@ private slots:
QAction *_addAccountAction = nullptr;
bool _menuShown = false;
+ bool _e2eEncryptionSetupDone = false;
QFrame *_encryptionPanel = nullptr;
QHash _folderConnections;
diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui
index 83a762a6f0a43..0947be3120447 100644
--- a/src/gui/accountsettings.ui
+++ b/src/gui/accountsettings.ui
@@ -133,46 +133,6 @@
- -
-
-
-
- 0
- 0
-
-
-
- QFrame::Shape::NoFrame
-
-
-
- QLayout::SizeConstraint::SetDefaultConstraint
-
-
-
-
-
-
- true
-
-
-
- File Provider
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
-
-
-
-
@@ -206,6 +166,60 @@
+ -
+
+
+ false
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ File Provider is enabled, but this account still has classic sync folders. The Finder integration stays unavailable until this is resolved.
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Resolve…
+
+
+
+
+
+
-
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index 85c21b98758d3..38b41a14f43d4 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -515,14 +515,22 @@ Application::Application(int &argc, char **argv)
#if defined(BUILD_FILE_PROVIDER_MODULE)
Mac::FileProvider::instance();
+
+ // Instantiating the settings controller runs its startup migrations: initialising
+ // the app-level File Provider mode flag, and — on macOS 13 Ventura, where the file
+ // provider feature is unsupported — gracefully tearing down any pre-existing VFS
+ // domains.
+ Mac::FileProviderSettingsController::instance();
+
if (Mac::FileProvider::available()) {
Mac::FileProvider::instance()->configureXPC();
- } else {
- // macOS 13 Ventura: instantiating the settings controller triggers the
- // one-time cleanup that gracefully tears down any pre-existing VFS
- // domains. The check (and this branch) can be removed once Ventura is
- // no longer supported.
- Mac::FileProviderSettingsController::instance();
+
+ // Deferred so that the event loop is running: reconciles the file provider
+ // domains with the app-level mode and, if both File Provider mode and classic
+ // sync folders are configured, asks the user which one to keep.
+ QTimer::singleShot(0, this, [] {
+ Mac::FileProviderSettingsController::instance()->performStartupReconciliation();
+ });
}
#endif
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index c31565bdb7ac8..6a6c7de3670da 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -1387,6 +1387,15 @@ void FolderMan::slotFolderSyncFinished(const SyncResult &)
Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition &folderDefinition)
{
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ // Classic sync folders and the File Provider integration are mutually exclusive:
+ // the FinderSync extension cannot run while the File Provider extension is active.
+ if (ConfigFile().macFileProviderModeEnabled()) {
+ qCWarning(lcFolderMan) << "Refusing to add classic sync folder: File Provider mode is enabled.";
+ return nullptr;
+ }
+#endif
+
// Choose a db filename
auto definition = folderDefinition;
definition.journalPath = definition.defaultJournalPath(accountState->account());
diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp
index 7ac9317bc6a45..3d6e2378393a8 100644
--- a/src/gui/generalsettings.cpp
+++ b/src/gui/generalsettings.cpp
@@ -14,8 +14,18 @@
#include "settingspanelstyle.h"
#include "theme.h"
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "account.h"
+#include "accountstate.h"
+#include "folder.h"
+#include "folderman.h"
+#include "macOS/fileprovider.h"
+#include "macOS/fileprovidersettingscontroller.h"
+#endif
+
#include
#include
+#include
#include
#include
@@ -65,6 +75,26 @@ GeneralSettings::GeneralSettings(QWidget *parent)
_ui->monoIconsRowWidget->setVisible(monoIconsAvailable);
_ui->startupSeparator->setVisible(monoIconsAvailable);
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+ if (Mac::FileProvider::available() && !Theme::instance()->disableVirtualFilesSyncFolder()) {
+ const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
+
+ // "clicked" rather than "toggled": only direct user interaction may open the
+ // confirmation flow, never the programmatic setChecked in loadMiscSettings().
+ connect(_ui->fileProviderCheckBox, &QAbstractButton::clicked,
+ this, &GeneralSettings::slotFileProviderSwitchClicked);
+ connect(fpSettingsController, &Mac::FileProviderSettingsController::fileProviderModeEnabledChanged,
+ this, &GeneralSettings::loadMiscSettings);
+ connect(fpSettingsController, &Mac::FileProviderSettingsController::operationInProgressChanged,
+ this, &GeneralSettings::loadMiscSettings);
+ } else {
+ // macOS 13 Ventura (feature unsupported) or branding that bans virtual files.
+ _ui->fileProviderGroupBox->setVisible(false);
+ }
+#else
+ _ui->fileProviderGroupBox->setVisible(false);
+#endif
+
customizeStyle();
}
@@ -97,6 +127,16 @@ void GeneralSettings::loadMiscSettings()
_ui->quotaWarningNotificationsLabel->setEnabled(cfgFile.optionalServerNotifications());
_ui->quotaWarningNotificationsCheckBox->setEnabled(cfgFile.optionalServerNotifications());
_ui->quotaWarningNotificationsCheckBox->setChecked(cfgFile.showQuotaWarningNotifications());
+
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+ if (Mac::FileProvider::available()) {
+ const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
+ const auto fpOperationInProgress = fpSettingsController->isOperationInProgress();
+ _ui->fileProviderCheckBox->setChecked(fpSettingsController->fileProviderModeEnabled());
+ _ui->fileProviderCheckBox->setEnabled(!fpOperationInProgress);
+ _ui->fileProviderLabel->setEnabled(!fpOperationInProgress);
+ }
+#endif
}
void GeneralSettings::saveMiscSettings()
@@ -165,6 +205,105 @@ void GeneralSettings::slotToggleQuotaWarningNotifications(bool enable)
ConfigFile().setShowQuotaWarningNotifications(enable);
}
+void GeneralSettings::slotFileProviderSwitchClicked(bool checked)
+{
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+ const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
+
+ // The switch must not lead the actual state: snap it back immediately and only let
+ // it move once the controller confirms the change via fileProviderModeEnabledChanged.
+ {
+ const QSignalBlocker blocker(_ui->fileProviderCheckBox);
+ _ui->fileProviderCheckBox->setChecked(fpSettingsController->fileProviderModeEnabled());
+ }
+
+ if (checked == fpSettingsController->fileProviderModeEnabled()) {
+ return;
+ }
+
+ if (checked) {
+ confirmEnableFileProviderMode();
+ } else {
+ confirmDisableFileProviderMode();
+ }
+#else
+ Q_UNUSED(checked)
+#endif
+}
+
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+void GeneralSettings::confirmEnableFileProviderMode()
+{
+ // Gather the accounts whose classic sync folder connections enabling would discard.
+ QStringList accountsWithClassicFolders;
+
+ if (const auto folderMan = FolderMan::instance()) {
+ const auto folderMap = folderMan->map();
+ for (const auto folder : folderMap) {
+ const auto accountState = folder->accountState();
+ const auto account = accountState ? accountState->account() : nullptr;
+ const auto accountName = account ? account->userIdAtHostWithPort() : tr("Unknown account");
+ if (!accountsWithClassicFolders.contains(accountName)) {
+ accountsWithClassicFolders.append(accountName);
+ }
+ }
+ }
+
+ auto text = tr("File Provider will be enabled for all accounts. Your files will appear in Finder under the \"Locations\" section. Accounts added later will also be set up as File Providers.");
+
+ if (!accountsWithClassicFolders.isEmpty()) {
+ QStringList accountLines;
+ for (const auto &accountName : std::as_const(accountsWithClassicFolders)) {
+ accountLines.append(QStringLiteral("• ") + accountName);
+ }
+
+ text += QStringLiteral("\n\n")
+ + tr("This removes classic sync folder connections from the following accounts:")
+ + QStringLiteral("\n\n")
+ + accountLines.join(QStringLiteral("\n"))
+ + QStringLiteral("\n\n")
+ + tr("Synced files stay on your computer, but they will no longer be kept up to date and settings such as selective sync are discarded.");
+ }
+
+ const auto messageBox = new QMessageBox(QMessageBox::Question,
+ tr("Enable File Provider?"),
+ text,
+ QMessageBox::NoButton,
+ this);
+ messageBox->setAttribute(Qt::WA_DeleteOnClose);
+ const auto enableButton = messageBox->addButton(tr("Enable File Provider"), QMessageBox::AcceptRole);
+ messageBox->addButton(tr("Cancel"), QMessageBox::RejectRole);
+ connect(messageBox, &QMessageBox::finished, this, [messageBox, enableButton] {
+ if (messageBox->clickedButton() == enableButton) {
+ Mac::FileProviderSettingsController::instance()->setFileProviderModeEnabled(true);
+ }
+ });
+ messageBox->open();
+}
+
+void GeneralSettings::confirmDisableFileProviderMode()
+{
+ const auto text = tr("File Provider will be turned off for all accounts, and your files will no longer be available in Finder under the \"Locations\" section.")
+ + QStringLiteral("\n\n")
+ + tr("Items that were not uploaded yet will be preserved and shown to you. Classic sync folders are not set up again automatically — you can add folder sync connections afterwards in each account's settings.");
+
+ const auto messageBox = new QMessageBox(QMessageBox::Question,
+ tr("Disable File Provider?"),
+ text,
+ QMessageBox::NoButton,
+ this);
+ messageBox->setAttribute(Qt::WA_DeleteOnClose);
+ const auto disableButton = messageBox->addButton(tr("Disable File Provider"), QMessageBox::AcceptRole);
+ messageBox->addButton(tr("Cancel"), QMessageBox::RejectRole);
+ connect(messageBox, &QMessageBox::finished, this, [messageBox, disableButton] {
+ if (messageBox->clickedButton() == disableButton) {
+ Mac::FileProviderSettingsController::instance()->setFileProviderModeEnabled(false);
+ }
+ });
+ messageBox->open();
+}
+#endif
+
void GeneralSettings::slotStyleChanged()
{
customizeStyle();
@@ -173,6 +312,13 @@ void GeneralSettings::slotStyleChanged()
void GeneralSettings::customizeStyle()
{
SettingsPanelStyle::apply(this);
+
+ // SettingsPanelStyle gives every row equal margins on all sides. For the File
+ // Provider description we only want a left indent that matches the switch label
+ // above it, and no padding on the other sides — so copy the row's left margin and
+ // zero the rest. Done after apply() so it is not overwritten.
+ const auto rowLeftMargin = _ui->fileProviderRow->contentsMargins().left();
+ _ui->fileProviderDescriptionRow->setContentsMargins(rowLeftMargin, 0, 0, 0);
}
} // namespace OCC
diff --git a/src/gui/generalsettings.h b/src/gui/generalsettings.h
index 9b69b94e59d99..02f1816ca12b0 100644
--- a/src/gui/generalsettings.h
+++ b/src/gui/generalsettings.h
@@ -7,6 +7,9 @@
#ifndef MIRALL_GENERALSETTINGS_H
#define MIRALL_GENERALSETTINGS_H
+// For BUILD_FILE_PROVIDER_MODULE, which gates the File Provider members below.
+#include "config.h"
+
#include
namespace OCC {
@@ -38,11 +41,17 @@ private slots:
void slotToggleChatNotifications(bool);
void slotToggleCallNotifications(bool);
void slotToggleQuotaWarningNotifications(bool);
+ void slotFileProviderSwitchClicked(bool checked);
void loadMiscSettings();
private:
void customizeStyle();
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ void confirmEnableFileProviderMode();
+ void confirmDisableFileProviderMode();
+#endif
+
Ui::GeneralSettings *_ui;
bool _currentlyLoading = false;
};
diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui
index 83eba393b3add..5b80de17228eb 100644
--- a/src/gui/generalsettings.ui
+++ b/src/gui/generalsettings.ui
@@ -117,6 +117,56 @@
+ -
+
+
+
+
+
+
+ 0
+
+
-
+
+
-
+
+
+ Enable &File Provider
+
+
+ fileProviderCheckBox
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ Files appear in Finder under the "Locations" section and do not use local storage space; contents download automatically when you open a file. Applies to all accounts and replaces classic sync folders and their Finder integration.
+
+
+ true
+
+
+
+
+
+
+
+
-
@@ -283,6 +333,7 @@
autostartCheckBox
monoIconsCheckBox
+ fileProviderCheckBox
serverNotificationsCheckBox
chatNotificationsCheckBox
callNotificationsCheckBox
diff --git a/src/gui/macOS/fileproviderdomainmanager.mm b/src/gui/macOS/fileproviderdomainmanager.mm
index c6c9a48b02390..4802d4f31a15c 100644
--- a/src/gui/macOS/fileproviderdomainmanager.mm
+++ b/src/gui/macOS/fileproviderdomainmanager.mm
@@ -43,13 +43,16 @@
/**
* @brief Synchronous and logging wrapper for `[NSFileProviderManager addDomain:]`.
+ * @return true when the domain was added, false when the system reported an error.
*/
- void addDomain(NSFileProviderDomain *domain)
+ bool addDomain(NSFileProviderDomain *domain)
{
qCInfo(lcMacFileProviderDomainManager) << "Adding domain" << domain.identifier;
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_enter(dispatchGroup);
+ __block bool addSucceeded = false;
+
[NSFileProviderManager addDomain:domain completionHandler:^(NSError * const error) {
if(error) {
qCWarning(lcMacFileProviderDomainManager) << "Error adding domain:"
@@ -60,10 +63,12 @@ void addDomain(NSFileProviderDomain *domain)
}
qCDebug(lcMacFileProviderDomainManager) << "Added domain with identifier" << domain.identifier;
+ addSucceeded = true;
dispatch_group_leave(dispatchGroup);
}];
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
+ return addSucceeded;
}
/**
@@ -405,7 +410,14 @@ QString addFileProviderDomain(const AccountState * const accountState)
const auto domainId = QUuid::createUuid().toString(QUuid::WithoutBraces);
NSFileProviderDomain * const domain = [[NSFileProviderDomain alloc] initWithIdentifier:domainId.toNSString() displayName:domainDisplayName.toNSString()];
domain.supportsSyncingTrash = YES;
- addDomain(domain);
+
+ if (!addDomain(domain)) {
+ qCWarning(lcMacFileProviderDomainManager) << "Failed to add file provider domain for account"
+ << accountId
+ << "- not persisting domain identifier.";
+ return {};
+ }
+
AccountManager::instance()->setFileProviderDomainIdentifier(accountId, domainId);
return domainId;
@@ -556,6 +568,10 @@ void removeFileProviderDomainData(NSString * const domainIdentifier)
const auto identifier = d->addFileProviderDomain(accountState);
+ if (identifier.isEmpty()) {
+ return {};
+ }
+
// Disconnect the domain when something changes regarding authentication
connect(accountState, &AccountState::stateChanged, this, [this, accountState] {
slotAccountStateChanged(accountState);
diff --git a/src/gui/macOS/fileprovidersettingscontroller.h b/src/gui/macOS/fileprovidersettingscontroller.h
index a8081f66fc898..f59208c316b2a 100644
--- a/src/gui/macOS/fileprovidersettingscontroller.h
+++ b/src/gui/macOS/fileprovidersettingscontroller.h
@@ -5,8 +5,9 @@
#pragma once
+#include
#include
-#include
+#include
class QAbstractListModel;
@@ -18,6 +19,11 @@ namespace Mac {
/**
* @brief Dedicated type to manage account configuration related to macOS file provider domains.
+ *
+ * The File Provider integration is an app-level mode: as soon as any file provider domain
+ * exists, macOS activates the File Provider extension, which prevents the FinderSync
+ * extension (used by classic sync folders) from running. The mode is therefore a single
+ * switch for the whole client; when it is on, every configured account gets a domain.
*/
class FileProviderSettingsController : public QObject
{
@@ -28,32 +34,47 @@ class FileProviderSettingsController : public QObject
public:
static FileProviderSettingsController *instance();
- [[nodiscard]] QQuickWidget *settingsViewWidget(const QString &accountUserIdAtHost,
- QWidget *const parent = nullptr,
- const QQuickWidget::ResizeMode resizeMode = QQuickWidget::SizeRootObjectToView);
+ /// App-level File Provider mode (single switch for all accounts).
+ [[nodiscard]] bool fileProviderModeEnabled() const;
[[nodiscard]] QStringList vfsEnabledAccounts() const;
+ [[nodiscard]] bool vfsEnabledForAccount(const QString &userIdAtHost) const;
+ [[nodiscard]] bool isOperationInProgress() const;
+ [[nodiscard]] QString operationMessage() const;
/**
- * @brief One-time changes in configuration and file provider domains to ensure client deployments updated to a sandboxed release still work.
+ * @brief Resolves conflicts between the app-level File Provider mode and configured
+ * classic sync folders by prompting the user; without a conflict, reconciles the
+ * file provider domains with the mode (creating or removing domains as needed).
+ * Invoked deferred at startup and from the account settings conflict banner.
*/
- void migrateToAppSandbox();
-
- [[nodiscard]] Q_INVOKABLE bool vfsEnabledForAccount(const QString &userIdAtHost) const;
- [[nodiscard]] bool isOperationInProgress() const;
- [[nodiscard]] QString operationMessage() const;
+ void performStartupReconciliation();
signals:
void operationInProgressChanged();
void operationMessageChanged();
void vfsEnabledForAccountChanged(const QString &userIdAtHost);
+ void fileProviderModeEnabledChanged(bool enabled);
+ void fileProviderModeApplyFinished(bool enabled, const QStringList &failedAccounts);
public slots:
- void setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled, const bool showInformationDialog = true);
+ /**
+ * @brief Switches the app-level File Provider mode. The caller is responsible for
+ * having obtained explicit user confirmation. Enabling discards all classic sync
+ * folder configurations (files stay on disk) once every account's domain has been
+ * created successfully; disabling removes all domains without recreating any
+ * classic sync folders.
+ */
+ void setFileProviderModeEnabled(const bool enabled);
private:
explicit FileProviderSettingsController(QObject *parent = nullptr);
+ void setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled);
+ void applyFileProviderModeToAllAccounts(const bool enabled);
+ void removeAllClassicSyncFolders();
+ void showReconciliationDialog();
+
[[nodiscard]] QString fileProviderDomainIdentifierForAccount(const QString &userIdAtHost) const;
void setOperationInProgress(bool inProgress, const QString &message = QString());
@@ -63,6 +84,7 @@ public slots:
QHash _userInfos;
bool _isOperationInProgress = false;
+ bool _reconciliationDialogShowing = false;
QString _operationMessage;
};
diff --git a/src/gui/macOS/fileprovidersettingscontroller_mac.mm b/src/gui/macOS/fileprovidersettingscontroller_mac.mm
index 0bb2bc3b97cd6..a7c546b478b09 100644
--- a/src/gui/macOS/fileprovidersettingscontroller_mac.mm
+++ b/src/gui/macOS/fileprovidersettingscontroller_mac.mm
@@ -8,18 +8,17 @@
#include
#include
#include
-#include
-#include
-#include
#include
+#include
#include "gui/accountmanager.h"
-#include "gui/systray.h"
+#include "gui/folderman.h"
#include "gui/userinfo.h"
#include "gui/macOS/fileprovider.h"
#include "gui/macOS/fileprovideritemmetadata.h"
#include "gui/macOS/fileproviderutils.h"
#include "libsync/configfile.h"
+#include "libsync/theme.h"
// Objective-C imports
#import
@@ -28,11 +27,6 @@
// End of Objective-C imports
namespace {
-constexpr auto fpSettingsQmlPath = "qrc:/qml/src/gui/macOS/ui/FileProviderSettings.qml";
-
-// QML properties -- make sure they match up in QML file!
-constexpr auto fpAccountUserIdAtHostProp = "accountUserIdAtHost";
-
// NSUserDefaults entries
constexpr auto enabledAccountsSettingsKey = "enabledAccounts";
@@ -50,6 +44,7 @@
enum class VfsAccountsAction {
VfsAccountsNoAction,
VfsAccountsEnabledChanged,
+ VfsAccountsFailed,
};
explicit MacImplementation(FileProviderSettingsController *const parent)
@@ -57,15 +52,22 @@ explicit MacImplementation(FileProviderSettingsController *const parent)
q = parent;
migrateToAppSandbox();
+ ensureModeFlagInitialized();
removeOrphanedDomains();
if (Mac::FileProvider::available()) {
- restoreMissingDomains();
+ // Domains are reconciled with the app-level mode later, in
+ // performStartupReconciliation(), once the event loop runs and a possible
+ // conflict with classic sync folders can be put in front of the user.
Mac::FileProvider::instance()->domainManager()->reconcileDomainDisplayNames();
Mac::FileProvider::instance()->domainManager()->reconnectAll();
Mac::FileProvider::instance()->configureXPC();
} else {
disableFileProviderForAllEnabledAccountsOnUnsupportedOS();
+
+ // Keep the persisted app-level mode consistent with what this OS can run, so
+ // the wizard, folder guard and settings pages all read the same state.
+ ConfigFile().setMacFileProviderModeEnabled(false);
}
};
@@ -104,8 +106,8 @@ explicit MacImplementation(FileProviderSettingsController *const parent)
const auto existingDomainId = q->fileProviderDomainIdentifierForAccount(userIdAtHost);
- if (setEnabled && existingDomainId.isEmpty() == false) {
- qCWarning(lcFileProviderSettingsController) << "Cancelling because account already has a domain identifier!" << userIdAtHost;
+ if (!setEnabled && existingDomainId.isEmpty()) {
+ qCInfo(lcFileProviderSettingsController) << "Account has no file provider domain, nothing to disable." << userIdAtHost;
return VfsAccountsAction::VfsAccountsNoAction;
}
@@ -118,7 +120,20 @@ explicit MacImplementation(FileProviderSettingsController *const parent)
}
if (setEnabled) {
+ // addDomainForAccount is idempotent: it returns the existing identifier when
+ // the domain is still registered with the system, re-creates it when the
+ // stored identifier is stale (the "fake" identifiers minted by
+ // migrateToAppSandbox, or a domain the user removed in System Settings), or
+ // creates a fresh one. Always going through it guarantees a real domain
+ // exists before the caller discards the classic sync folders.
auto const identifier = Mac::FileProvider::instance()->domainManager()->addDomainForAccount(accountState.data());
+
+ if (identifier.isEmpty()) {
+ qCWarning(lcFileProviderSettingsController) << "Failed to create file provider domain for account"
+ << userIdAtHost;
+ return VfsAccountsAction::VfsAccountsFailed;
+ }
+
accountManager->setFileProviderDomainIdentifier(userIdAtHost, identifier);
} else {
// Check if the extension has dirty user data before removing the domain.
@@ -207,52 +222,95 @@ void migrateToAppSandbox()
qCInfo(lcFileProviderSettingsController) << "App sandbox migration completed.";
}
- void restoreMissingDomains()
+ void ensureModeFlagInitialized()
{
- qCInfo(lcFileProviderSettingsController) << "Restoring missing domains...";
+ ConfigFile cfg;
- const auto domains = Mac::FileProvider::instance()->domainManager()->getDomains();
- const auto accountStates = AccountManager::instance()->accounts();
- QSet existingDomainIdentifiers;
+ const auto brandingDisablesVfs = Theme::instance()->disableVirtualFilesSyncFolder();
+ const auto accounts = AccountManager::instance()->accounts();
+
+ // With no accounts configured there is no deployment state to preserve, so always
+ // (re-)default to File Provider on a supported, non-branding-disabled build. This
+ // is the product default for a new setup — the first account becomes a File
+ // Provider — and it also recovers a stale flag left behind after all accounts were
+ // removed (removing accounts does not clear this key). Done before the "already
+ // set" guard on purpose. On an unsupported OS the caller forces the flag back off
+ // afterwards.
+ if (accounts.isEmpty()) {
+ qCInfo(lcFileProviderSettingsController) << "No accounts configured; defaulting app-level file provider mode to" << !brandingDisablesVfs;
+ cfg.setMacFileProviderModeEnabled(!brandingDisablesVfs);
+ return;
+ }
- for (NSFileProviderDomain * const domain : domains) {
- existingDomainIdentifiers.insert(QString::fromNSString(domain.identifier));
+ // Existing deployment: decide the app-level mode only once, so a later explicit
+ // user choice is respected across launches. Keep per-account File Provider users
+ // on File Provider; leave a classic-only deployment (accounts configured, none
+ // using the File Provider) on classic so an upgrade does not disrupt them.
+ if (cfg.macFileProviderModeEnabledIsSet()) {
+ return;
}
- for (const auto &accountState : accountStates) {
- const auto account = accountState->account();
+ const auto modeEnabled = !enabledAccounts().isEmpty() && !brandingDisablesVfs;
+ qCInfo(lcFileProviderSettingsController) << "Initialising app-level file provider mode:" << modeEnabled;
+ cfg.setMacFileProviderModeEnabled(modeEnabled);
+ }
- if (!account) {
- continue;
- }
+ void ensureDomainConfigurationMatchesMode()
+ {
+ const auto modeEnabled = ConfigFile().macFileProviderModeEnabled();
+ qCInfo(lcFileProviderSettingsController) << "Reconciling file provider domains with app-level mode:" << modeEnabled;
- const auto identifier = account->fileProviderDomainIdentifier();
+ const auto accountStates = AccountManager::instance()->accounts();
- if (identifier.isEmpty()) {
- continue;
+ if (modeEnabled) {
+ const auto domains = Mac::FileProvider::instance()->domainManager()->getDomains();
+ QSet existingDomainIdentifiers;
+
+ for (NSFileProviderDomain * const domain : domains) {
+ existingDomainIdentifiers.insert(QString::fromNSString(domain.identifier));
}
- if (existingDomainIdentifiers.contains(identifier) == false) {
- qCInfo(lcFileProviderSettingsController) << "Restoring missing domain with identifier"
- << identifier
- << "for account"
- << account->userIdAtHostWithPort();
+ for (const auto &accountState : accountStates) {
+ const auto account = accountState->account();
+
+ if (!account) {
+ continue;
+ }
+
+ const auto userIdAtHost = account->userIdAtHostWithPort();
+ const auto identifier = account->fileProviderDomainIdentifier();
- const auto newIdentifier = Mac::FileProvider::instance()->domainManager()->addDomainForAccount(accountState.data());
+ if (identifier.isEmpty()) {
+ qCInfo(lcFileProviderSettingsController) << "Creating missing file provider domain for account" << userIdAtHost;
+ (void)setVfsEnabledForAccount(userIdAtHost, true);
+ } else if (existingDomainIdentifiers.contains(identifier) == false) {
+ qCInfo(lcFileProviderSettingsController) << "Restoring missing domain with identifier"
+ << identifier
+ << "for account"
+ << userIdAtHost;
- if (newIdentifier.isEmpty() == false) {
- AccountManager::instance()->setFileProviderDomainIdentifier(account->userIdAtHostWithPort(), newIdentifier);
+ const auto newIdentifier = Mac::FileProvider::instance()->domainManager()->addDomainForAccount(accountState.data());
+
+ if (newIdentifier.isEmpty() == false) {
+ AccountManager::instance()->setFileProviderDomainIdentifier(userIdAtHost, newIdentifier);
+ }
}
- } else {
- qCInfo(lcFileProviderSettingsController) << "Domain"
- << identifier
- << "for account"
- << account->userIdAtHostWithPort()
- << "is still there.";
+ }
+ } else {
+ for (const auto &accountState : accountStates) {
+ const auto account = accountState->account();
+
+ if (!account || account->fileProviderDomainIdentifier().isEmpty()) {
+ continue;
+ }
+
+ const auto userIdAtHost = account->userIdAtHostWithPort();
+ qCInfo(lcFileProviderSettingsController) << "Removing file provider domain of account" << userIdAtHost;
+ (void)setVfsEnabledForAccount(userIdAtHost, false);
}
}
- qCInfo(lcFileProviderSettingsController) << "Finished restoring missing domains.";
+ qCInfo(lcFileProviderSettingsController) << "Finished reconciling file provider domains with app-level mode.";
}
void removeOrphanedDomains()
@@ -346,20 +404,29 @@ void disableFileProviderForAllEnabledAccountsOnUnsupportedOS()
_userInfos.insert(accountUserIdAtHost, userInfo);
userInfo->setActive(true);
}
-}
-QQuickWidget *FileProviderSettingsController::settingsViewWidget(const QString &accountUserIdAtHost,
- QWidget *const parent,
- const QQuickWidget::ResizeMode resizeMode)
-{
- const auto settingsViewWidget = new QQuickWidget(Systray::instance()->trayEngine(), parent);
- settingsViewWidget->setResizeMode(resizeMode);
- settingsViewWidget->setSource(QUrl(fpSettingsQmlPath));
- settingsViewWidget->rootObject()->setProperty(fpAccountUserIdAtHostProp, accountUserIdAtHost);
- QObject::connect(settingsViewWidget->rootObject(), &QQuickItem::implicitHeightChanged, settingsViewWidget, [settingsViewWidget] {
- settingsViewWidget->updateGeometry();
+ connect(accManager, &AccountManager::accountAdded,
+ this, [this](AccountState *const accountState) {
+ const auto account = accountState ? accountState->account() : nullptr;
+
+ if (!account) {
+ return;
+ }
+
+ const auto accountUserIdAtHost = account->userIdAtHostWithPort();
+
+ if (!_userInfos.contains(accountUserIdAtHost)) {
+ const auto userInfo = new UserInfo(accountState, false, false, this);
+ _userInfos.insert(accountUserIdAtHost, userInfo);
+ userInfo->setActive(true);
+ }
+
+ if (Mac::FileProvider::available() && fileProviderModeEnabled() && !vfsEnabledForAccount(accountUserIdAtHost)) {
+ qCInfo(lcFileProviderSettingsController) << "File provider mode is enabled, setting up domain for new account"
+ << accountUserIdAtHost;
+ setVfsEnabledForAccount(accountUserIdAtHost, true);
+ }
});
- return settingsViewWidget;
}
QStringList FileProviderSettingsController::vfsEnabledAccounts() const
@@ -372,7 +439,7 @@ void disableFileProviderForAllEnabledAccountsOnUnsupportedOS()
return d->vfsEnabledForAccount(userIdAtHost);
}
-void FileProviderSettingsController::setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled, const bool showInformationDialog)
+void FileProviderSettingsController::setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled)
{
// Prevent multiple concurrent operations
if (_isOperationInProgress) {
@@ -397,18 +464,219 @@ void disableFileProviderForAllEnabledAccountsOnUnsupportedOS()
if (enabledAccountsAction == MacImplementation::VfsAccountsAction::VfsAccountsEnabledChanged) {
emit controller->vfsEnabledForAccountChanged(capturedUserIdAtHost);
+ }
+ });
+ });
+}
+
+bool FileProviderSettingsController::fileProviderModeEnabled() const
+{
+ return ConfigFile().macFileProviderModeEnabled();
+}
+
+void FileProviderSettingsController::setFileProviderModeEnabled(const bool enabled)
+{
+ if (_isOperationInProgress) {
+ qCWarning(lcFileProviderSettingsController) << "Operation already in progress, ignoring file provider mode change";
+ return;
+ }
+
+ if (enabled == fileProviderModeEnabled()) {
+ // Nothing to do, but let listeners (e.g. the settings switch) re-sync.
+ emit fileProviderModeEnabledChanged(enabled);
+ return;
+ }
+
+ if (enabled && !Mac::FileProvider::available()) {
+ qCWarning(lcFileProviderSettingsController) << "Cannot enable file provider mode, it is unavailable on this system.";
+ return;
+ }
- if (setEnabled && showInformationDialog) {
+ // The flag records the user's intent up front. Should anything below fail or the
+ // client die mid-way, "mode enabled + classic folders still configured" is picked
+ // up by performStartupReconciliation() on the next launch.
+ ConfigFile().setMacFileProviderModeEnabled(enabled);
+ emit fileProviderModeEnabledChanged(enabled);
+
+ applyFileProviderModeToAllAccounts(enabled);
+}
+
+void FileProviderSettingsController::applyFileProviderModeToAllAccounts(const bool enabled)
+{
+ if (_isOperationInProgress) {
+ qCWarning(lcFileProviderSettingsController) << "Operation already in progress, not applying file provider mode";
+ return;
+ }
+
+ setOperationInProgress(true, enabled ? tr("Setting up…") : tr("Cleaning up…"));
+
+ // Snapshot the account ids on the main thread; AccountManager is not to be iterated
+ // from the background worker.
+ QStringList accountIds;
+ const auto accountStates = AccountManager::instance()->accounts();
+
+ for (const auto &accountState : accountStates) {
+ if (const auto account = accountState->account()) {
+ accountIds.append(account->userIdAtHostWithPort());
+ }
+ }
+
+ const QStringList capturedAccountIds = accountIds;
+ auto *controller = this;
+
+ // NOTE: the per-account worker still reaches into AccountManager (accountFromUserId /
+ // setFileProviderDomainIdentifier) from this background queue — the same pre-existing
+ // cross-thread access the single-account path has always used. This loop widens the
+ // window; a full fix would marshal all AccountManager access back to the main thread.
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ QStringList changedAccounts;
+ QStringList failedAccounts;
+
+ for (const auto &accountId : capturedAccountIds) {
+ switch (controller->d->setVfsEnabledForAccount(accountId, enabled)) {
+ case MacImplementation::VfsAccountsAction::VfsAccountsEnabledChanged:
+ changedAccounts.append(accountId);
+ break;
+ case MacImplementation::VfsAccountsAction::VfsAccountsFailed:
+ failedAccounts.append(accountId);
+ break;
+ case MacImplementation::VfsAccountsAction::VfsAccountsNoAction:
+ break;
+ }
+ }
+
+ const QStringList capturedChangedAccounts = changedAccounts;
+ const QStringList capturedFailedAccounts = failedAccounts;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ controller->setOperationInProgress(false, QString());
+
+ for (const auto &accountId : capturedChangedAccounts) {
+ emit controller->vfsEnabledForAccountChanged(accountId);
+ }
+
+ if (enabled && capturedFailedAccounts.isEmpty()) {
+ // Only discard the classic sync folder configurations once every
+ // account's domain has verifiably been created.
+ controller->removeAllClassicSyncFolders();
+
+ if (!capturedChangedAccounts.isEmpty()) {
QMessageBox::information(nullptr,
- controller->tr("File Provider enabled"),
- controller->tr("File Provider has been enabled for this account.\n\n"
- "Your files are now accessible in Finder under the \"Locations\" section."));
+ tr("File Provider enabled"),
+ tr("File Provider has been enabled for all accounts.\n\nYour files are now accessible in Finder under the \"Locations\" section."));
}
+ } else if (enabled) {
+ QMessageBox::warning(nullptr,
+ tr("File Provider could not be enabled for all accounts"),
+ tr("File Provider could not be set up for the following account(s):\n%1\n\nYour classic sync folders were left unchanged. You will be asked how to proceed the next time %2 starts, or you can resolve it from the account settings.")
+ .arg(capturedFailedAccounts.join(QStringLiteral("\n")),
+ Theme::instance()->appNameGUI()));
}
+
+ emit controller->fileProviderModeApplyFinished(enabled, capturedFailedAccounts);
});
});
}
+void FileProviderSettingsController::removeAllClassicSyncFolders()
+{
+ const auto folderMan = FolderMan::instance();
+
+ if (!folderMan) {
+ return;
+ }
+
+ const auto folders = folderMan->map().values();
+
+ if (folders.isEmpty()) {
+ return;
+ }
+
+ qCInfo(lcFileProviderSettingsController) << "Removing" << folders.count()
+ << "classic sync folder connection(s); local files are kept on disk.";
+
+ for (const auto folder : folders) {
+ folderMan->removeFolder(folder);
+ }
+}
+
+void FileProviderSettingsController::performStartupReconciliation()
+{
+ if (!Mac::FileProvider::available()) {
+ return;
+ }
+
+ if (_isOperationInProgress) {
+ qCWarning(lcFileProviderSettingsController) << "Operation already in progress, skipping reconciliation";
+ return;
+ }
+
+ const auto folderMan = FolderMan::instance();
+ const auto hasClassicFolders = folderMan && !folderMan->map().isEmpty();
+
+ if (fileProviderModeEnabled() && hasClassicFolders) {
+ // App-level conflict: the File Provider extension and the FinderSync extension
+ // (classic sync folders) cannot run at the same time. Let the user pick a side.
+ showReconciliationDialog();
+ return;
+ }
+
+ d->ensureDomainConfigurationMatchesMode();
+}
+
+void FileProviderSettingsController::showReconciliationDialog()
+{
+ if (_reconciliationDialogShowing) {
+ return;
+ }
+
+ _reconciliationDialogShowing = true;
+
+ const auto messageBoxText = tr("This installation has both the File Provider integration and classic sync folders configured. macOS cannot run the File Provider integration and the classic Finder integration at the same time.")
+ + QStringLiteral("\n\n")
+ + tr("Keep File Provider: all accounts appear in Finder under \"Locations\". Classic sync folder connections will be removed. Already synced files will be preserved in place.")
+ + QStringLiteral("\n\n")
+ + tr("Keep classic sync folders: File Provider is turned off for all accounts. Items that were not uploaded yet will be preserved.");
+
+ const auto messageBox = new QMessageBox(QMessageBox::Question,
+ tr("Choose how to sync your files"),
+ messageBoxText);
+ messageBox->setAttribute(Qt::WA_DeleteOnClose);
+ const auto keepFileProviderButton = messageBox->addButton(tr("Keep File Provider"), QMessageBox::AcceptRole);
+ const auto keepClassicButton = messageBox->addButton(tr("Keep classic sync folders"), QMessageBox::AcceptRole);
+ // The single RejectRole button, so Esc and closing the window mean "Decide later".
+ messageBox->addButton(tr("Decide later"), QMessageBox::RejectRole);
+
+ connect(messageBox, &QMessageBox::finished, this, [this, messageBox, keepFileProviderButton, keepClassicButton] {
+ _reconciliationDialogShowing = false;
+
+ // The dialog is non-modal, so the mode may have been changed elsewhere (e.g. the
+ // General settings switch) while it was open. Re-validate against the current
+ // state so a stale click cannot destroy the user's configuration.
+ const auto modeEnabled = fileProviderModeEnabled();
+
+ if (messageBox->clickedButton() == keepFileProviderButton) {
+ if (!modeEnabled) {
+ qCInfo(lcFileProviderSettingsController) << "Ignoring stale 'Keep File Provider' choice; mode was disabled meanwhile.";
+ return;
+ }
+ // Create any missing domains and, on full success, discard the classic sync
+ // folder configurations (local files stay on disk).
+ applyFileProviderModeToAllAccounts(true);
+ } else if (messageBox->clickedButton() == keepClassicButton) {
+ if (!modeEnabled) {
+ qCInfo(lcFileProviderSettingsController) << "Ignoring stale 'Keep classic' choice; mode already disabled.";
+ return;
+ }
+ // The dialog is the explicit confirmation for turning File Provider off.
+ setFileProviderModeEnabled(false);
+ }
+ // "Decide later": both configurations stay untouched; the dialog is shown
+ // again on the next launch and can be reopened from the account settings.
+ });
+ messageBox->open();
+}
+
QString FileProviderSettingsController::fileProviderDomainIdentifierForAccount(const QString &userIdAtHost) const
{
const auto accountState = AccountManager::instance()->accountFromUserId(userIdAtHost);
diff --git a/src/gui/macOS/ui/FileProviderSettings.qml b/src/gui/macOS/ui/FileProviderSettings.qml
deleted file mode 100644
index a0d214bdcd3a4..0000000000000
--- a/src/gui/macOS/ui/FileProviderSettings.qml
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-import QtQuick 2.15
-import QtQuick.Controls 2.15
-import QtQuick.Layouts 1.15
-
-import Style 1.0
-import "../../filedetails"
-import "../../tray"
-
-import com.nextcloud.desktopclient 1.0
-
-Page {
- id: root
-
- property bool showBorder: false
- property var controller: FileProviderSettingsController
- property string accountUserIdAtHost: ""
-
- title: qsTr("File provider settings")
-
- background: Rectangle {
- color: palette.light
- border.width: root.showBorder ? Style.normalBorderWidth : 0
- border.color: palette.mid
- }
-
- leftPadding: 0
- rightPadding: 0
- topPadding: Style.fileProviderSettingsPadding // Style.standardSpacing is 10, the QtWidgets layout uses 12. set it here as well to avoid a rough cutoff
- bottomPadding: Style.fileProviderSettingsPadding
- implicitHeight: rootColumn.implicitHeight + topPadding + bottomPadding
-
- ColumnLayout {
- id: rootColumn
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- spacing: Style.standardSpacing
-
- RowLayout {
- id: virtualFilesLayout
-
- Layout.fillWidth: true
- Layout.leftMargin: Style.fileProviderSettingsPadding
- Layout.rightMargin: Style.fileProviderSettingsPadding
-
- EnforcedPlainTextLabel {
- Layout.fillWidth: true
- Layout.preferredWidth: Math.max(0, virtualFilesLayout.width - vfsEnabledCheckBox.width - virtualFilesLayout.spacing)
- horizontalAlignment: Text.AlignLeft
- wrapMode: Text.WordWrap
- text: qsTr("File Provider items appear like regular files, but they do not use local storage space. The content downloads automatically when you open the file. It is not possible to have a File Provider and classic sync Finder integration enabled simultaneously.")
- }
- Switch {
- id: vfsEnabledCheckBox
- Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
- Layout.preferredWidth: 30
- Layout.preferredHeight: 16
- implicitWidth: 30
- implicitHeight: 16
- leftPadding: 0
- rightPadding: 0
- topPadding: 0
- bottomPadding: 0
- checked: root.controller.vfsEnabledForAccount(root.accountUserIdAtHost)
- onClicked: root.controller.setVfsEnabledForAccount(root.accountUserIdAtHost, checked)
-
- indicator: Rectangle {
- readonly property color enabledTrackColor: vfsEnabledCheckBox.checked ? "#00679e" : "#6b6b6b"
- readonly property real disabledTrackAlpha: vfsEnabledCheckBox.checked ? 0.45 : 0.35
-
- implicitWidth: 30
- implicitHeight: 16
- x: 0
- y: parent.height / 2 - height / 2
- radius: height / 2
- color: Qt.rgba(enabledTrackColor.r, enabledTrackColor.g, enabledTrackColor.b, vfsEnabledCheckBox.enabled ? 1.0 : disabledTrackAlpha)
-
- Rectangle {
- width: 10
- height: 10
- x: vfsEnabledCheckBox.checked ? parent.width - width - 3 : 3
- y: 3.6
- radius: width / 2
- color: vfsEnabledCheckBox.enabled ? "#26000000" : "#14000000"
- }
-
- Rectangle {
- width: 10
- height: 10
- x: vfsEnabledCheckBox.checked ? parent.width - width - 3 : 3
- y: 3
- radius: width / 2
- color: "#ffffff"
- opacity: vfsEnabledCheckBox.enabled ? 1.0 : 0.8
- }
-
- Rectangle {
- anchors.fill: parent
- anchors.margins: 1
- radius: height / 2
- color: "transparent"
- border.width: vfsEnabledCheckBox.activeFocus ? 2 : 0
- border.color: Qt.rgba(palette.highlight.r, palette.highlight.g, palette.highlight.b, 0.35)
- }
- }
-
- contentItem: Item {}
- }
- }
- }
-}
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 032771ea1cd53..62f2bbc3b4ccf 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -188,10 +188,6 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "TrayAccountAppsModel", TrayAccountAppsModel::instance());
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "Theme", Theme::instance());
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "Systray", Systray::instance());
-
-#ifdef BUILD_FILE_PROVIDER_MODULE
- qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "FileProviderSettingsController", Mac::FileProviderSettingsController::instance());
-#endif
}
void ownCloudGui::createTray()
diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp
index 02b302c194bcd..511059ba46e33 100644
--- a/src/gui/owncloudsetupwizard.cpp
+++ b/src/gui/owncloudsetupwizard.cpp
@@ -17,6 +17,10 @@
#include "nativetitlebar_mac.h"
#endif
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "macOS/fileprovidersettingscontroller.h"
+#endif
+
#include
#include
#include
@@ -31,6 +35,23 @@ Q_LOGGING_CATEGORY(lcWizard, "nextcloud.gui.wizard", QtInfoMsg)
OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent)
: QObject(parent)
{
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ // The wizard decides up front whether to offer classic sync options based on the
+ // app-level File Provider mode. If that mode is toggled from the General settings
+ // while the wizard is open, its decision is stale — cancel the wizard so it is
+ // restarted against the new mode, rather than provisioning a conflicting mix of a
+ // classic sync folder and a File Provider domain. fileProviderModeEnabledChanged is
+ // only emitted on an actual app-level toggle, never by the wizard's own account
+ // creation (that emits the per-account vfsEnabledForAccountChanged).
+ connect(Mac::FileProviderSettingsController::instance(),
+ &Mac::FileProviderSettingsController::fileProviderModeEnabledChanged,
+ this, [this] {
+ if (!_finished) {
+ qCInfo(lcWizard) << "File Provider mode changed while the account wizard was open; cancelling the wizard.";
+ finish(QDialog::Rejected);
+ }
+ });
+#endif
}
OwncloudSetupWizard::~OwncloudSetupWizard()
diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp
index ce835cccf115d..12871424e6b9c 100644
--- a/src/gui/settingsdialog.cpp
+++ b/src/gui/settingsdialog.cpp
@@ -508,7 +508,7 @@ void SettingsDialog::customizeStyle()
"#settings_content, #settings_content_scroll { background: transparent; border-radius: 12px; }"
/* Panels */
- "#generalGroupBox, #notificationsGroupBox, #advancedGroupBox, #syncBehaviorGroupBox,"
+ "#generalGroupBox, #fileProviderGroupBox, #notificationsGroupBox, #advancedGroupBox, #syncBehaviorGroupBox,"
"#advancedActionsGroupBox, #aboutAndUpdatesGroupBox, #updatesGroupBox {"
" background: palette(" BACKGROUND_PALETTE ");"
" border: none;"
@@ -516,14 +516,14 @@ void SettingsDialog::customizeStyle()
" margin: 0px;"
" padding: 0px;"
" }"
- "#accountStatusPanel, #encryptionPanel, #fileProviderPanel, #syncFoldersPanel, #accountActionsPanel {"
+ "#accountStatusPanel, #encryptionPanel, #syncFoldersPanel, #accountActionsPanel {"
" background: palette(" BACKGROUND_PALETTE ");"
" border: none;"
" border-radius: 12px;"
" margin: 0px;"
" padding: 6px;"
" }"
- "#generalGroupBox QLabel, #notificationsGroupBox QLabel, #advancedGroupBox QLabel,"
+ "#generalGroupBox QLabel, #fileProviderGroupBox QLabel, #notificationsGroupBox QLabel, #advancedGroupBox QLabel,"
"#syncBehaviorGroupBox QLabel, #advancedActionsGroupBox QLabel,"
"#aboutAndUpdatesGroupBox QLabel, #updatesGroupBox QLabel {"
" margin: 0px;"
diff --git a/src/gui/wizard/accountwizardcontroller.cpp b/src/gui/wizard/accountwizardcontroller.cpp
index 1ba290c21008e..1e6f11f4af57a 100644
--- a/src/gui/wizard/accountwizardcontroller.cpp
+++ b/src/gui/wizard/accountwizardcontroller.cpp
@@ -25,7 +25,6 @@
#ifdef BUILD_FILE_PROVIDER_MODULE
#include "gui/macOS/fileprovider.h"
-#include "gui/macOS/fileprovidersettingscontroller.h"
#endif
#ifdef Q_OS_MACOS
@@ -340,7 +339,7 @@ bool AccountWizardController::canUseVirtualFiles() const
}
#ifdef BUILD_FILE_PROVIDER_MODULE
- return Mac::FileProvider::available();
+ return Mac::FileProvider::available() && ConfigFile().macFileProviderModeEnabled();
#elif defined(Q_OS_WIN)
return bestAvailableVfsMode() == Vfs::WindowsCfApi && Theme::instance()->showVirtualFilesOption();
#else
@@ -351,7 +350,7 @@ bool AccountWizardController::canUseVirtualFiles() const
bool AccountWizardController::isUsingFileProvider() const
{
#ifdef BUILD_FILE_PROVIDER_MODULE
- return Mac::FileProvider::available();
+ return Mac::FileProvider::available() && ConfigFile().macFileProviderModeEnabled();
#else
return false;
#endif
@@ -1264,18 +1263,10 @@ void AccountWizardController::skipFolderConfiguration()
AccountState *AccountWizardController::applyAccountChanges()
{
auto manager = AccountManager::instance();
- AccountState *accountState = nullptr;
-#ifdef BUILD_FILE_PROVIDER_MODULE
- if (_syncMode == VirtualFiles) {
- accountState = manager->addAccount(_account);
- const auto accountId = accountState->account()->userIdAtHostWithPort();
- Mac::FileProviderSettingsController::instance()->setVfsEnabledForAccount(accountId, true, false);
- } else
-#endif
- {
- accountState = manager->addAccount(_account);
- }
+ // With the app-level File Provider mode enabled, the new account's file provider
+ // domain is created by the FileProviderSettingsController's accountAdded hook.
+ const auto accountState = manager->addAccount(_account);
manager->saveAccount(_account);
return accountState;
diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp
index 63767eeb05a0a..920ff4aa9ef20 100644
--- a/src/libsync/configfile.cpp
+++ b/src/libsync/configfile.cpp
@@ -60,6 +60,7 @@ static constexpr char logFlushC[] = "logFlush";
static constexpr char showExperimentalOptionsC[] = "showExperimentalOptions";
static constexpr char clientPreviousVersionC[] = "clientPreviousVersion";
static constexpr char fileProviderDomainsAppSandboxMigrationCompletedC[] = "fileProviderDomainsAppSandboxMigrationCompleted";
+static constexpr char macFileProviderModeEnabledC[] = "macFileProviderModeEnabled";
static constexpr char fileProviderDomainUuidsGroupC[] = "FileProviderDomainUuids";
static constexpr char fileProviderAccountIdsGroupC[] = "FileProviderAccountIds";
@@ -1365,6 +1366,25 @@ void ConfigFile::setFileProviderDomainsAppSandboxMigrationCompleted(const bool c
settings.setValue(fileProviderDomainsAppSandboxMigrationCompletedC, completed);
}
+bool ConfigFile::macFileProviderModeEnabled() const
+{
+ QSettings settings(configFile(), QSettings::IniFormat);
+ return settings.value(macFileProviderModeEnabledC, false).toBool();
+}
+
+bool ConfigFile::macFileProviderModeEnabledIsSet() const
+{
+ QSettings settings(configFile(), QSettings::IniFormat);
+ return settings.contains(macFileProviderModeEnabledC);
+}
+
+void ConfigFile::setMacFileProviderModeEnabled(const bool enabled)
+{
+ QSettings settings(configFile(), QSettings::IniFormat);
+ settings.setValue(macFileProviderModeEnabledC, enabled);
+ settings.sync();
+}
+
bool ConfigFile::isUpgrade() const
{
const auto currentVersion = QVersionNumber::fromString(MIRALL_VERSION_STRING);
diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h
index 60af427800000..552273b2ef387 100644
--- a/src/libsync/configfile.h
+++ b/src/libsync/configfile.h
@@ -266,6 +266,13 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
[[nodiscard]] bool fileProviderDomainsAppSandboxMigrationCompleted() const;
void setFileProviderDomainsAppSandboxMigrationCompleted(bool completed);
+ /// App-level macOS File Provider mode: when enabled, every account gets a file
+ /// provider domain and classic sync folders are unavailable (the File Provider
+ /// extension and the FinderSync extension cannot run at the same time).
+ [[nodiscard]] bool macFileProviderModeEnabled() const;
+ [[nodiscard]] bool macFileProviderModeEnabledIsSet() const;
+ void setMacFileProviderModeEnabled(bool enabled);
+
/// Helper function for migration/upgrade proccess
enum MigrationPhase {
NotStarted,
diff --git a/test/testaccountwizardcontroller.cpp b/test/testaccountwizardcontroller.cpp
index bbd3e5e0665d9..802dfec61d07e 100644
--- a/test/testaccountwizardcontroller.cpp
+++ b/test/testaccountwizardcontroller.cpp
@@ -7,9 +7,14 @@
#include "configfile.h"
#include "theme.h"
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "gui/macOS/fileprovider.h"
+#endif
+
#include
#include
#include
+#include
#include
using namespace OCC;
@@ -279,6 +284,36 @@ private slots:
QVERIFY(!controller.serverUrlEditable());
QVERIFY(controller.startLoginFlowAutomatically());
}
+
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ // Keep this test last: it redirects the configuration directory.
+ void followsAppLevelFileProviderMode()
+ {
+ QTemporaryDir dir;
+ ConfigFile::setConfDir(dir.path());
+ QVERIFY(dir.isValid());
+
+ ConfigFile cfg;
+ cfg.setMacFileProviderModeEnabled(false);
+
+ {
+ AccountWizardController controller;
+ QVERIFY(!controller.canUseVirtualFiles());
+ QVERIFY(!controller.isUsingFileProvider());
+ QVERIFY(controller.canUseClassicSync());
+ QVERIFY(controller.localSyncFolderRequired());
+ }
+
+ cfg.setMacFileProviderModeEnabled(true);
+
+ {
+ AccountWizardController controller;
+ QCOMPARE(controller.canUseVirtualFiles(), Mac::FileProvider::available());
+ QCOMPARE(controller.isUsingFileProvider(), Mac::FileProvider::available());
+ QCOMPARE(controller.localSyncFolderRequired(), !Mac::FileProvider::available());
+ }
+ }
+#endif
};
QTEST_MAIN(TestAccountWizardController)
diff --git a/test/testfolderman.cpp b/test/testfolderman.cpp
index 127a9b4f3144c..e06761a13bf8a 100644
--- a/test/testfolderman.cpp
+++ b/test/testfolderman.cpp
@@ -588,6 +588,55 @@ private slots:
QCOMPARE(FolderMan::instance()->map().count(), 0);
}
+
+ void testMacFileProviderModeEnabledConfig()
+ {
+ QTemporaryDir dir;
+ ConfigFile::setConfDir(dir.path());
+ QVERIFY(dir.isValid());
+
+ ConfigFile cfg;
+ QVERIFY(!cfg.macFileProviderModeEnabledIsSet());
+ QVERIFY(!cfg.macFileProviderModeEnabled());
+
+ cfg.setMacFileProviderModeEnabled(true);
+ QVERIFY(cfg.macFileProviderModeEnabledIsSet());
+ QVERIFY(cfg.macFileProviderModeEnabled());
+
+ cfg.setMacFileProviderModeEnabled(false);
+ QVERIFY(cfg.macFileProviderModeEnabledIsSet());
+ QVERIFY(!cfg.macFileProviderModeEnabled());
+ }
+
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ void testAddFolderRefusedWhenFileProviderModeEnabled()
+ {
+ _fm.reset({});
+ _fm.reset(new FolderMan{});
+
+ QTemporaryDir dir;
+ ConfigFile::setConfDir(dir.path());
+ QVERIFY(dir.isValid());
+ QVERIFY(QDir(dir.path()).mkpath(QStringLiteral("folder1")));
+
+ auto account = Account::create();
+ account->setCredentials(new FakeCredentials{new FakeQNAM({})});
+ account->setUrl(QUrl(QStringLiteral("http://example.de")));
+ auto accountState = new FakeAccountState(account);
+
+ // Classic sync folders and File Provider mode are mutually exclusive.
+ ConfigFile().setMacFileProviderModeEnabled(true);
+ QVERIFY(!FolderMan::instance()->addFolder(accountState, folderDefinition(dir.path() + QStringLiteral("/folder1"))));
+ QCOMPARE(FolderMan::instance()->map().count(), 0);
+
+ ConfigFile().setMacFileProviderModeEnabled(false);
+ QVERIFY(FolderMan::instance()->addFolder(accountState, folderDefinition(dir.path() + QStringLiteral("/folder1"))));
+ QCOMPARE(FolderMan::instance()->map().count(), 1);
+
+ FolderMan::instance()->unloadAndDeleteAllFolders();
+ QCOMPARE(FolderMan::instance()->map().count(), 0);
+ }
+#endif
};
QTEST_GUILESS_MAIN(TestFolderMan)
diff --git a/theme/Style/Style.qml b/theme/Style/Style.qml
index 18d6de8f41a5b..09941091252d5 100644
--- a/theme/Style/Style.qml
+++ b/theme/Style/Style.qml
@@ -117,7 +117,6 @@ QtObject {
property int smallSpacing: 5
property int extraSmallSpacing: 2
property int extraExtraSmallSpacing: 1
- readonly property int fileProviderSettingsPadding: 12
property int iconButtonWidth: 36
property int standardPrimaryButtonHeight: 40