Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5127f76
refactor(migration): add a class to handle the migration logic.
camilasan Dec 4, 2025
50753e8
fix(testmigration): fix QSettings state between tests and wrong setter.
camilasan May 12, 2026
c335bfe
fix(migration): drop use of Migration() in configVersionMigration.
camilasan May 12, 2026
f19ff24
fix(migration): remove const from legacyData().
camilasan May 12, 2026
7148ab7
fix(accountmanager): remove legacy config path constants moved to Mig…
camilasan May 12, 2026
d4779d6
fix(migration): transfer ownership of legacy settings.
camilasan Aug 12, 2026
be3670a
fix(migration): trigger migration when config version differs from bi…
camilasan Aug 12, 2026
961c941
fix(migration): resolve unbranded to branded state.
camilasan Aug 12, 2026
82f1999
refactor(migration): reuse ConfigFile::backupConfigFiles in config mi…
camilasan Aug 12, 2026
f80b93e
refactor(migration): convert Migration to a static helper class.
camilasan Aug 12, 2026
6f54d4c
feat(settings): add ManagedSettings resolver with source precedence.
camilasan Aug 12, 2026
2b32369
feat(settings): add managed settings schema for update settings
camilasan Aug 12, 2026
e40393b
feat(settings): add user config setting source
camilasan Aug 12, 2026
bcff994
feat(settings): add platform device sources and factory
camilasan Aug 12, 2026
8f1afe3
refactor(settings): resolve update settings through the managed setti…
camilasan Aug 12, 2026
24b18b8
feat(settings): add resolveAll for managed settings diagnostics
camilasan Aug 12, 2026
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
6 changes: 3 additions & 3 deletions src/cmd/cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "accountsetupcommandlinemanager.h"
#include "folderman.h"
#include "configfile.h" // ONLY ACCESS THE STATIC FUNCTIONS!
#include "settings/migration.h"
#ifdef TOKEN_AUTH_ONLY
# include "creds/tokencredentials.h"
#else
Expand Down Expand Up @@ -371,13 +372,12 @@ void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList)
auto result = false;

auto folderManager = FolderMan::instance();
ConfigFile configFile;
configFile.setMigrationPhase(ConfigFile::MigrationPhase::SetupUsers);
Migration::setPhase(Migration::Phase::SetupUsers);
if (!setupAccountsOnly()) {
return result;
}

configFile.setMigrationPhase(ConfigFile::MigrationPhase::SetupFolders);
Migration::setPhase(Migration::Phase::SetupFolders);
const auto foldersListSize = folderManager->setupFolders();
folderManager->setSyncEnabled(true);

Expand Down
162 changes: 57 additions & 105 deletions src/gui/accountmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#if !DISABLE_ACCOUNT_MIGRATION
#include "legacyaccountselectiondialog.h"
#endif
#include "settings/migration.h"

#include <QSettings>
#include <QDir>
Expand Down Expand Up @@ -69,11 +70,6 @@ constexpr auto webflowAuthPrefix = "webflow_";

constexpr auto networkProxyPasswordKeychainKeySuffixC = "_proxy_password";

constexpr auto legacyRelativeConfigLocationC = "/ownCloud/owncloud.cfg";
constexpr auto legacyCfgFileNameC = "owncloud.cfg";

constexpr auto unbrandedRelativeConfigLocationC = "/Nextcloud/nextcloud.cfg";
constexpr auto unbrandedCfgFileNameC = "nextcloud.cfg";

// The maximum versions that this client can read
constexpr auto maxAccountsVersion = 13;
Expand All @@ -85,7 +81,6 @@ constexpr auto serverDesktopEnterpriseUpdateChannelC = "desktopEnterpriseChannel
constexpr auto generalC = "General";
}


namespace OCC {

Q_LOGGING_CATEGORY(lcAccountManager, "nextcloud.gui.account.manager", QtInfoMsg)
Expand Down Expand Up @@ -195,110 +190,67 @@ bool AccountManager::restoreFromLegacySettings()
{
qCInfo(lcAccountManager) << "Migrate: restoreFromLegacySettings, checking settings group"
<< Theme::instance()->appName();

// try to open the correctly themed settings
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());

auto wasLegacyImportDialogDisplayed = false;
const auto displayLegacyImportDialog = Theme::instance()->displayLegacyImportDialog();
QStringList selectedAccountIds;

// if the settings file could not be opened, the childKeys list is empty
// then try to load settings from a very old place
if (settings->childKeys().isEmpty()) {
// Legacy settings used QDesktopServices to get the location for the config folder in 2.4 and before
const auto legacy2_4CfgSettingsLocation = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data"));
const auto legacy2_4CfgFileParentFolder = legacy2_4CfgSettingsLocation.left(legacy2_4CfgSettingsLocation.lastIndexOf('/'));

// 2.5+ (rest of 2.x series)
const auto legacy2_5CfgSettingsLocation = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation);
const auto legacy2_5CfgFileParentFolder = legacy2_5CfgSettingsLocation.left(legacy2_5CfgSettingsLocation.lastIndexOf('/'));

// Now try the locations we use today
const auto fullLegacyCfgFile = QDir::fromNativeSeparators(settings->fileName());
const auto legacyCfgFileParentFolder = fullLegacyCfgFile.left(fullLegacyCfgFile.lastIndexOf('/'));
const auto legacyCfgFileGrandParentFolder = legacyCfgFileParentFolder.left(legacyCfgFileParentFolder.lastIndexOf('/'));

const auto legacyCfgFileNamePath = QString(QStringLiteral("/") + legacyCfgFileNameC);
const auto legacyCfgFileRelativePath = QString(legacyRelativeConfigLocationC);

auto legacyLocations = QVector<QString>{legacy2_4CfgFileParentFolder + legacyCfgFileRelativePath,
legacy2_5CfgFileParentFolder + legacyCfgFileRelativePath,
legacyCfgFileParentFolder + legacyCfgFileNamePath,
legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath};

if (Theme::instance()->isBranded()) {
const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC);
const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC);
legacyLocations.append({legacyCfgFileParentFolder + unbrandedCfgFileNamePath, legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath});
}

for (const auto &configFile : std::as_const(legacyLocations)) {
auto oCSettings = std::make_unique<QSettings>(configFile, QSettings::IniFormat);
if (oCSettings->status() != QSettings::Status::NoError) {
qCInfo(lcAccountManager) << "Error reading legacy configuration file" << oCSettings->status();
break;
}

oCSettings->beginGroup(QLatin1String(accountsC));
const auto childGroups = oCSettings->childGroups();
const auto accountsListSize = childGroups.size();
oCSettings->endGroup(); //accountsC
if (const QFileInfo configFileInfo(configFile);
configFileInfo.exists() && configFileInfo.isReadable()) {

qCInfo(lcAccountManager) << "Migrate: checking old config " << configFile;
if (!forceLegacyImport() && accountsListSize > 0 && displayLegacyImportDialog) {
wasLegacyImportDialogDisplayed = true;
if (accountsListSize == 1) {
const auto importQuestion =
tr("An account was detected from a legacy desktop client.\n"
"Should the account be imported?");
QMessageBox importMessageBox(QMessageBox::Question, tr("Legacy import"), importQuestion);
importMessageBox.addButton(tr("Import"), QMessageBox::AcceptRole);
const auto skipButton = importMessageBox.addButton(tr("Skip"), QMessageBox::DestructiveRole);
importMessageBox.exec();
if (importMessageBox.clickedButton() == skipButton) {
return false;
}
selectedAccountIds = childGroups;
} else {
QVector<LegacyAccountSelectionDialog::AccountItem> accountsToDisplay;
oCSettings->beginGroup(QLatin1String(accountsC));
for (const auto &accId : childGroups) {
oCSettings->beginGroup(accId);
const auto displayName = oCSettings->value(QLatin1String(displayNameC)).toString();
const auto urlStr = oCSettings->value(QLatin1String(urlC)).toString();
oCSettings->endGroup(); //accId
const auto label = QString("%1 - %2").arg(displayName, urlStr);
accountsToDisplay.push_back({accId, label});
}
oCSettings->endGroup(); //accountsC

LegacyAccountSelectionDialog accountSelectionDialog(accountsToDisplay);
if (accountSelectionDialog.exec() != QDialog::Accepted) {
return false;
}
selectedAccountIds = accountSelectionDialog.selectedAccountIds();
if (selectedAccountIds.isEmpty()) {
return false;
}
}
} else {
selectedAccountIds = childGroups;
if (auto legacyData = Migration::legacyData(); legacyData) {

const auto displayLegacyImportDialog = Theme::instance()->displayLegacyImportDialog();

auto oCSettings = std::move(legacyData);

oCSettings->beginGroup(QLatin1String(accountsC));
const auto childGroups = oCSettings->childGroups();
const auto accountsListSize = childGroups.size();
oCSettings->endGroup(); // accountsC

qCInfo(lcAccountManager) << "Migrate: checking old config";
if (!forceLegacyImport() && displayLegacyImportDialog && accountsListSize > 0) {
wasLegacyImportDialogDisplayed = true;
if (childGroups.size() == 1) {
const auto importQuestion =
tr("An account was detected from a legacy desktop client.\n"
"Should the account be imported?");
QMessageBox importMessageBox(QMessageBox::Question, tr("Legacy import"), importQuestion);
importMessageBox.addButton(tr("Import"), QMessageBox::AcceptRole);
const auto skipButton = importMessageBox.addButton(tr("Skip"), QMessageBox::DestructiveRole);
importMessageBox.exec();
if (importMessageBox.clickedButton() == skipButton) {
return false;
}

const auto legacyVersion = oCSettings->value(ConfigFile::clientVersionC, {}).toString();
ConfigFile().setClientPreviousVersionString(legacyVersion);
qCInfo(lcAccountManager) << "Migrating from" << legacyVersion;
qCInfo(lcAccountManager) << "Copy settings" << oCSettings->allKeys().join(", ");
settings = std::move(oCSettings);
ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath());
break;
selectedAccountIds = childGroups;
} else {
qCInfo(lcAccountManager) << "Migrate: could not read old config " << configFile;
QVector<LegacyAccountSelectionDialog::AccountItem> accountsToDisplay;
oCSettings->beginGroup(QLatin1String(accountsC));
for (const auto &accId : childGroups) {
oCSettings->beginGroup(accId);
const auto displayName = oCSettings->value(QLatin1String(displayNameC)).toString();
const auto urlStr = oCSettings->value(QLatin1String(urlC)).toString();
oCSettings->endGroup(); // accId
const auto label = QString("%1 - %2").arg(displayName, urlStr);
accountsToDisplay.push_back({accId, label});
}
oCSettings->endGroup(); // accountsC

LegacyAccountSelectionDialog accountSelectionDialog(accountsToDisplay);
if (accountSelectionDialog.exec() != QDialog::Accepted) {
return false;
}
selectedAccountIds = accountSelectionDialog.selectedAccountIds();
if (selectedAccountIds.isEmpty()) {
return false;
}
}
} else {
selectedAccountIds = childGroups;
}

const QFileInfo legacyConfigInfo(oCSettings->fileName());
Migration::setDiscoveredLegacyConfigPath(legacyConfigInfo.canonicalPath());
ConfigFile().setClientPreviousVersionString(oCSettings->value(ConfigFile::clientVersionC).toString());

settings = std::move(oCSettings);
}

ConfigFile configFile;
Expand Down Expand Up @@ -344,7 +296,7 @@ bool AccountManager::restoreFromLegacySettings()
configFile.setDownloadLimit(settings->value(ConfigFile::downloadLimitC, configFile.downloadLimit()).toInt());

// Try to load the single account.
configFile.setMigrationPhase(ConfigFile::MigrationPhase::SetupUsers);
Migration::setPhase(Migration::Phase::SetupUsers);
if (!settings->childKeys().isEmpty()) {
settings->beginGroup(accountsC);
const auto childGroups = selectedAccountIds.isEmpty() ? settings->childGroups() : selectedAccountIds;
Expand Down Expand Up @@ -545,7 +497,7 @@ void AccountManager::migrateNetworkSettings(const AccountPtr &account, const QSe
// Override user settings with global (QNetworkProxy::DefaultProxy) settings
// if user is set to use global settings
ConfigFile configFile;
if (accountProxyType == QNetworkProxy::DefaultProxy && configFile.isMigrationInProgress()) {
if (accountProxyType == QNetworkProxy::DefaultProxy && Migration::isInProgress()) {
accountProxyType = static_cast<QNetworkProxy::ProxyType>(configFile.proxyType());
accountProxyHost = configFile.proxyHostName();
accountProxyPort = configFile.proxyPort();
Expand Down Expand Up @@ -694,7 +646,7 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)

ConfigFile configFile;
const auto proxyPasswordKey = QString(acc->userIdAtHostWithPort() + networkProxyPasswordKeychainKeySuffixC);
const auto appName = configFile.isUnbrandedToBrandedMigrationInProgress() ? ConfigFile::unbrandedAppName
const auto appName = Migration::isUnbrandedToBrandedMigration() ? ConfigFile::unbrandedAppName
: Theme::instance()->appName();
const auto job = new QKeychain::ReadPasswordJob(appName, this);
job->setKey(proxyPasswordKey);
Expand Down
10 changes: 5 additions & 5 deletions src/gui/accountstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "ocsuserstatusconnector.h"
#include "pushnotifications.h"
#include "networkjobs.h"
#include "settings/migration.h"

#include <QSettings>
#include <QTimer>
Expand Down Expand Up @@ -301,9 +302,9 @@ void AccountState::checkConnectivity()
if (!account()->credentials()->wasFetched()) {
_waitingForNewCredentials = true;
ConfigFile configFile;
const auto shouldTryUnbrandedToBrandedMigration = configFile.shouldTryUnbrandedToBrandedMigration();
const auto shouldTryUnbrandedToBrandedMigration = Migration::shouldTryUnbrandedToBrandedMigration();
qCDebug(lcAccountState) << "shouldTryUnbrandedToBrandedMigration?" << shouldTryUnbrandedToBrandedMigration;
qCDebug(lcAccountState) << "migrationPhase?" << configFile.migrationPhase();
qCDebug(lcAccountState) << "migration Phase?" << Migration::phase();
const auto appName = shouldTryUnbrandedToBrandedMigration ? configFile.unbrandedAppName : "";
account()->credentials()->fetchFromKeychain(appName);
return;
Expand Down Expand Up @@ -498,9 +499,8 @@ void AccountState::slotCredentialsFetched(AbstractCredentials *)
qCInfo(lcAccountState) << "Fetched credentials for" << _account->url().toString()
<< "attempting to connect";
_waitingForNewCredentials = false;
ConfigFile configFile;
if (configFile.isMigrationInProgress()) {
configFile.setMigrationPhase(ConfigFile::MigrationPhase::Done);
if (Migration::isInProgress()) {
Migration::setPhase(Migration::Phase::Done);
}
checkConnectivity();
}
Expand Down
Loading