From 1caae7e87d2bb6562db0d6456cf792e81ad416f0 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Mon, 14 Apr 2025 17:56:01 +0200 Subject: [PATCH 01/10] refactor(migration): move logic from Application to related classes. - First look for legacy locations and then try to restore general, proxy and accounts settings. - Functions handling account migration go into AccountManager and functions handling legacy config check go in ConfigFile. Signed-off-by: Camila Ayres --- src/gui/accountmanager.cpp | 264 +++++++++++++++++++------------------ src/gui/accountmanager.h | 8 +- src/gui/application.cpp | 184 ++------------------------ src/gui/application.h | 4 - src/libsync/configfile.cpp | 120 +++++++++++++++++ src/libsync/configfile.h | 9 ++ 6 files changed, 280 insertions(+), 309 deletions(-) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 12c353da23be4..d6b4177f266a6 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -63,12 +63,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; constexpr auto maxAccountVersion = 13; @@ -89,12 +83,15 @@ AccountManager *AccountManager::instance() return &instance; } -AccountManager::AccountsRestoreResult AccountManager::restore(const bool alsoRestoreLegacySettings) +AccountManager::AccountsRestoreResult AccountManager::restore(const QString &legacyConfigFile, const bool alsoRestoreLegacySettings) { QStringList skipSettingsKeys; backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys); - const auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); + const auto isLegacyMigration = !legacyConfigFile.isEmpty(); + auto settings = isLegacyMigration ? std::make_unique(legacyConfigFile, QSettings::IniFormat) + : ConfigFile::settingsWithGroup(QLatin1String(accountsC)); + if (settings->status() != QSettings::NoError || !settings->isWritable()) { qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName() << settings->status(); @@ -107,13 +104,23 @@ AccountManager::AccountsRestoreResult AccountManager::restore(const bool alsoRes return AccountsRestoreSuccessWithSkipped; } - // If there are no accounts, check the old format. - if (settings->childGroups().isEmpty() && !settings->contains(QLatin1String(versionC)) && alsoRestoreLegacySettings) { - restoreFromLegacySettings(); - return AccountsRestoreSuccessFromLegacyVersion; + qCWarning(lcAccountManager) << "Restoring settings" << settings->allKeys().join(", ") << "from" << (isLegacyMigration ? legacyConfigFile + : ConfigFile().configFile()); + // migrate general and proxy settings + if (isLegacyMigration && alsoRestoreLegacySettings) { + ConfigFile configFile; + configFile.setVfsEnabled(settings->value(configFile.isVfsEnabledC).toBool()); + configFile.setLaunchOnSystemStartup(settings->value(configFile.launchOnSystemStartupC).toBool()); + configFile.setOptionalServerNotifications(settings->value(configFile.optionalServerNotificationsC).toBool()); + configFile.setPromptDeleteFiles(settings->value(configFile.promptDeleteC).toBool()); + configFile.setShowCallNotifications(settings->value(configFile.showCallNotificationsC).toBool()); + configFile.setShowChatNotifications(settings->value(configFile.showChatNotificationsC).toBool()); + configFile.setShowInExplorerNavigationPane(settings->value(configFile.showInExplorerNavigationPaneC).toBool()); + ClientProxy().saveProxyConfigurationFromSettings(*settings); } - auto result = AccountsRestoreSuccess; + auto result = isLegacyMigration ? AccountsRestoreSuccess : AccountsRestoreSuccessFromLegacyVersion; + settings->beginGroup(accountsC); const auto settingsChildGroups = settings->childGroups(); for (const auto &accountId : settingsChildGroups) { settings->beginGroup(accountId); @@ -135,6 +142,7 @@ AccountManager::AccountsRestoreResult AccountManager::restore(const bool alsoRes } settings->endGroup(); } + settings->endGroup(); return result; } @@ -164,124 +172,6 @@ void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStr } } -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(); - - // 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{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 : legacyLocations) { - auto oCSettings = std::make_unique(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 accountsListSize = oCSettings->childGroups().size(); - oCSettings->endGroup(); - if (const QFileInfo configFileInfo(configFile); - configFileInfo.exists() && configFileInfo.isReadable()) { - qCInfo(lcAccountManager) << "Migrate: checking old config " << configFile; - if (!forceLegacyImport() && accountsListSize > 0 && displayLegacyImportDialog) { - wasLegacyImportDialogDisplayed = true; - const auto importQuestion = accountsListSize > 1 - ? tr("%1 accounts were detected from a legacy desktop client.\n" - "Should the accounts be imported?").arg(QString::number(accountsListSize)) - : tr("1 account was detected from a legacy desktop client.\n" - "Should the account be imported?"); - const auto importMessageBox = new QMessageBox(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; - } - } - - qCInfo(lcAccountManager) << "Copy settings" << oCSettings->allKeys().join(", "); - settings = std::move(oCSettings); - - ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); - break; - } else { - qCInfo(lcAccountManager) << "Migrate: could not read old config " << configFile; - } - } - } - - ConfigFile configFile; - configFile.setVfsEnabled(settings->value(configFile.isVfsEnabledC).toBool()); - configFile.setLaunchOnSystemStartup(settings->value(configFile.launchOnSystemStartupC).toBool()); - configFile.setOptionalServerNotifications(settings->value(configFile.optionalServerNotificationsC).toBool()); - configFile.setPromptDeleteFiles(settings->value(configFile.promptDeleteC).toBool()); - configFile.setShowCallNotifications(settings->value(configFile.showCallNotificationsC).toBool()); - configFile.setShowChatNotifications(settings->value(configFile.showChatNotificationsC).toBool()); - configFile.setShowInExplorerNavigationPane(settings->value(configFile.showInExplorerNavigationPaneC).toBool()); - ClientProxy().saveProxyConfigurationFromSettings(*settings); - configFile.setUseUploadLimit(settings->value(configFile.useUploadLimitC).toInt()); - configFile.setUploadLimit(settings->value(configFile.uploadLimitC).toInt()); - configFile.setUseDownloadLimit(settings->value(configFile.useDownloadLimitC).toInt()); - configFile.setDownloadLimit(settings->value(configFile.downloadLimitC).toInt()); - - // Try to load the single account. - if (!settings->childKeys().isEmpty()) { - settings->beginGroup(accountsC); - const auto childGroups = settings->childGroups(); - for (const auto &accountId : childGroups) { - settings->beginGroup(accountId); - if (const auto acc = loadAccountHelper(*settings)) { - addAccount(acc); - } - settings->endGroup(); - } - return true; - } - - if (wasLegacyImportDialogDisplayed) { - QMessageBox::information(nullptr, - tr("Legacy import"), - tr("Could not import accounts from legacy client configuration.")); - } - - return false; -} - void AccountManager::save(bool saveCredentials) { const auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); @@ -713,4 +603,116 @@ void AccountManager::setForceLegacyImport(const bool forceLegacyImport) _forceLegacyImport = forceLegacyImport; Q_EMIT forceLegacyImportChanged(); } + +void AccountManager::setupAccountsAndFolders() +{ + const auto accountsRestoreResult = restoreLegacyAccount(); + + const auto foldersListSize = FolderMan::instance()->setupFolders(); + + const auto prettyNamesList = [](const QList &accounts) { + QStringList list; + for (const auto &account : accounts) { + list << account->account()->prettyName().prepend("- "); + } + return list.join("\n"); + }; + + if (const auto accounts = AccountManager::instance()->accounts(); + accountsRestoreResult == AccountManager::AccountsRestoreSuccessFromLegacyVersion + && !accounts.isEmpty()) { + + const auto accountsListSize = accounts.size(); + if (Theme::instance()->displayLegacyImportDialog()) { + const auto accountsRestoreMessage = accountsListSize > 1 + ? tr("%1 accounts", "number of accounts imported").arg(QString::number(accountsListSize)) + : tr("1 account"); + const auto foldersRestoreMessage = foldersListSize > 1 + ? tr("%1 folders", "number of folders imported").arg(QString::number(foldersListSize)) + : tr("1 folder"); + const auto messageBox = new QMessageBox(QMessageBox::Information, + tr("Legacy import"), + tr("Imported %1 and %2 from a legacy desktop client.\n%3", + "number of accounts and folders imported. list of users.") + .arg(accountsRestoreMessage, + foldersRestoreMessage, + prettyNamesList(accounts)) + ); + messageBox->setWindowModality(Qt::NonModal); + messageBox->open(); + } + + qCWarning(lcAccountManager) << "Migration result AccountManager::AccountsRestoreResult:" << accountsRestoreResult; + qCWarning(lcAccountManager) << "Folders migrated: " << foldersListSize; + qCWarning(lcAccountManager) << accountsListSize << "account(s) were migrated:" << prettyNamesList(accounts); + + } else { + qCWarning(lcAccountManager) << "Migration result AccountManager::AccountsRestoreResult: " << accountsRestoreResult; + qCWarning(lcAccountManager) << "Folders migrated: " << foldersListSize; + qCWarning(lcAccountManager) << "No accounts were migrated, prompting user to set up accounts and folders from scratch."; + } +} + +AccountManager::AccountsRestoreResult AccountManager::restoreLegacyAccount() +{ + ConfigFile configFile; + const auto tryMigrate = configFile.overrideServerUrl().isEmpty(); + + auto accountsRestoreResult = AccountManager::AccountsRestoreFailure; + const auto legacyConfigFile = ConfigFile().findLegacyConfigFile(); + if (legacyConfigFile.isEmpty()) { + return accountsRestoreResult; + } + + const auto displayLegacyImportDialog = Theme::instance()->displayLegacyImportDialog(); + auto oCSettings = std::make_unique(legacyConfigFile, QSettings::IniFormat); + oCSettings->beginGroup(QLatin1String(accountsC)); + const auto accountsListSize = oCSettings->childGroups().size(); + oCSettings->endGroup(); + + auto showDialogs = false; + if (!forceLegacyImport() && accountsListSize > 0 && displayLegacyImportDialog) { + showDialogs = true; + const auto importQuestion = accountsListSize > 1 + ? tr("%1 accounts were detected from a legacy desktop client.\n" + "Should the accounts be imported?").arg(QString::number(accountsListSize)) + : tr("1 account was detected from a legacy desktop client.\n" + "Should the account be imported?"); + const auto importMessageBox = new QMessageBox(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) { + QMessageBox::information(nullptr, + tr("Legacy import"), + tr("Could not import accounts from legacy client configuration.")); + return accountsRestoreResult; + } + + if (accountsRestoreResult = restore(legacyConfigFile, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure) { + // If there is an error reading the account settings, try again + // after a couple of seconds, if that fails, give up. + // (non-existence is not an error) + Utility::sleep(5); + if (accountsRestoreResult = AccountManager::instance()->restore(legacyConfigFile, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure + && showDialogs) { + qCCritical(lcAccountManager) << "Could not read the account settings, quitting"; + QMessageBox::critical( + nullptr, + tr("Error accessing the configuration file"), + tr("There was an error while accessing the configuration " + "file at %1. Please make sure the file can be accessed by your system account.") + .arg(ConfigFile().configFile()), + QMessageBox::Ok + ); + QTimer::singleShot(0, qApp, &QCoreApplication::quit); + } + } + } + + return accountsRestoreResult; +} + } diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index 9c965e497ad55..bb78f82610ef4 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -9,6 +9,8 @@ #include "account.h" #include "accountstate.h" +class QSettings; + namespace OCC { /** @@ -39,7 +41,7 @@ class AccountManager : public QObject * Returns false if there was an error reading the settings, * but note that settings not existing is not an error. */ - AccountsRestoreResult restore(const bool alsoRestoreLegacySettings = true); + AccountsRestoreResult restore(const QString &legacyConfigFile, const bool alsoRestoreLegacySettings = true); /** * Add this account in the list of saved accounts. @@ -82,6 +84,8 @@ class AccountManager : public QObject */ static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); + AccountManager::AccountsRestoreResult restoreLegacyAccount(); + void setupAccountsAndFolders(); public slots: /// Saves account data when adding user, when updating e.g. dav user, not including the credentials void saveAccount(const OCC::AccountPtr &newAccountData); @@ -113,8 +117,6 @@ public slots: void saveAccountHelper(const AccountPtr &account, QSettings &settings, bool saveCredentials = true); AccountPtr loadAccountHelper(QSettings &settings); - bool restoreFromLegacySettings(); - [[nodiscard]] bool isAccountIdAvailable(const QString &id) const; [[nodiscard]] QString generateFreeAccountId() const; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 38dac21532aec..63056c05c75f1 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -247,46 +247,19 @@ Application::Application(int &argc, char **argv) setApplicationName(_theme->appName()); setWindowIcon(_theme->applicationIcon()); - if (!ConfigFile().exists()) { - setApplicationName(_theme->appNameGUI()); - QString legacyDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME; - - if (legacyDir.endsWith('/')) { - legacyDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move. - } - setApplicationName(_theme->appName()); - if (QFileInfo(legacyDir).isDir()) { - auto confDir = ConfigFile().configPath(); - - // macOS 10.11.x does not like trailing slash for rename/move. - if (confDir.endsWith('/')) { - confDir.chop(1); - } - - qCInfo(lcApplication) << "Migrating old config from" << legacyDir << "to" << confDir; - - if (!QFile::rename(legacyDir, confDir)) { - qCWarning(lcApplication) << "Failed to move the old config directory to its new location (" << legacyDir << "to" << confDir << ")"; + parseOptions(arguments()); - // Try to move the files one by one - if (QFileInfo(confDir).isDir() || QDir().mkdir(confDir)) { - const QStringList filesList = QDir(legacyDir).entryList(QDir::Files); - qCInfo(lcApplication) << "Will move the individual files" << filesList; - for (const auto &name : filesList) { - if (!QFile::rename(legacyDir + "/" + name, confDir + "/" + name)) { - qCWarning(lcApplication) << "Fallback move of " << name << "also failed"; - } - } - } - } else { -#ifndef Q_OS_WIN - // Create a symbolic link so a downgrade of the client would still find the config. - QFile::link(confDir, legacyDir); -#endif - } + ConfigFile configFile; + if (configFile.exists()) { + if (const auto genericConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME; + configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { + qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << genericConfigLocation << "failed."; } } else { - setupConfigFile(); + if (const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + configFile.setupConfigFolderFromLegacyLocation(appDataLocation)) { + qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << appDataLocation << "failed."; + } } if (_theme->doNotUseProxy()) { @@ -298,7 +271,6 @@ Application::Application(int &argc, char **argv) } } - parseOptions(arguments()); //no need to waste time; if (_helpOnly || _versionOnly) { return; @@ -382,7 +354,9 @@ Application::Application(int &argc, char **argv) connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); // create accounts and folders from a legacy desktop client or from the current config file - setupAccountsAndFolders(); + _folderManager.reset(new FolderMan); + FolderMan::instance()->setSyncEnabled(true); + AccountManager::instance()->setupAccountsAndFolders(); setQuitOnLastWindowClosed(false); @@ -468,138 +442,6 @@ Application::~Application() AccountManager::instance()->shutdown(); } -void Application::setupAccountsAndFolders() -{ - _folderManager.reset(new FolderMan); - FolderMan::instance()->setSyncEnabled(true); - - const auto accountsRestoreResult = restoreLegacyAccount(); - - const auto foldersListSize = FolderMan::instance()->setupFolders(); - - const auto prettyNamesList = [](const QList &accounts) { - QStringList list; - for (const auto &account : accounts) { - list << account->account()->prettyName().prepend("- "); - } - return list.join("\n"); - }; - - if (const auto accounts = AccountManager::instance()->accounts(); - accountsRestoreResult == AccountManager::AccountsRestoreSuccessFromLegacyVersion - && !accounts.isEmpty()) { - - const auto accountsListSize = accounts.size(); - if (Theme::instance()->displayLegacyImportDialog()) { - const auto accountsRestoreMessage = accountsListSize > 1 - ? tr("%1 accounts", "number of accounts imported").arg(QString::number(accountsListSize)) - : tr("1 account"); - const auto foldersRestoreMessage = foldersListSize > 1 - ? tr("%1 folders", "number of folders imported").arg(QString::number(foldersListSize)) - : tr("1 folder"); - const auto messageBox = new QMessageBox(QMessageBox::Information, - tr("Legacy import"), - tr("Imported %1 and %2 from a legacy desktop client.\n%3", - "number of accounts and folders imported. list of users.") - .arg(accountsRestoreMessage, - foldersRestoreMessage, - prettyNamesList(accounts)) - ); - messageBox->setWindowModality(Qt::NonModal); - messageBox->open(); - } - - qCWarning(lcApplication) << "Migration result AccountManager::AccountsRestoreResult:" << accountsRestoreResult; - qCWarning(lcApplication) << "Folders migrated: " << foldersListSize; - qCWarning(lcApplication) << accountsListSize << "account(s) were migrated:" << prettyNamesList(accounts); - - } else { - qCWarning(lcApplication) << "Migration result AccountManager::AccountsRestoreResult: " << accountsRestoreResult; - qCWarning(lcApplication) << "Folders migrated: " << foldersListSize; - qCWarning(lcApplication) << "No accounts were migrated, prompting user to set up accounts and folders from scratch."; - } -} - -void Application::setupConfigFile() -{ - // Migrate from version <= 2.4 - setApplicationName(_theme->appNameGUI()); -#ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 - #define QT_WARNING_DISABLE_DEPRECATED QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") -#endif - QT_WARNING_PUSH - QT_WARNING_DISABLE_DEPRECATED - QT_WARNING_POP - setApplicationName(_theme->appName()); - - auto oldDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - - // macOS 10.11.x does not like trailing slash for rename/move. - if (oldDir.endsWith('/')) { - oldDir.chop(1); - } - - if (!QFileInfo(oldDir).isDir()) { - return; - } - - auto confDir = ConfigFile().configPath(); - - // macOS 10.11.x does not like trailing slash for rename/move. - if (confDir.endsWith('/')) { - confDir.chop(1); - } - - qCInfo(lcApplication) << "Migrating old config from" << oldDir << "to" << confDir; - if (!QFile::rename(oldDir, confDir)) { - qCWarning(lcApplication) << "Failed to move the old config directory to its new location (" << oldDir << "to" << confDir << ")"; - - // Try to move the files one by one - if (QFileInfo(confDir).isDir() || QDir().mkdir(confDir)) { - const QStringList filesList = QDir(oldDir).entryList(QDir::Files); - qCInfo(lcApplication) << "Will move the individual files" << filesList; - for (const auto &name : filesList) { - if (!QFile::rename(oldDir + "/" + name, confDir + "/" + name)) { - qCWarning(lcApplication) << "Fallback move of " << name << "also failed"; - } - } - } - } else { -#ifndef Q_OS_WIN - // Create a symbolic link so a downgrade of the client would still find the config. - QFile::link(confDir, oldDir); -#endif - } -} - -AccountManager::AccountsRestoreResult Application::restoreLegacyAccount() -{ - ConfigFile cfg; - const auto tryMigrate = cfg.overrideServerUrl().isEmpty(); - auto accountsRestoreResult = AccountManager::AccountsRestoreFailure; - if (accountsRestoreResult = AccountManager::instance()->restore(tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure) { - // If there is an error reading the account settings, try again - // after a couple of seconds, if that fails, give up. - // (non-existence is not an error) - Utility::sleep(5); - if (accountsRestoreResult = AccountManager::instance()->restore(tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure) { - qCCritical(lcApplication) << "Could not read the account settings, quitting"; - QMessageBox::critical( - nullptr, - tr("Error accessing the configuration file"), - tr("There was an error while accessing the configuration " - "file at %1. Please make sure the file can be accessed by your system account.") - .arg(ConfigFile().configFile()), - QMessageBox::Ok - ); - QTimer::singleShot(0, qApp, &QCoreApplication::quit); - } - } - return accountsRestoreResult; -} - void Application::slotAccountStateRemoved(AccountState *accountState) { if (_gui) { diff --git a/src/gui/application.h b/src/gui/application.h index caba9df0e893f..92a4c9e6462cb 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -100,10 +100,6 @@ protected slots: void handleEditLocallyFromOptions(); - AccountManager::AccountsRestoreResult restoreLegacyAccount(); - void setupConfigFile(); - void setupAccountsAndFolders(); - /** * Maybe a newer version of the client was used with this config file: * if so, backup, confirm with user and remove the config that can't be read. diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index a1eed97c33853..866dad4b5f261 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -97,6 +97,12 @@ static const QString defaultEnterpriseChannel = "enterprise"; static constexpr char languageC[] = "language"; static constexpr int deleteFilesThresholdDefaultValue = 100; + +constexpr auto accountsC = "Accounts"; +constexpr auto legacyRelativeConfigLocationC = "/ownCloud/owncloud.cfg"; +constexpr auto legacyCfgFileNameC = "owncloud.cfg"; +constexpr auto unbrandedRelativeConfigLocationC = "/copyNextcloud/copyNextcloud.cfg"; +constexpr auto unbrandedCfgFileNameC = "copyNextcloud.cfg"; } namespace OCC { @@ -1294,4 +1300,118 @@ void ConfigFile::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyCo _discoveredLegacyConfigPath = discoveredLegacyConfigPath; } +bool ConfigFile::setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const +{ + // Migrate from version <= 2.4 + qApp->setApplicationName(Theme::instance()->appNameGUI()); +#ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 + #define QT_WARNING_DISABLE_DEPRECATED QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") +#endif +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +QT_WARNING_POP + qApp->setApplicationName(Theme::instance()->appName()); + + auto legacyDir = legacyLocation; + if (legacyDir.endsWith('/')) { + legacyDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move. + } + + if (!QFileInfo(legacyDir).isDir()) { + return false; + } + + auto confDir = ConfigFile().configPath(); + if (confDir.endsWith('/')) { + confDir.chop(1); + } + + qCInfo(lcConfigFile) << "Migrating old config from" << legacyDir << "to" << confDir; + if (!QFile::rename(legacyDir, confDir)) { + qCWarning(lcConfigFile) << "Failed to move the old config directory" << legacyDir << "to new location" << confDir; + if (QFileInfo(confDir).isDir() || QDir().mkdir(confDir)) { + const QStringList filesList = QDir(legacyDir).entryList(QDir::Files); + qCInfo(lcConfigFile) << "Will move the individual files:" << filesList; + auto setupCompleted = false; + for (const auto &name : filesList) { + if (!QFile::rename(legacyDir + "/" + name, confDir + "/" + name)) { + qCDebug(lcConfigFile) << "Fallback move of " << name << "also failed"; + continue; + } + setupCompleted = true; + qCInfo(lcConfigFile) << "Move of " << name << "succeeded."; + } + return setupCompleted; + } + } else { +#ifndef Q_OS_WIN + // Create a symbolic link so a downgrade of the client would still find the config. + return QFile::link(confDir, legacyDir); +#endif + } + + return false; +} + +QString ConfigFile::findLegacyConfigFile() const +{ + qCInfo(lcConfigFile) << "Migrate: restoreFromLegacySettings, checking settings group" + << Theme::instance()->appName(); + + // try to open the correctly themed settings + auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); + + + QString validLegacyConfigFile; + // 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{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 : legacyLocations) { + auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); + if (oCSettings->status() != QSettings::Status::NoError) { + qCInfo(lcConfigFile) << "Error reading legacy configuration file" << oCSettings->status(); + break; + } + + if (const QFileInfo configFileInfo(configFile); + configFileInfo.exists() && configFileInfo.isReadable()) { + qCInfo(lcConfigFile) << "Migrate: checking old config " << configFile; + validLegacyConfigFile = configFile; + ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + break; + } else { + qCInfo(lcConfigFile()) << "Migrate: could not read old config " << configFile; + } + } + } + + return validLegacyConfigFile; +} } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 28fe084032cc9..6b63cc778a59a 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -258,6 +258,15 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static constexpr char uploadLimitC[] = "BWLimit/uploadLimit"; static constexpr char downloadLimitC[] = "BWLimit/downloadLimit"; + bool setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const; + /** + * Looks for config files with different names from older client versions + * in different locations + * + * Returns the found config file path found. + */ + [[nodiscard]] QString findLegacyConfigFile() const; + protected: [[nodiscard]] QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const; void storeData(const QString &group, const QString &key, const QVariant &value); From 03008a3650202f7445387c37e6e296ffb687def2 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Fri, 2 May 2025 21:20:37 +0200 Subject: [PATCH 02/10] fix(migration): run client with --conf-dir after upgrade. The client was failing to pick up the config file. Signed-off-by: Camila Ayres --- src/gui/accountmanager.cpp | 69 +++++++++++++++++++++----------------- src/gui/accountmanager.h | 2 +- src/gui/application.cpp | 22 ++++++++---- src/libsync/configfile.cpp | 23 ++++++++++--- src/libsync/configfile.h | 5 ++- 5 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index d6b4177f266a6..ec3b074b33f86 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -606,7 +606,7 @@ void AccountManager::setForceLegacyImport(const bool forceLegacyImport) void AccountManager::setupAccountsAndFolders() { - const auto accountsRestoreResult = restoreLegacyAccount(); + const auto accountsRestoreResult = restoreExistingAccounts(); const auto foldersListSize = FolderMan::instance()->setupFolders(); @@ -653,25 +653,32 @@ void AccountManager::setupAccountsAndFolders() } } -AccountManager::AccountsRestoreResult AccountManager::restoreLegacyAccount() +AccountManager::AccountsRestoreResult AccountManager::restoreExistingAccounts() { ConfigFile configFile; const auto tryMigrate = configFile.overrideServerUrl().isEmpty(); - auto accountsRestoreResult = AccountManager::AccountsRestoreFailure; - const auto legacyConfigFile = ConfigFile().findLegacyConfigFile(); - if (legacyConfigFile.isEmpty()) { - return accountsRestoreResult; - } + auto isALegacyClientRestore = false; + const auto whichConfigFileToRestore = [&](){ + const auto legacyConfigFile = configFile.discoveredLegacyConfigFile(); + if (legacyConfigFile.isEmpty()) { + return configFile.configFile(); + } + + isALegacyClientRestore = true; + return legacyConfigFile; + }; - const auto displayLegacyImportDialog = Theme::instance()->displayLegacyImportDialog(); - auto oCSettings = std::make_unique(legacyConfigFile, QSettings::IniFormat); + auto accountsRestoreResult = AccountManager::AccountsRestoreFailure; + const auto configFileToRestore = whichConfigFileToRestore(); + auto oCSettings = std::make_unique(configFileToRestore, QSettings::IniFormat); oCSettings->beginGroup(QLatin1String(accountsC)); const auto accountsListSize = oCSettings->childGroups().size(); oCSettings->endGroup(); - auto showDialogs = false; - if (!forceLegacyImport() && accountsListSize > 0 && displayLegacyImportDialog) { + if (!forceLegacyImport() && accountsListSize > 0 + && Theme::instance()->displayLegacyImportDialog() + && isALegacyClientRestore) { showDialogs = true; const auto importQuestion = accountsListSize > 1 ? tr("%1 accounts were detected from a legacy desktop client.\n" @@ -688,27 +695,27 @@ AccountManager::AccountsRestoreResult AccountManager::restoreLegacyAccount() tr("Could not import accounts from legacy client configuration.")); return accountsRestoreResult; } + } - if (accountsRestoreResult = restore(legacyConfigFile, tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure) { - // If there is an error reading the account settings, try again - // after a couple of seconds, if that fails, give up. - // (non-existence is not an error) - Utility::sleep(5); - if (accountsRestoreResult = AccountManager::instance()->restore(legacyConfigFile, tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure - && showDialogs) { - qCCritical(lcAccountManager) << "Could not read the account settings, quitting"; - QMessageBox::critical( - nullptr, - tr("Error accessing the configuration file"), - tr("There was an error while accessing the configuration " - "file at %1. Please make sure the file can be accessed by your system account.") - .arg(ConfigFile().configFile()), - QMessageBox::Ok - ); - QTimer::singleShot(0, qApp, &QCoreApplication::quit); - } + if (accountsRestoreResult = restore(configFileToRestore, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure) { + // If there is an error reading the account settings, try again + // after a couple of seconds, if that fails, give up. + // (non-existence is not an error) + Utility::sleep(5); + if (accountsRestoreResult = AccountManager::instance()->restore(configFileToRestore, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure + && showDialogs) { + qCCritical(lcAccountManager) << "Could not read the account settings, quitting"; + QMessageBox::critical( + nullptr, + tr("Error accessing the configuration file"), + tr("There was an error while accessing the configuration " + "file at %1. Please make sure the file can be accessed by your system account.") + .arg(ConfigFile().configFile()), + QMessageBox::Ok + ); + QTimer::singleShot(0, qApp, &QCoreApplication::quit); } } diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index bb78f82610ef4..f781590058bee 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -84,7 +84,7 @@ class AccountManager : public QObject */ static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); - AccountManager::AccountsRestoreResult restoreLegacyAccount(); + AccountManager::AccountsRestoreResult restoreExistingAccounts(); void setupAccountsAndFolders(); public slots: /// Saves account data when adding user, when updating e.g. dav user, not including the credentials diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 63056c05c75f1..23629bb329cb4 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -250,18 +250,24 @@ Application::Application(int &argc, char **argv) parseOptions(arguments()); ConfigFile configFile; - if (configFile.exists()) { - if (const auto genericConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME; - configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { - qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << genericConfigLocation << "failed."; - } + + // First check if there is an existing config from a previous version + if (configFile.exists() && configVersionMigration()) { + qCWarning(lcApplication) << "Config version migration was not possible."; } else { + // check legacy location for existing config files + // if (const auto genericConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME; + // !configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { + // qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << genericConfigLocation << "failed."; + // } else + if (const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - configFile.setupConfigFolderFromLegacyLocation(appDataLocation)) { + !configFile.setupConfigFolderFromLegacyLocation(appDataLocation)) { qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << appDataLocation << "failed."; } } +<<<<<<< HEAD if (_theme->doNotUseProxy()) { ConfigFile().setProxyType(QNetworkProxy::NoProxy); for (const auto &accountState : AccountManager::instance()->accounts()) { @@ -271,6 +277,8 @@ Application::Application(int &argc, char **argv) } } +======= +>>>>>>> 94a1ec663e (fix(migration): run client with --conf-dir after upgrade.) //no need to waste time; if (_helpOnly || _versionOnly) { return; @@ -353,7 +361,7 @@ Application::Application(int &argc, char **argv) connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); - // create accounts and folders from a legacy desktop client or from the current config file + // create accounts and folders from a legacy desktop client or for a new config file _folderManager.reset(new FolderMan); FolderMan::instance()->setSyncEnabled(true); AccountManager::instance()->setupAccountsAndFolders(); diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 866dad4b5f261..c0012e2884dcc 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -113,6 +113,7 @@ Q_LOGGING_CATEGORY(lcConfigFile, "nextcloud.sync.configfile", QtInfoMsg) QString ConfigFile::_confDir = {}; QString ConfigFile::_discoveredLegacyConfigPath = {}; +QString ConfigFile::_discoveredLegacyConfigFile = {}; static chrono::milliseconds millisecondsValue(const QSettings &setting, const char *key, chrono::milliseconds defaultValue) @@ -1300,6 +1301,20 @@ void ConfigFile::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyCo _discoveredLegacyConfigPath = discoveredLegacyConfigPath; } +QString ConfigFile::discoveredLegacyConfigFile() +{ + return _discoveredLegacyConfigFile; +} + +void ConfigFile::setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile) +{ + if (_discoveredLegacyConfigFile == discoveredLegacyConfigFile) { + return; + } + + _discoveredLegacyConfigFile = discoveredLegacyConfigFile; +} + bool ConfigFile::setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const { // Migrate from version <= 2.4 @@ -1353,15 +1368,14 @@ QT_WARNING_POP return false; } -QString ConfigFile::findLegacyConfigFile() const +QString ConfigFile::findLegacyClientConfigFile() const { qCInfo(lcConfigFile) << "Migrate: restoreFromLegacySettings, checking settings group" << Theme::instance()->appName(); - // try to open the correctly themed settings + // try to open the correctly themed settings auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); - QString validLegacyConfigFile; // if the settings file could not be opened, the childKeys list is empty // then try to load settings from a very old place @@ -1404,7 +1418,8 @@ QString ConfigFile::findLegacyConfigFile() const configFileInfo.exists() && configFileInfo.isReadable()) { qCInfo(lcConfigFile) << "Migrate: checking old config " << configFile; validLegacyConfigFile = configFile; - ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + setDiscoveredLegacyConfigFile(configFileInfo.filePath()); + setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); break; } else { qCInfo(lcConfigFile()) << "Migrate: could not read old config " << configFile; diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 6b63cc778a59a..af6a2550ab35a 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -244,6 +244,8 @@ class OWNCLOUDSYNC_EXPORT ConfigFile /// Set during first time migration of legacy accounts in AccountManager [[nodiscard]] static QString discoveredLegacyConfigPath(); static void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath); + [[nodiscard]] static QString discoveredLegacyConfigFile(); + static void setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile); static constexpr char isVfsEnabledC[] = "isVfsEnabled"; static constexpr char launchOnSystemStartupC[] = "launchOnSystemStartup"; @@ -265,7 +267,7 @@ class OWNCLOUDSYNC_EXPORT ConfigFile * * Returns the found config file path found. */ - [[nodiscard]] QString findLegacyConfigFile() const; + [[nodiscard]] QString findLegacyClientConfigFile() const; protected: [[nodiscard]] QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const; @@ -285,6 +287,7 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static QString _confDir; static QString _discoveredLegacyConfigPath; + static QString _discoveredLegacyConfigFile; }; } #endif // CONFIGFILE_H From ebf731d9108d04f7411a34355d631903868a95a0 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Fri, 2 May 2025 22:13:18 +0200 Subject: [PATCH 03/10] refactor(migration): move logic from Application to related classes. - Separate logic from widgets. - Functions handling account migration go into AccountManager and functions handling legacy config check go in ConfigFile. Signed-off-by: Camila Ayres --- src/gui/accountmanager.cpp | 127 ++++++------------ src/gui/accountmanager.h | 5 +- src/gui/application.cpp | 259 +++++++++++++++++++------------------ src/gui/application.h | 8 +- src/gui/folderman.cpp | 10 +- src/libsync/configfile.cpp | 64 +++++++-- src/libsync/configfile.h | 16 ++- 7 files changed, 256 insertions(+), 233 deletions(-) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index ec3b074b33f86..36b989872cf86 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -525,9 +525,9 @@ AccountPtr AccountManager::createAccount() const auto acc = Account::create(); acc->setSslErrorHandler(new SslDialogErrorHandler); connect(acc.data(), &Account::proxyAuthenticationRequired, - ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); + ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); connect(acc.data(), &Account::lockFileError, - Systray::instance(), &Systray::showErrorMessageDialog); + Systray::instance(), &Systray::showErrorMessageDialog); return acc; } @@ -604,82 +604,57 @@ void AccountManager::setForceLegacyImport(const bool forceLegacyImport) Q_EMIT forceLegacyImportChanged(); } -void AccountManager::setupAccountsAndFolders() -{ - const auto accountsRestoreResult = restoreExistingAccounts(); - const auto foldersListSize = FolderMan::instance()->setupFolders(); +int AccountManager::setupAccounts() +{ + if (!confirmRestoreExistingAccounts()) { + return 0; + } - const auto prettyNamesList = [](const QList &accounts) { - QStringList list; - for (const auto &account : accounts) { - list << account->account()->prettyName().prepend("- "); - } - return list.join("\n"); - }; - - if (const auto accounts = AccountManager::instance()->accounts(); - accountsRestoreResult == AccountManager::AccountsRestoreSuccessFromLegacyVersion - && !accounts.isEmpty()) { - - const auto accountsListSize = accounts.size(); - if (Theme::instance()->displayLegacyImportDialog()) { - const auto accountsRestoreMessage = accountsListSize > 1 - ? tr("%1 accounts", "number of accounts imported").arg(QString::number(accountsListSize)) - : tr("1 account"); - const auto foldersRestoreMessage = foldersListSize > 1 - ? tr("%1 folders", "number of folders imported").arg(QString::number(foldersListSize)) - : tr("1 folder"); - const auto messageBox = new QMessageBox(QMessageBox::Information, - tr("Legacy import"), - tr("Imported %1 and %2 from a legacy desktop client.\n%3", - "number of accounts and folders imported. list of users.") - .arg(accountsRestoreMessage, - foldersRestoreMessage, - prettyNamesList(accounts)) - ); - messageBox->setWindowModality(Qt::NonModal); - messageBox->open(); + ConfigFile configFile; + const auto tryMigrate = configFile.overrideServerUrl().isEmpty(); + const auto configFileToRestore = configFile.configFileToRestore(); + auto accountsRestoreResult = AccountManager::AccountsRestoreResult::AccountsRestoreSuccess; + if (accountsRestoreResult = restore(configFileToRestore, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure) { + // If there is an error reading the account settings, try again + // after a couple of seconds, if that fails, give up. + // (non-existence is not an error) + Utility::sleep(5); + if (accountsRestoreResult = restore(configFileToRestore, tryMigrate); + accountsRestoreResult == AccountManager::AccountsRestoreFailure) { + qCCritical(lcAccountManager) << "Could not read the account settings, quitting"; + QMessageBox::critical( + nullptr, + tr("Error accessing the configuration file"), + tr("There was an error while accessing the configuration " + "file at %1. Please make sure the file can be accessed by your system account.") + .arg(ConfigFile().configFile()), + QMessageBox::Ok + ); + QTimer::singleShot(0, qApp, &QCoreApplication::quit); + return 0; } + } - qCWarning(lcAccountManager) << "Migration result AccountManager::AccountsRestoreResult:" << accountsRestoreResult; - qCWarning(lcAccountManager) << "Folders migrated: " << foldersListSize; - qCWarning(lcAccountManager) << accountsListSize << "account(s) were migrated:" << prettyNamesList(accounts); + const auto accountsMigrated = AccountManager::instance()->accounts().size(); + qCWarning(lcAccountManager) << "Migration result AccountManager::AccountsRestoreResult:" << accountsRestoreResult; + qCWarning(lcAccountManager) << "Accounts migrated:" << accountsMigrated; - } else { - qCWarning(lcAccountManager) << "Migration result AccountManager::AccountsRestoreResult: " << accountsRestoreResult; - qCWarning(lcAccountManager) << "Folders migrated: " << foldersListSize; - qCWarning(lcAccountManager) << "No accounts were migrated, prompting user to set up accounts and folders from scratch."; - } + return accountsMigrated; } -AccountManager::AccountsRestoreResult AccountManager::restoreExistingAccounts() +bool AccountManager::confirmRestoreExistingAccounts() const { ConfigFile configFile; - const auto tryMigrate = configFile.overrideServerUrl().isEmpty(); - - auto isALegacyClientRestore = false; - const auto whichConfigFileToRestore = [&](){ - const auto legacyConfigFile = configFile.discoveredLegacyConfigFile(); - if (legacyConfigFile.isEmpty()) { - return configFile.configFile(); - } - - isALegacyClientRestore = true; - return legacyConfigFile; - }; - - auto accountsRestoreResult = AccountManager::AccountsRestoreFailure; - const auto configFileToRestore = whichConfigFileToRestore(); + const auto configFileToRestore = configFile.configFileToRestore(); auto oCSettings = std::make_unique(configFileToRestore, QSettings::IniFormat); oCSettings->beginGroup(QLatin1String(accountsC)); const auto accountsListSize = oCSettings->childGroups().size(); oCSettings->endGroup(); - auto showDialogs = false; if (!forceLegacyImport() && accountsListSize > 0 && Theme::instance()->displayLegacyImportDialog() - && isALegacyClientRestore) { - showDialogs = true; + && !configFile.discoveredLegacyConfigFile().isEmpty()) { const auto importQuestion = accountsListSize > 1 ? tr("%1 accounts were detected from a legacy desktop client.\n" "Should the accounts be imported?").arg(QString::number(accountsListSize)) @@ -693,33 +668,11 @@ AccountManager::AccountsRestoreResult AccountManager::restoreExistingAccounts() QMessageBox::information(nullptr, tr("Legacy import"), tr("Could not import accounts from legacy client configuration.")); - return accountsRestoreResult; - } - } - - if (accountsRestoreResult = restore(configFileToRestore, tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure) { - // If there is an error reading the account settings, try again - // after a couple of seconds, if that fails, give up. - // (non-existence is not an error) - Utility::sleep(5); - if (accountsRestoreResult = AccountManager::instance()->restore(configFileToRestore, tryMigrate); - accountsRestoreResult == AccountManager::AccountsRestoreFailure - && showDialogs) { - qCCritical(lcAccountManager) << "Could not read the account settings, quitting"; - QMessageBox::critical( - nullptr, - tr("Error accessing the configuration file"), - tr("There was an error while accessing the configuration " - "file at %1. Please make sure the file can be accessed by your system account.") - .arg(ConfigFile().configFile()), - QMessageBox::Ok - ); - QTimer::singleShot(0, qApp, &QCoreApplication::quit); + return false; } } - return accountsRestoreResult; + return true; } } diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index f781590058bee..fee75e39b3718 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -84,8 +84,9 @@ class AccountManager : public QObject */ static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); - AccountManager::AccountsRestoreResult restoreExistingAccounts(); - void setupAccountsAndFolders(); + bool confirmRestoreExistingAccounts() const; + int setupAccounts(); + public slots: /// Saves account data when adding user, when updating e.g. dav user, not including the credentials void saveAccount(const OCC::AccountPtr &newAccountData); diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 23629bb329cb4..364cd4f1486b5 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -115,93 +115,7 @@ namespace { // ---------------------------------------------------------------------------------- -bool Application::configVersionMigration() -{ - QStringList deleteKeys, ignoreKeys; - AccountManager::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); - FolderMan::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); - - ConfigFile configFile; - - // Did the client version change? - // (The client version is adjusted further down) - const auto currentVersion = QVersionNumber::fromString(MIRALL_VERSION_STRING); - const auto previousVersion = QVersionNumber::fromString(configFile.clientVersionString()); - const auto versionChanged = previousVersion != currentVersion; - const auto downgrading = previousVersion > currentVersion; - - if (versionChanged) { - qCInfo(lcApplication) << "Version changed. Removing updater settings from config."; - configFile.cleanUpdaterConfiguration(); - } - - if (!versionChanged && !(!deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && versionChanged))) { - return true; - } - - // 'Launch on system startup' defaults to true > 3.11.x - const auto theme = Theme::instance(); - configFile.setLaunchOnSystemStartup(configFile.launchOnSystemStartup()); - Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), configFile.launchOnSystemStartup()); - - // default is now off to displaying dialog warning user of too many files deletion - configFile.setPromptDeleteFiles(false); - - // back up all old config files - QStringList backupFilesList; - QDir configDir(configFile.configPath()); - const auto anyConfigFileNameList = configDir.entryInfoList({"*.cfg"}, QDir::Files); - for (const auto &oldConfig : anyConfigFileNameList) { - const auto oldConfigFileName = oldConfig.fileName(); - const auto oldConfigFilePath = oldConfig.filePath(); - const auto newConfigFileName = configFile.configFile(); - backupFilesList.append(configFile.backup(oldConfigFileName)); - if (oldConfigFilePath != newConfigFileName) { - if (!QFile::rename(oldConfigFilePath, newConfigFileName)) { - qCWarning(lcApplication) << "Failed to rename configuration file from" << oldConfigFilePath << "to" << newConfigFileName; - } - } - } - - // We want to message the user either for destructive changes, - // or if we're ignoring something and the client version changed. - if (configFile.showConfigBackupWarning() && backupFilesList.count() > 0) { - QMessageBox box( - QMessageBox::Warning, - APPLICATION_SHORTNAME, - tr("Some settings were configured in %1 versions of this client and " - "use features that are not available in this version.
" - "
" - "Continuing will mean %2 these settings.
" - "
" - "The current configuration file was already backed up to %3.") - .arg((downgrading ? tr("newer", "newer software version") : tr("older", "older software version")), - deleteKeys.isEmpty()? tr("ignoring") : tr("deleting"), - backupFilesList.join("
"))); - box.addButton(tr("Quit"), QMessageBox::AcceptRole); - auto continueBtn = box.addButton(tr("Continue"), QMessageBox::DestructiveRole); - - box.exec(); - if (box.clickedButton() != continueBtn) { - QTimer::singleShot(0, qApp, &QCoreApplication::quit); - return false; - } - } - - if (!deleteKeys.isEmpty()) { - auto settings = ConfigFile::settingsWithGroup("foo"); - settings->endGroup(); - - // Wipe confusing keys from the future, ignore the others - for (const auto &badKey : std::as_const(deleteKeys)) { - settings->remove(badKey); - qCInfo(lcApplication) << "Migration: removed" << badKey << "key from settings."; - } - } - configFile.setClientVersionString(MIRALL_VERSION_STRING); - return true; -} ownCloudGui *Application::gui() const { @@ -249,25 +163,37 @@ Application::Application(int &argc, char **argv) parseOptions(arguments()); - ConfigFile configFile; + //no need to waste time; + if (_helpOnly || _versionOnly) { + return; + } - // First check if there is an existing config from a previous version - if (configFile.exists() && configVersionMigration()) { - qCWarning(lcApplication) << "Config version migration was not possible."; - } else { - // check legacy location for existing config files - // if (const auto genericConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME; - // !configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { - // qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << genericConfigLocation << "failed."; - // } else - - if (const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - !configFile.setupConfigFolderFromLegacyLocation(appDataLocation)) { - qCWarning(lcApplication) << "Setup of config folder and files from legacy location" << appDataLocation << "failed."; + ConfigFile configFile; + // First check if there is a valid config file + if (const auto configExists = configFile.exists(); configExists && makeConfigSettingsBackwardCompatible()) { + qCWarning(lcApplication) << "Existing config is compatible with current version."; + } else if (!configExists) { + // look for previous used application name for config folder. + if (const auto genericConfigLocation = QString(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME); + configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { + qCWarning(lcApplication) << "Copy of config folder and files from legacy location" << genericConfigLocation << "suceeded."; + + // app data folder + } else if (const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { + qCWarning(lcApplication) << "Copy of config folder and files from legacy location" << appDataLocation << "suceeded."; + } else { + configFile.findLegacyClientConfigFile(); } } -<<<<<<< HEAD + if (_quitInstance) { + QTimer::singleShot(0, qApp, &QApplication::quit); + return; + } + + setupAccountsAndFolders(); + if (_theme->doNotUseProxy()) { ConfigFile().setProxyType(QNetworkProxy::NoProxy); for (const auto &accountState : AccountManager::instance()->accounts()) { @@ -277,18 +203,6 @@ Application::Application(int &argc, char **argv) } } -======= ->>>>>>> 94a1ec663e (fix(migration): run client with --conf-dir after upgrade.) - //no need to waste time; - if (_helpOnly || _versionOnly) { - return; - } - - if (_quitInstance) { - QTimer::singleShot(0, qApp, &QApplication::quit); - return; - } - if (isRunning()) { return; } @@ -296,12 +210,6 @@ Application::Application(int &argc, char **argv) setupLogging(); setupTranslations(); - // try to migrate legacy accounts and folders from a previous client version - // only copy the settings and check what should be skipped - if (!configVersionMigration()) { - qCWarning(lcApplication) << "Config version migration was not possible."; - } - ConfigFile cfg; { auto shouldExit = false; @@ -361,11 +269,6 @@ Application::Application(int &argc, char **argv) connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); - // create accounts and folders from a legacy desktop client or for a new config file - _folderManager.reset(new FolderMan); - FolderMan::instance()->setSyncEnabled(true); - AccountManager::instance()->setupAccountsAndFolders(); - setQuitOnLastWindowClosed(false); // Setting up the gui class will allow tray notifications for the @@ -436,6 +339,114 @@ Application::Application(int &argc, char **argv) #endif } +bool Application::confirmConfigChangesOrQuitApp(bool isDeleteKeysEmpty, const QStringList backupFilesList) +{ + // Message the user about changes + QMessageBox box( + QMessageBox::Warning, + APPLICATION_SHORTNAME, + tr("Some settings were configured in %1 versions of this client and " + "use features that are not available in this version.
" + "
" + "Continuing will mean %2 these settings.
" + "
" + "The current configuration file was already backed up to %3.") + .arg((ConfigFile().isDowngrade() ? tr("newer", "newer software version") : tr("older", "older software version")), + isDeleteKeysEmpty? tr("ignoring") : tr("deleting"), + backupFilesList.join("
"))); + box.addButton(tr("Quit"), QMessageBox::AcceptRole); + auto continueBtn = box.addButton(tr("Continue"), QMessageBox::DestructiveRole); + + box.exec(); + if (box.clickedButton() != continueBtn) { + _quitInstance = true; + return false; + } + + return true; +} + +void Application::setupAccountsAndFolders() +{ + // create accounts and folders from a legacy desktop client or for a new config file + _folderManager.reset(new FolderMan); + FolderMan::instance()->setSyncEnabled(true); + const auto accounts = AccountManager::instance()->setupAccounts(); + const auto folders = FolderMan::instance()->setupFolders(); + if (accounts > 0 && Theme::instance()->displayLegacyImportDialog()) { + const auto prettyNamesList = [](const QList &accounts) { + QStringList list; + for (const auto &account : accounts) { + list << account->account()->prettyName().prepend("- "); + } + return list.join("\n"); + }; + + const auto accountsRestoreMessage = accounts > 1 + ? tr("%1 accounts", "number of accounts imported").arg(QString::number(accounts)) + : tr("1 account"); + const auto foldersRestoreMessage = folders > 1 + ? tr("%1 folders", "number of folders imported").arg(QString::number(folders)) + : tr("1 folder"); + const auto messageBox = new QMessageBox(QMessageBox::Information, + tr("Legacy import"), + tr("Imported %1 and %2 from a legacy desktop client.\n%3", + "number of accounts and folders imported. list of users.") + .arg(accountsRestoreMessage, + foldersRestoreMessage, + prettyNamesList(AccountManager::instance()->accounts())) + ); + messageBox->setWindowModality(Qt::NonModal); + messageBox->open(); + } + + ConfigFile().setClientVersionString(MIRALL_VERSION_STRING); +} + +bool Application::makeConfigSettingsBackwardCompatible() +{ + ConfigFile configFile; + const auto didVersionChanged = configFile.isUpgrade() || configFile.isDowngrade(); + if (!didVersionChanged) { + qCInfo(lcApplication) << "No upgrade or downgrade detected."; + return true; + } + + configFile.cleanUpdaterConfiguration(); + + QStringList deleteKeys, ignoreKeys; + AccountManager::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); + FolderMan::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); + if (!didVersionChanged && !(!deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && didVersionChanged))) { + qCInfo(lcApplication) << "There are no settings to delete or to ignore. No need to change the current config file."; + return true; + } + + const auto isDeleteKeysEmpty = deleteKeys.isEmpty(); + if (const auto backupFilesList = configFile.backupConfigFiles(); + configFile.showConfigBackupWarning() + && backupFilesList.size() > 0 + && !confirmConfigChangesOrQuitApp(isDeleteKeysEmpty, backupFilesList)) { + return false; + } + + if (!isDeleteKeysEmpty) { + auto settings = configFile.settingsWithGroup("foo"); + settings->endGroup(); + + // Wipe confusing keys from the future, ignore the others + for (const auto &badKey : std::as_const(deleteKeys)) { + settings->remove(badKey); + qCDebug(lcApplication) << "Migration: removed" << badKey << "key from settings."; + } + } + + configFile.setClientVersionString(MIRALL_VERSION_STRING); + qCDebug(lcApplication) << "Client version changed to" << configFile.clientVersionString(); + + return true; +} + Application::~Application() { // Make sure all folders are gone, otherwise removing the diff --git a/src/gui/application.h b/src/gui/application.h index 92a4c9e6462cb..30bf96967f08c 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -100,11 +100,9 @@ protected slots: void handleEditLocallyFromOptions(); - /** - * Maybe a newer version of the client was used with this config file: - * if so, backup, confirm with user and remove the config that can't be read. - */ - bool configVersionMigration(); + bool makeConfigSettingsBackwardCompatible(); + bool confirmConfigChangesOrQuitApp(bool isDeleteKeysEmpty, const QStringList backupFilesList); + void setupAccountsAndFolders(); QPointer _gui; diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index c11c4f1f1cc1f..d305343ea02c1 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -170,6 +170,10 @@ void FolderMan::registerFolderWithSocketApi(Folder *folder) int FolderMan::setupFolders() { + if (AccountManager::instance()->accounts().isEmpty()) { + return 0; + } + Utility::registerUriHandlerForLocalEditing(); unloadAndDeleteAllFolders(); @@ -186,7 +190,6 @@ int FolderMan::setupFolders() qCInfo(lcFolderMan) << "Setup folders from settings file"; - // this is done in Application::configVersionMigration QStringList skipSettingsKeys; backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys); const auto accounts = AccountManager::instance()->accounts(); @@ -225,7 +228,10 @@ int FolderMan::setupFolders() folder->processSwitchedToVirtualFiles(); } - return _folderMap.size(); + const auto foldersListSize = _folderMap.size(); + qCWarning(lcFolderMan) << "Folders migrated: " << foldersListSize; + + return foldersListSize; } void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, const QStringList &ignoreKeys, bool backwardsCompatible, bool foldersWithPlaceholders) diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index c0012e2884dcc..24c3de19281c0 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -98,11 +98,10 @@ static constexpr char languageC[] = "language"; static constexpr int deleteFilesThresholdDefaultValue = 100; -constexpr auto accountsC = "Accounts"; constexpr auto legacyRelativeConfigLocationC = "/ownCloud/owncloud.cfg"; constexpr auto legacyCfgFileNameC = "owncloud.cfg"; -constexpr auto unbrandedRelativeConfigLocationC = "/copyNextcloud/copyNextcloud.cfg"; -constexpr auto unbrandedCfgFileNameC = "copyNextcloud.cfg"; +constexpr auto unbrandedRelativeConfigLocationC = "/Nextcloud/nextcloud.cfg"; +constexpr auto unbrandedCfgFileNameC = "nextcloud.cfg"; } namespace OCC { @@ -1368,7 +1367,7 @@ QT_WARNING_POP return false; } -QString ConfigFile::findLegacyClientConfigFile() const +void ConfigFile::findLegacyClientConfigFile() { qCInfo(lcConfigFile) << "Migrate: restoreFromLegacySettings, checking settings group" << Theme::instance()->appName(); @@ -1376,7 +1375,6 @@ QString ConfigFile::findLegacyClientConfigFile() const // try to open the correctly themed settings auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); - QString validLegacyConfigFile; // 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()) { @@ -1384,11 +1382,11 @@ QString ConfigFile::findLegacyClientConfigFile() const 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) + // 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 + // 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('/')); @@ -1416,8 +1414,7 @@ QString ConfigFile::findLegacyClientConfigFile() const if (const QFileInfo configFileInfo(configFile); configFileInfo.exists() && configFileInfo.isReadable()) { - qCInfo(lcConfigFile) << "Migrate: checking old config " << configFile; - validLegacyConfigFile = configFile; + qCInfo(lcConfigFile) << "Migrate: saving old config file" << configFile; setDiscoveredLegacyConfigFile(configFileInfo.filePath()); setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); break; @@ -1426,7 +1423,54 @@ QString ConfigFile::findLegacyClientConfigFile() const } } } +} + +bool ConfigFile::isUpgrade() const +{ + return QVersionNumber::fromString(MIRALL_VERSION_STRING) > QVersionNumber::fromString(clientVersionString()); +} + +bool ConfigFile::isDowngrade() const +{ + return QVersionNumber::fromString(clientVersionString()) > QVersionNumber::fromString(MIRALL_VERSION_STRING); +} + +QString ConfigFile::configFileToRestore() const +{ + const auto legacyConfigFile = discoveredLegacyConfigFile(); + if (legacyConfigFile.isEmpty()) { + return configFile(); + } + + return legacyConfigFile; +} + +QStringList ConfigFile::backupConfigFiles() +{ + // 'Launch on system startup' defaults to true > 3.11.x + const auto theme = Theme::instance(); + setLaunchOnSystemStartup(launchOnSystemStartup()); + Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), launchOnSystemStartup()); + + // default is now off to displaying dialog warning user of too many files deletion + setPromptDeleteFiles(false); + + // back up all old config files + QStringList backupFilesList; + QDir configDir(configPath()); + const auto anyConfigFileNameList = configDir.entryInfoList({"*.cfg"}, QDir::Files); + for (const auto &oldConfig : anyConfigFileNameList) { + const auto oldConfigFileName = oldConfig.fileName(); + const auto oldConfigFilePath = oldConfig.filePath(); + const auto newConfigFileName = configFile(); + backupFilesList.append(backup(oldConfigFileName)); + if (oldConfigFilePath != newConfigFileName) { + if (!QFile::rename(oldConfigFilePath, newConfigFileName)) { + qCWarning(lcConfigFile) << "Failed to rename configuration file from" << oldConfigFilePath << "to" << newConfigFileName; + } + } + } - return validLegacyConfigFile; + return backupFilesList; } } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index af6a2550ab35a..8d74523aeed37 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -254,20 +254,30 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static constexpr char showCallNotificationsC[] = "showCallNotifications"; static constexpr char showChatNotificationsC[] = "showChatNotifications"; static constexpr char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane"; - static constexpr char useUploadLimitC[] = "BWLimit/useUploadLimit"; static constexpr char useDownloadLimitC[] = "BWLimit/useDownloadLimit"; static constexpr char uploadLimitC[] = "BWLimit/uploadLimit"; static constexpr char downloadLimitC[] = "BWLimit/downloadLimit"; - bool setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const; + [[nodiscard]] bool setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const; + /** * Looks for config files with different names from older client versions * in different locations * * Returns the found config file path found. */ - [[nodiscard]] QString findLegacyClientConfigFile() const; + void findLegacyClientConfigFile(); + + /** + * Maybe a newer version of the client was used with this config file: if so, backup. + * Return backup files list. + */ + [[nodiscard]] QStringList backupConfigFiles(); + [[nodiscard]] bool isUpgrade() const; + [[nodiscard]] bool isDowngrade() const; + [[nodiscard]] QString configFileToRestore() const; + [[nodiscard]] QString findLegacyConfigFile() const; protected: [[nodiscard]] QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const; From 45798db8f3f4d7c82bb9917fe1c821776c775c03 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Wed, 30 Apr 2025 22:12:05 +0200 Subject: [PATCH 04/10] test(migration): wip! test each migration scenarion. Signed-off-by: Camila Ayres --- src/gui/folderman.h | 2 + test/CMakeLists.txt | 1 + test/testmigration.cpp | 290 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 test/testmigration.cpp diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 16e33b088089c..ad566a7e9261b 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -26,6 +26,7 @@ class TestFolderStatusModel; class ShareTestHelper; class EndToEndTestHelper; class TestSyncConflictsModel; +class TestMigration; namespace OCC { @@ -413,6 +414,7 @@ private slots: friend class ::ShareTestHelper; friend class ::EndToEndTestHelper; friend class ::TestFolderStatusModel; + friend class ::TestMigration; }; } // namespace OCC diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e4e9027a67277..eae537e8e547b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -140,6 +140,7 @@ nextcloud_add_benchmark(LargeSync) nextcloud_add_test(Account) nextcloud_add_test(FolderMan) nextcloud_add_test(RemoteWipe) +nextcloud_add_test(Migration) configure_file(test_journal.db "${PROJECT_BINARY_DIR}/bin/test_journal.db" COPYONLY) diff --git a/test/testmigration.cpp b/test/testmigration.cpp new file mode 100644 index 0000000000000..ade95db4ea7ec --- /dev/null +++ b/test/testmigration.cpp @@ -0,0 +1,290 @@ +/* + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include + +#include "common/utility.h" +#include "folderman.h" +#include "account.h" +#include "accountstate.h" +#include "accountmanager.h" +#include "configfile.h" +#include "syncenginetestutils.h" +#include "testhelper.h" +#include "version.h" + +using namespace OCC; + +class TestMigration: public QObject +{ + Q_OBJECT + + ConfigFile _configFile; + QTemporaryDir _temporaryDir; + std::unique_ptr _folderMan; + +private: + static constexpr char legacyAppName[] = "ownCloud"; + static constexpr char standardAppName[] = "Nextcloud"; + static constexpr char brandedAppName[] = "Branded"; + static constexpr char ocBrandedAppName[] = "branded"; + static constexpr char legacyAppConfigContent[] = "[General]\n" + "clientVersion=5.3.2.15463\n" + "issuesWidgetFilter=FatalError, BlacklistedError, Excluded, Message, FilenameReserved\n" + "logHttp=false\n" + "optionalDesktopNotifications=true\n" + "\n" + "[Accounts]e\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\davUrl=@Variant(http://oc.de/remote.php/dav/files/admin/)\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\deployed=false\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\displayString=ownCloud\n" + "0\\Folders\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\ignoreHiddenFiles=true\n" + "0\\Folders\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\journalPath=.sync_journal.db\n" + "0\\Folders\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\localPath=/ownCloud/\n" + "0\\Folders\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\paused=false\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\priority=0\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\targetPath=/\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\version=13\n" + "0\\Folders\\2ba4b09a-1223-aaaa-abcd-c2df238816d8\\virtualFilesMode=off\n" + "0\\capabilities=@QVariant()\n" + "0\\dav_user=admin\n" + "0\\default_sync_root=/ownCloud\n" + "0\\display-name=admin\n" + "0\\http_CredentialVersion=1\n" + "0\\http_oauth=false\n" + "0\\http_user=admin\n" + "0\\supportsSpaces=true\n" + "0\\url=http://oc.de/\n" + "0\\user=admin\n" + "0\\userExplicitlySignedOut=false\n" + "0\\uuid=@Variant()\n" + "0\\version=13\n" + "version=13\n" + "\n" + "[Credentials]\n" + "ownCloud_credentials%oc.de%2ba4b09a-1223-aaaa-abcd-c2df238816d8\\http\\password=true"; + +private slots: + void setupStandardConfigFolder() + { + QVERIFY(QDir(_temporaryDir.path()).mkpath(standardAppName)); + const auto standardConfigFolder = QString(_temporaryDir.path() + "/" + standardAppName); + _configFile.setConfDir(standardConfigFolder); + } + + void setupStandarConfig(const QString &version) + { + setupStandardConfigFolder(); + QSettings settings(_configFile.configFile(), QSettings::IniFormat); + _configFile.setClientVersionString(version); + _configFile.setOptionalServerNotifications(true); + _configFile.setShowChatNotifications(true); + _configFile.setShowCallNotifications(true); + _configFile.setShowInExplorerNavigationPane(true); + _configFile.setShowInExplorerNavigationPane(true); + _configFile.setRemotePollInterval(std::chrono::milliseconds(1000)); + _configFile.setAutoUpdateCheck(true, QString()); + _configFile.setUpdateChannel("beta"); + _configFile.setOverrideServerUrl("http://example.de"); + _configFile.setOverrideLocalDir("A"); + _configFile.setVfsEnabled(true); + _configFile.setProxyType(0); + _configFile.setVfsEnabled(true); + _configFile.setUseUploadLimit(0); + _configFile.setUploadLimit(1); + _configFile.setUseDownloadLimit(0); + _configFile.setUseDownloadLimit(1); + _configFile.setNewBigFolderSizeLimit(true, 500); + _configFile.setNotifyExistingFoldersOverLimit(true); + _configFile.setStopSyncingExistingFoldersOverLimit(true); + _configFile.setConfirmExternalStorage(true); + _configFile.setMoveToTrash(true); + _configFile.setForceLoginV2(true); + _configFile.setPromptDeleteFiles(true); + _configFile.setDeleteFilesThreshold(1); + _configFile.setMonoIcons(true); + _configFile.setAutomaticLogDir(true); + _configFile.setLogDir(_temporaryDir.path()); + _configFile.setLogDebug(true); + _configFile.setLogExpire(72); + _configFile.setLogFlush(true); + _configFile.setCertificatePath(_temporaryDir.path()); + _configFile.setCertificatePasswd("123456"); + _configFile.setLaunchOnSystemStartup(true); + _configFile.setServerHasValidSubscription(true); + _configFile.setDesktopEnterpriseChannel("stable"); + _configFile.setLanguage("pt"); + settings.sync(); + QVERIFY(_configFile.exists()); + QScopedPointer fakeQnam(new FakeQNAM({})); + OCC::AccountPtr account = OCC::Account::create(); + account->setDavUser("user"); + account->setDavDisplayName("Nextcloud user"); + account->setProxyType(QNetworkProxy::ProxyType::HttpProxy); + account->setProxyUser("proxyuser"); + account->setDownloadLimit(120); + account->setUploadLimit(120); + account->setDownloadLimitSetting(OCC::Account::AccountNetworkTransferLimitSetting::ManualLimit); + account->setServerVersion("30"); + account->setCredentials(new FakeCredentials{fakeQnam.data()}); + account->setUrl(QUrl(("http://example.de"))); + const auto accountState = OCC::AccountManager::instance()->addAccount(account); + OCC::AccountManager::instance()->saveAccount(accountState->account()); + OCC::FolderDefinition folderDefinition; + folderDefinition.localPath = "/standardAppName"; + folderDefinition.targetPath = "/"; + folderDefinition.alias = standardAppName; + _folderMan.reset({}); + _folderMan.reset(new FolderMan{}); + QVERIFY(_folderMan->addFolder(accountState, folderDefinition)); + } + + + void initTestCase() + { + OCC::Logger::instance()->setLogFlush(true); + OCC::Logger::instance()->setLogDebug(true); + + QStandardPaths::setTestModeEnabled(true); + } + + // Upgrade - TODO: test running app with --confdir + void testUpgrade() + { + // create Nextcloud config with older version + setupStandarConfig("1.0.0"); + const auto oldAppVersionNumber = QVersionNumber::fromString(_configFile.clientVersionString()); + QVERIFY(_configFile.isUpgrade()); + + // backup old config + const auto backupFilesList = _configFile.backupConfigFiles(); + QCOMPARE_GE(backupFilesList.size(), 1); + + // successfully upgrade to new config + const auto afterUpgradeVersionNumber = MIRALL_VERSION_STRING; + _configFile.setClientVersionString(afterUpgradeVersionNumber); + QVERIFY(MIRALL_VERSION_STRING == _configFile.clientVersionString()); + + QCOMPARE_GE(AccountManager::instance()->accounts().size(), 1); + auto accounts = AccountManager::instance()->accounts().first()->settings(); + QCOMPARE_GE(accounts->childGroups().size(), 1); + accounts->beginGroup(QLatin1String("Folders")); + QCOMPARE_GE(accounts->childGroups().size(), 1); + accounts->endGroup(); + } + + // From oC client to Nextcloud + void testMigrationFromOctoNextcloud() + { + QTemporaryDir tempDir; + QVERIFY(QDir(tempDir.path()).mkpath(legacyAppName)); + const auto ocConfigFolder = QString(tempDir.path() + "/" + legacyAppName); + const auto ocConfig = QString(ocConfigFolder + "/" + QString(legacyAppName).toLower() + ".cfg"); + QFile ocConfigFile(ocConfig); + QVERIFY(ocConfigFile.open(QFile::WriteOnly)); + QCOMPARE_GE(ocConfigFile.write(legacyAppConfigContent, qstrlen(legacyAppConfigContent)), 0); + ocConfigFile.close(); + + ConfigFile configFile; + + QVERIFY(QDir(tempDir.path()).mkpath(standardAppName)); + const auto standardConfigFolder = QString(tempDir.path() + "/" + standardAppName); + configFile.setConfDir(standardConfigFolder); + + // Nextcloud config file does not exist + QVERIFY(!configFile.exists()); + + // owncloud config files exists + configFile.findLegacyClientConfigFile(); + const auto legacyConfigFile = configFile.discoveredLegacyConfigFile(); + QVERIFY(!legacyConfigFile.isEmpty()); + QCOMPARE(legacyConfigFile, ocConfig); + + // TODO: add accounts and folders to AccountManager and FolderMan without UI interference + //_folderMan.reset({}); + // _folderMan.reset(new FolderMan{}); + // create accounts and folders from a legacy desktop client or for a new config file + // QVERIFY(AccountManager::instance()->restore(configFile.configFileToRestore()) != AccountManager::AccountsRestoreFailure); + // QCOMPARE_GE(FolderMan::instance()->setupFoldersMigration(), 1); + // QVERIFY(configFile.configFile().contains("nextcloud")); + // QCOMPARE_GE(AccountManager::instance()->accounts().size(), 1); + // auto accounts = AccountManager::instance()->accounts().first()->settings(); + // QCOMPARE_GE(accounts->childGroups().size(), 1); + // accounts->beginGroup(QLatin1String("Folders")); + // QCOMPARE_GE(accounts->childGroups().size(), 1); + // accounts->endGroup(); + } + + // From branded oC client to branded Nextcloud + void testMigrationFromBrandedOctoBrandedNextcloud() + { + QCoreApplication::setApplicationName(brandedAppName); + setupStandardConfigFolder(); + + // branded legacy have directory name in lower case + QTemporaryDir tempDir; + QVERIFY(QDir(tempDir.path()).mkpath(ocBrandedAppName)); + const auto ocBrandedConfigFolder = QString(tempDir.path() + "/" + ocBrandedAppName); + const auto ocBrandedConfig = QString(ocBrandedConfigFolder + "/" + QString(ocBrandedAppName) + ".cfg"); + QFile::copy(_configFile.configFile(), QFileInfo(ocBrandedConfig).filePath()); + + QFile ocBrandedConfigFile(ocBrandedConfig); + QVERIFY(ocBrandedConfigFile.open(QFile::WriteOnly)); + QCOMPARE_GE(ocBrandedConfigFile.write(legacyAppConfigContent, qstrlen(legacyAppConfigContent)), 0); + ocBrandedConfigFile.close(); + + ConfigFile configFile; + QVERIFY(QDir(tempDir.path()).mkpath(brandedAppName)); + const auto brandedConfigFolder = QString(tempDir.path() + "/" + brandedAppName); + configFile.setConfDir(brandedConfigFolder); + + const auto path3 = _configFile.configFile(); + const auto path4 = configFile.configFile(); + + // our branded config file does not exist + QVERIFY(!configFile.exists()); + + // branded owncloud config files exists + configFile.findLegacyClientConfigFile(); + const auto legacyConfigFile = configFile.discoveredLegacyConfigFile(); + QVERIFY(!legacyConfigFile.isEmpty()); + QCOMPARE(legacyConfigFile, ocBrandedConfig); + } + + + // From the standard Nextcloud client to a branded version + void testMigrationFromNextcloudToBranded() + { + setupStandardConfigFolder(); + + QTemporaryDir tempDir; + QVERIFY(QDir(tempDir.path()).mkpath(brandedAppName)); + const auto brandedConfigFolder = QString(tempDir.path() + "/" + brandedAppName); + const auto brandedConfig = QString(brandedConfigFolder + "/" + QString(brandedAppName).toLower() + ".cfg"); + QFile::copy(_configFile.configFile(), QFileInfo(brandedConfig).filePath()); + + ConfigFile configFile; + QVERIFY(QDir(tempDir.path()).mkpath(standardAppName)); + const auto standardConfigFolder = QString(tempDir.path() + "/" + standardAppName); + configFile.setConfDir(standardConfigFolder); + + // Nextcloud config file does not exist + QVERIFY(!configFile.exists()); + + // owncloud config files exists + configFile.findLegacyClientConfigFile(); + const auto legacyConfigFile = configFile.discoveredLegacyConfigFile(); + QVERIFY(!legacyConfigFile.isEmpty()); + QCOMPARE(legacyConfigFile, brandedConfig); + } + + // TODO: Downgrade +}; + +QTEST_GUILESS_MAIN(TestMigration) +#include "testmigration.moc" From 1c934f542fc66c37b0fe969c7b1d35b9739bf746 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Tue, 13 May 2025 16:21:00 +0200 Subject: [PATCH 05/10] refactor: ConfigFile::findLegacyClientConfigFile(). Signed-off-by: Camila Ayres --- src/libsync/configfile.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 24c3de19281c0..28d2f783d33c4 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -1412,15 +1412,16 @@ void ConfigFile::findLegacyClientConfigFile() break; } - if (const QFileInfo configFileInfo(configFile); - configFileInfo.exists() && configFileInfo.isReadable()) { - qCInfo(lcConfigFile) << "Migrate: saving old config file" << configFile; - setDiscoveredLegacyConfigFile(configFileInfo.filePath()); - setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); - break; - } else { + const QFileInfo configFileInfo(configFile); + if (!configFileInfo.exists() || !configFileInfo.isReadable()) { qCInfo(lcConfigFile()) << "Migrate: could not read old config " << configFile; + continue; } + + qCInfo(lcConfigFile) << "Migrate: old config file" << configFile; + setDiscoveredLegacyConfigFile(configFileInfo.filePath()); + setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + break; } } } From 19e9cee7c3031085ecd47e6cd4e8b2645b0dddeb Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Tue, 13 May 2025 16:43:17 +0200 Subject: [PATCH 06/10] refactor(migration): include more paths to search for legacy config files. Signed-off-by: Camila Ayres --- src/libsync/configfile.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 28d2f783d33c4..dba96656f8075 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -1379,12 +1379,12 @@ void ConfigFile::findLegacyClientConfigFile() // 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('/')); + const auto legacyStandardPaths = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data")); + const auto legacyStandardPathsParentFolder = legacyStandardPaths.left(legacyStandardPaths.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('/')); + const auto standardPaths = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation); + const auto standardPathsParentFolder = standardPaths.left(standardPaths.lastIndexOf('/')); // Now try the locations we use today const auto fullLegacyCfgFile = QDir::fromNativeSeparators(settings->fileName()); @@ -1394,17 +1394,21 @@ void ConfigFile::findLegacyClientConfigFile() const auto legacyCfgFileNamePath = QString(QStringLiteral("/") + legacyCfgFileNameC); const auto legacyCfgFileRelativePath = QString(legacyRelativeConfigLocationC); - auto legacyLocations = QVector{legacy2_4CfgFileParentFolder + legacyCfgFileRelativePath, - legacy2_5CfgFileParentFolder + legacyCfgFileRelativePath, - legacyCfgFileParentFolder + legacyCfgFileNamePath, - legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath}; + auto legacyLocations = QVector{legacyStandardPathsParentFolder + legacyCfgFileRelativePath, + standardPathsParentFolder + legacyCfgFileRelativePath, + legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath, + legacyCfgFileParentFolder + legacyCfgFileNamePath}; if (Theme::instance()->isBranded()) { const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC); const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC); - legacyLocations.append({legacyCfgFileParentFolder + unbrandedCfgFileNamePath, legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); + legacyLocations.append({standardPathsParentFolder + unbrandedCfgFileRelativePath, + legacyCfgFileParentFolder + unbrandedCfgFileNamePath, + legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); } + qCInfo(lcConfigFile) << "Paths:" << legacyLocations; + for (const auto &configFile : legacyLocations) { auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); if (oCSettings->status() != QSettings::Status::NoError) { From f76a16d89999fb9c601f2bf151da5ee25c7c30ed Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Tue, 13 May 2025 16:59:08 +0200 Subject: [PATCH 07/10] gui: display legacy file location when asking user to import it. Signed-off-by: Camila Ayres --- src/gui/accountmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 36b989872cf86..2d4d7ea0419fb 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -656,10 +656,10 @@ bool AccountManager::confirmRestoreExistingAccounts() const && Theme::instance()->displayLegacyImportDialog() && !configFile.discoveredLegacyConfigFile().isEmpty()) { const auto importQuestion = accountsListSize > 1 - ? tr("%1 accounts were detected from a legacy desktop client.\n" - "Should the accounts be imported?").arg(QString::number(accountsListSize)) - : tr("1 account was detected from a legacy desktop client.\n" - "Should the account be imported?"); + ? tr("%1 accounts were detected from a legacy desktop client at %2.\n" + "Should the accounts be imported?").arg(QString::number(accountsListSize), configFile.discoveredLegacyConfigFile()) + : tr("1 account was detected from a legacy desktop client at %1.\n" + "Should the account be imported?").arg(configFile.discoveredLegacyConfigFile()); const auto importMessageBox = new QMessageBox(QMessageBox::Question, tr("Legacy import"), importQuestion); importMessageBox->addButton(tr("Import"), QMessageBox::AcceptRole); const auto skipButton = importMessageBox->addButton(tr("Skip"), QMessageBox::DestructiveRole); From 8b5d26a9dc8dfbf9b04d90efc3f1031cb1c1c12b Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Wed, 14 May 2025 12:34:31 +0200 Subject: [PATCH 08/10] fix(migration): remove check for existing settings when looking for legacy file. At that point we already know that the config file does not exist. Signed-off-by: Camila Ayres --- src/libsync/configfile.cpp | 100 +++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index dba96656f8075..04c2c1a035e3f 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -1369,64 +1369,56 @@ QT_WARNING_POP void ConfigFile::findLegacyClientConfigFile() { - qCInfo(lcConfigFile) << "Migrate: restoreFromLegacySettings, checking settings group" - << Theme::instance()->appName(); - - // try to open the correctly themed settings - auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); - - // 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 legacyStandardPaths = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data")); - const auto legacyStandardPathsParentFolder = legacyStandardPaths.left(legacyStandardPaths.lastIndexOf('/')); - - // 2.5+ (rest of 2.x series) - const auto standardPaths = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation); - const auto standardPathsParentFolder = standardPaths.left(standardPaths.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{legacyStandardPathsParentFolder + legacyCfgFileRelativePath, - standardPathsParentFolder + legacyCfgFileRelativePath, - legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath, - legacyCfgFileParentFolder + legacyCfgFileNamePath}; - - if (Theme::instance()->isBranded()) { - const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC); - const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC); - legacyLocations.append({standardPathsParentFolder + unbrandedCfgFileRelativePath, - legacyCfgFileParentFolder + unbrandedCfgFileNamePath, - legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); - } + qCInfo(lcConfigFile) << "Migrate: findLegacyClientConfigFile" << Theme::instance()->appName(); + + // Legacy settings used QDesktopServices to get the location for the config folder in 2.4 and before + const auto legacyStandardPaths = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data")); + const auto legacyStandardPathsParentFolder = legacyStandardPaths.left(legacyStandardPaths.lastIndexOf('/')); + + // 2.5+ (rest of 2.x series) + const auto standardPaths = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation); + const auto standardPathsParentFolder = standardPaths.left(standardPaths.lastIndexOf('/')); + + // Now try the locations we use today + const auto fullLegacyCfgFile = QDir::fromNativeSeparators(configFile()); + 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{legacyStandardPathsParentFolder + legacyCfgFileRelativePath, + standardPathsParentFolder + legacyCfgFileRelativePath, + legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath, + legacyCfgFileParentFolder + legacyCfgFileNamePath}; + + if (Theme::instance()->isBranded()) { + const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC); + const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC); + legacyLocations.append({standardPathsParentFolder + unbrandedCfgFileRelativePath, + legacyCfgFileParentFolder + unbrandedCfgFileNamePath, + legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); + } - qCInfo(lcConfigFile) << "Paths:" << legacyLocations; + qCDebug(lcConfigFile) << "Looking for existing config files at:" << legacyLocations; - for (const auto &configFile : legacyLocations) { - auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); - if (oCSettings->status() != QSettings::Status::NoError) { - qCInfo(lcConfigFile) << "Error reading legacy configuration file" << oCSettings->status(); - break; - } - - const QFileInfo configFileInfo(configFile); - if (!configFileInfo.exists() || !configFileInfo.isReadable()) { - qCInfo(lcConfigFile()) << "Migrate: could not read old config " << configFile; - continue; - } - - qCInfo(lcConfigFile) << "Migrate: old config file" << configFile; - setDiscoveredLegacyConfigFile(configFileInfo.filePath()); - setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + for (const auto &configFile : legacyLocations) { + auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); + if (oCSettings->status() != QSettings::Status::NoError) { + qCInfo(lcConfigFile) << "Error reading legacy configuration file" << oCSettings->status(); break; } + + const QFileInfo configFileInfo(configFile); + if (!configFileInfo.exists() || !configFileInfo.isReadable()) { + qCInfo(lcConfigFile()) << "Migrate: could not read old config " << configFile; + continue; + } + + qCInfo(lcConfigFile) << "Migrate: old config file" << configFile; + setDiscoveredLegacyConfigFile(configFileInfo.filePath()); + setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + break; } } From a36d65d18f9b8e44ece1ce5685e5589d7ef90f0c Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Wed, 14 May 2025 18:21:29 +0200 Subject: [PATCH 09/10] refactor(migration): remove logic used for locating legacy config files. The same logic is already implemented in ConfigFile::findLegacyClientConfigFile. Signed-off-by: Camila Ayres --- src/gui/application.cpp | 14 +-------- src/libsync/configfile.cpp | 58 +++----------------------------------- src/libsync/configfile.h | 2 -- 3 files changed, 5 insertions(+), 69 deletions(-) diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 364cd4f1486b5..f1b6b1b35be9d 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -169,22 +169,10 @@ Application::Application(int &argc, char **argv) } ConfigFile configFile; - // First check if there is a valid config file if (const auto configExists = configFile.exists(); configExists && makeConfigSettingsBackwardCompatible()) { qCWarning(lcApplication) << "Existing config is compatible with current version."; } else if (!configExists) { - // look for previous used application name for config folder. - if (const auto genericConfigLocation = QString(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/" + APPLICATION_CONFIG_NAME); - configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { - qCWarning(lcApplication) << "Copy of config folder and files from legacy location" << genericConfigLocation << "suceeded."; - - // app data folder - } else if (const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - configFile.setupConfigFolderFromLegacyLocation(genericConfigLocation)) { - qCWarning(lcApplication) << "Copy of config folder and files from legacy location" << appDataLocation << "suceeded."; - } else { - configFile.findLegacyClientConfigFile(); - } + configFile.findLegacyClientConfigFile(); } if (_quitInstance) { diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 04c2c1a035e3f..4bd018ad1a267 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -1314,59 +1314,6 @@ void ConfigFile::setDiscoveredLegacyConfigFile(const QString &discoveredLegacyCo _discoveredLegacyConfigFile = discoveredLegacyConfigFile; } -bool ConfigFile::setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const -{ - // Migrate from version <= 2.4 - qApp->setApplicationName(Theme::instance()->appNameGUI()); -#ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 - #define QT_WARNING_DISABLE_DEPRECATED QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") -#endif -QT_WARNING_PUSH -QT_WARNING_DISABLE_DEPRECATED -QT_WARNING_POP - qApp->setApplicationName(Theme::instance()->appName()); - - auto legacyDir = legacyLocation; - if (legacyDir.endsWith('/')) { - legacyDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move. - } - - if (!QFileInfo(legacyDir).isDir()) { - return false; - } - - auto confDir = ConfigFile().configPath(); - if (confDir.endsWith('/')) { - confDir.chop(1); - } - - qCInfo(lcConfigFile) << "Migrating old config from" << legacyDir << "to" << confDir; - if (!QFile::rename(legacyDir, confDir)) { - qCWarning(lcConfigFile) << "Failed to move the old config directory" << legacyDir << "to new location" << confDir; - if (QFileInfo(confDir).isDir() || QDir().mkdir(confDir)) { - const QStringList filesList = QDir(legacyDir).entryList(QDir::Files); - qCInfo(lcConfigFile) << "Will move the individual files:" << filesList; - auto setupCompleted = false; - for (const auto &name : filesList) { - if (!QFile::rename(legacyDir + "/" + name, confDir + "/" + name)) { - qCDebug(lcConfigFile) << "Fallback move of " << name << "also failed"; - continue; - } - setupCompleted = true; - qCInfo(lcConfigFile) << "Move of " << name << "succeeded."; - } - return setupCompleted; - } - } else { -#ifndef Q_OS_WIN - // Create a symbolic link so a downgrade of the client would still find the config. - return QFile::link(confDir, legacyDir); -#endif - } - - return false; -} - void ConfigFile::findLegacyClientConfigFile() { qCInfo(lcConfigFile) << "Migrate: findLegacyClientConfigFile" << Theme::instance()->appName(); @@ -1395,7 +1342,10 @@ void ConfigFile::findLegacyClientConfigFile() if (Theme::instance()->isBranded()) { const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC); const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC); - legacyLocations.append({standardPathsParentFolder + unbrandedCfgFileRelativePath, + const auto brandedLegacyCfgFilePath = QString(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/") + APPLICATION_SHORTNAME + QStringLiteral("/")); + const auto brandedLegacyCfgFile = QString(APPLICATION_CONFIG_NAME + QStringLiteral(".cfg")); + legacyLocations.append({brandedLegacyCfgFilePath + brandedLegacyCfgFile, + standardPathsParentFolder + unbrandedCfgFileRelativePath, legacyCfgFileParentFolder + unbrandedCfgFileNamePath, legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 8d74523aeed37..94ffa30e6b932 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -259,8 +259,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static constexpr char uploadLimitC[] = "BWLimit/uploadLimit"; static constexpr char downloadLimitC[] = "BWLimit/downloadLimit"; - [[nodiscard]] bool setupConfigFolderFromLegacyLocation(const QString &legacyLocation) const; - /** * Looks for config files with different names from older client versions * in different locations From bdde6f23f47346ee3c1710627ef3873cf9866325 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Thu, 15 May 2025 11:18:00 +0200 Subject: [PATCH 10/10] wip: add Migration class. Signed-off-by: Camila Ayres --- src/libsync/CMakeLists.txt | 3 +- src/libsync/migration.cpp | 313 +++++++++++++++++++++++++++++++++++++ src/libsync/migration.h | 59 +++++++ 3 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 src/libsync/migration.cpp create mode 100644 src/libsync/migration.h diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 8871024278d4c..3f47079235dce 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -208,7 +208,8 @@ ENDIF(NOT APPLE) find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WebSockets Xml Sql Gui Svg Widgets) -add_library(nextcloudsync SHARED ${libsync_SRCS}) +add_library(nextcloudsync SHARED ${libsync_SRCS} + migration.h migration.cpp) add_library(Nextcloud::sync ALIAS nextcloudsync) target_link_libraries(nextcloudsync diff --git a/src/libsync/migration.cpp b/src/libsync/migration.cpp new file mode 100644 index 0000000000000..7589beab1aa39 --- /dev/null +++ b/src/libsync/migration.cpp @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "migration.h" + +#include "theme.h" +#include "config.h" +#include "configfile.h" +#include "common/utility.h" +#include "version.h" +#include "gui/folder.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr auto legacyRelativeConfigLocationC = "/ownCloud/owncloud.cfg"; +constexpr auto legacyCfgFileNameC = "owncloud.cfg"; +constexpr auto unbrandedRelativeConfigLocationC = "/Nextcloud/nextcloud.cfg"; +constexpr auto unbrandedCfgFileNameC = "nextcloud.cfg"; + +constexpr auto accountsC = "Accounts"; +constexpr auto versionC = "version"; + +constexpr auto maxAccountsVersion = 13; +constexpr auto maxAccountVersion = 13; + +constexpr auto settingsAccountsC = "Accounts"; +constexpr auto settingsFoldersC = "Folders"; +constexpr auto settingsFoldersWithPlaceholdersC = "FoldersWithPlaceholders"; +constexpr auto settingsVersionC = "version"; +constexpr auto maxFoldersVersion = 1; +} + +namespace OCC { + +Q_LOGGING_CATEGORY(lcMigration, "nextcloud.gui.migration", QtInfoMsg); + +Migration::Migration() +{} + +QString Migration::discoveredLegacyConfigPath() const +{ + return _discoveredLegacyConfigPath; +} + +void Migration::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath) +{ + if (_discoveredLegacyConfigPath == discoveredLegacyConfigPath) { + return; + } + + _discoveredLegacyConfigPath = discoveredLegacyConfigPath; +} + +QString Migration::discoveredLegacyConfigFile() const +{ + return _discoveredLegacyConfigFile; +} + +void Migration::setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile) +{ + if (_discoveredLegacyConfigFile == discoveredLegacyConfigFile) { + return; + } + + _discoveredLegacyConfigFile = discoveredLegacyConfigFile; +} + +void Migration::findLegacyClientConfigFile() +{ + qCInfo(lcMigration) << "Migrate: findLegacyClientConfigFile" << Theme::instance()->appName(); + + // Legacy settings used QDesktopServices to get the location for the config folder in 2.4 and before + const auto legacyStandardPaths = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data")); + const auto legacyStandardPathsParentFolder = legacyStandardPaths.left(legacyStandardPaths.lastIndexOf('/')); + + // 2.5+ (rest of 2.x series) + const auto standardPaths = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation); + const auto standardPathsParentFolder = standardPaths.left(standardPaths.lastIndexOf('/')); + + // Now try the locations we use today + const auto fullLegacyCfgFile = QDir::fromNativeSeparators(ConfigFile().configFile()); + 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{legacyStandardPathsParentFolder + legacyCfgFileRelativePath, + standardPathsParentFolder + legacyCfgFileRelativePath, + legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath, + legacyCfgFileParentFolder + legacyCfgFileNamePath}; + + if (Theme::instance()->isBranded()) { + const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC); + const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC); + const auto brandedLegacyCfgFilePath = QString(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/") + APPLICATION_SHORTNAME + QStringLiteral("/")); + const auto brandedLegacyCfgFile = QString(APPLICATION_CONFIG_NAME + QStringLiteral(".cfg")); + legacyLocations.append({brandedLegacyCfgFilePath + brandedLegacyCfgFile, + standardPathsParentFolder + unbrandedCfgFileRelativePath, + legacyCfgFileParentFolder + unbrandedCfgFileNamePath, + legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath}); + } + + qCDebug(lcMigration) << "Looking for existing config files at:" << legacyLocations; + + for (const auto &configFile : legacyLocations) { + auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); + if (oCSettings->status() != QSettings::Status::NoError) { + qCInfo(lcMigration) << "Error reading legacy configuration file" << oCSettings->status(); + break; + } + + const QFileInfo configFileInfo(configFile); + if (!configFileInfo.exists() || !configFileInfo.isReadable()) { + qCInfo(lcMigration()) << "Migrate: could not read old config " << configFile; + continue; + } + + qCInfo(lcMigration) << "Migrate: old config file" << configFile; + setDiscoveredLegacyConfigFile(configFileInfo.filePath()); + setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath()); + break; + } +} + +bool Migration::isUpgrade() const +{ + return QVersionNumber::fromString(MIRALL_VERSION_STRING) > QVersionNumber::fromString(ConfigFile().clientVersionString()); +} + +bool Migration::isDowngrade() const +{ + return QVersionNumber::fromString(ConfigFile().clientVersionString()) > QVersionNumber::fromString(MIRALL_VERSION_STRING); +} + +QString Migration::configFileToRestore() const +{ + const auto legacyConfigFile = discoveredLegacyConfigFile(); + if (legacyConfigFile.isEmpty()) { + return ConfigFile().configFile(); + } + + return legacyConfigFile; +} + +QString Migration::backup(const QString &fileName) const +{ + const QString baseFilePath = ConfigFile().configPath() + fileName; + auto versionString = ConfigFile().clientVersionString(); + + if (!versionString.isEmpty()) { + versionString.prepend('_'); + } + + QString backupFile = + QStringLiteral("%1.backup_%2%3") + .arg(baseFilePath) + .arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")) + .arg(versionString); + + // If this exact file already exists it's most likely that a backup was + // already done. (two backup calls directly after each other, potentially + // even with source alterations in between!) + // QFile does not overwrite backupFile + if(!QFile::copy(baseFilePath, backupFile)) { + qCWarning(lcMigration) << "Failed to create a backup of the config file" << baseFilePath; + } + + return backupFile; +} + +QStringList Migration::backupConfigFiles() const +{ + // 'Launch on system startup' defaults to true > 3.11.x + const auto theme = Theme::instance(); + ConfigFile().setLaunchOnSystemStartup(ConfigFile().launchOnSystemStartup()); + Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), ConfigFile().launchOnSystemStartup()); + + // default is now off to displaying dialog warning user of too many files deletion + ConfigFile().setPromptDeleteFiles(false); + + // back up all old config files + QStringList backupFilesList; + QDir configDir(ConfigFile().configPath()); + const auto anyConfigFileNameList = configDir.entryInfoList({"*.cfg"}, QDir::Files); + for (const auto &oldConfig : anyConfigFileNameList) { + const auto oldConfigFileName = oldConfig.fileName(); + const auto oldConfigFilePath = oldConfig.filePath(); + const auto newConfigFileName = ConfigFile().configFile(); + backupFilesList.append(backup(oldConfigFileName)); + if (oldConfigFilePath != newConfigFileName) { + if (!QFile::rename(oldConfigFilePath, newConfigFileName)) { + qCWarning(lcMigration) << "Failed to rename configuration file from" << oldConfigFilePath << "to" << newConfigFileName; + } + } + } + + return backupFilesList; +} + +void Migration::accountbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys) +{ + const auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); + const auto accountsVersion = settings->value(QLatin1String(versionC)).toInt(); + + qCInfo(lcMigration) << "Checking for accounts versions."; + qCInfo(lcMigration) << "Config accounts version:" << accountsVersion; + qCInfo(lcMigration) << "Max accounts Version is set to:" << maxAccountsVersion; + if (accountsVersion <= maxAccountsVersion) { + const auto settingsChildGroups = settings->childGroups(); + for (const auto &accountId : settingsChildGroups) { + settings->beginGroup(accountId); + const auto accountVersion = settings->value(QLatin1String(versionC), 1).toInt(); + + if (accountVersion > maxAccountVersion) { + ignoreKeys->append(settings->group()); + qCInfo(lcMigration) << "Ignoring account" << accountId << "because of version" << accountVersion; + } + settings->endGroup(); + } + } else { + deleteKeys->append(settings->group()); + } +} + +void Migration::folderbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys) +{ + auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts")); + + auto processSubgroup = [&](const QString &name) { + settings->beginGroup(name); + const auto foldersVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt(); + qCInfo(lcMigration) << "FolderDefinition::maxSettingsVersion:" << FolderDefinition::maxSettingsVersion(); + if (foldersVersion <= maxFoldersVersion) { + for (const auto &folderAlias : settings->childGroups()) { + settings->beginGroup(folderAlias); + const auto folderVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt(); + if (folderVersion > FolderDefinition::maxSettingsVersion()) { + qCInfo(lcMigration) << "Ignoring folder:" << folderAlias << "version:" << folderVersion; + ignoreKeys->append(settings->group()); + } + settings->endGroup(); + } + } else { + qCInfo(lcMigration) << "Ignoring group:" << name << "version:" << foldersVersion; + deleteKeys->append(settings->group()); + } + settings->endGroup(); + }; + + const auto settingsChildGroups = settings->childGroups(); + for (const auto &accountId : settingsChildGroups) { + settings->beginGroup(accountId); + processSubgroup("Folders"); + processSubgroup("Multifolders"); + processSubgroup("FoldersWithPlaceholders"); + settings->endGroup(); + } +} + +bool Migration::makeConfigSettingsBackwardCompatible() const +{ + ConfigFile configFile; + const auto didVersionChanged = configFile.isUpgrade() || configFile.isDowngrade(); + if (!didVersionChanged) { + qCInfo(lcMigration) << "No upgrade or downgrade detected."; + return true; + } + + configFile.cleanUpdaterConfiguration(); + + QStringList deleteKeys, ignoreKeys; + // accountbackwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); + // folderbackwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); + if (!didVersionChanged && !(!deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && didVersionChanged))) { + qCInfo(lcMigration) << "There are no settings to delete or to ignore. No need to change the current config file."; + return true; + } + + const auto isDeleteKeysEmpty = deleteKeys.isEmpty(); + if (const auto backupFilesList = configFile.backupConfigFiles(); + configFile.showConfigBackupWarning() + && backupFilesList.size() > 0 + /*&& !confirmConfigChangesOrQuitApp(isDeleteKeysEmpty, backupFilesList)*/) { + return false; + } + + if (!isDeleteKeysEmpty) { + auto settings = configFile.settingsWithGroup("foo"); + settings->endGroup(); + + // Wipe confusing keys from the future, ignore the others + for (const auto &badKey : std::as_const(deleteKeys)) { + settings->remove(badKey); + qCDebug(lcMigration) << "Migration: removed" << badKey << "key from settings."; + } + } + + configFile.setClientVersionString(MIRALL_VERSION_STRING); + qCDebug(lcMigration) << "Client version changed to" << configFile.clientVersionString(); + + return true; +} +} diff --git a/src/libsync/migration.h b/src/libsync/migration.h new file mode 100644 index 0000000000000..927e45bc590d9 --- /dev/null +++ b/src/libsync/migration.h @@ -0,0 +1,59 @@ +#ifndef MIGRATION_H +#define MIGRATION_H + +#include + +namespace OCC { + +class Migration +{ +public: + Migration(); + /** + * Looks for config files with different names from older client versions + * in different locations + * + * Returns the found config file path found. + */ + void findLegacyClientConfigFile(); + + /** + * Maybe a newer version of the client was used with this config file: if so, backup. + * Return backup files list. + */ + [[nodiscard]] QStringList backupConfigFiles() const; + [[nodiscard]] bool isUpgrade() const; + [[nodiscard]] bool isDowngrade() const; + [[nodiscard]] QString configFileToRestore() const; + [[nodiscard]] QString findLegacyConfigFile() const; + [[nodiscard]] bool makeConfigSettingsBackwardCompatible() const; + + [[nodiscard]] QString backup(const QString &fileName) const; + + /** + * Returns the list of settings keys that can't be read because + * they are from the future. + */ + void accountbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); + + /** + * Returns a list of keys that can't be read because they are from + * future versions. + */ + void folderbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); + + + /// Set during first time migration of legacy accounts in AccountManager + [[nodiscard]] QString discoveredLegacyConfigPath() const; + void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath); + [[nodiscard]] QString discoveredLegacyConfigFile() const; + void setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile); + +private: + QString _confDir; + QString _discoveredLegacyConfigPath; + QString _discoveredLegacyConfigFile; + +}; +} +#endif // MIGRATION_H