diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 51630d0a95cfa..ce981e11a6eab 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace { constexpr auto urlC = "url"; @@ -178,26 +179,33 @@ bool AccountManager::restoreFromLegacySettings() legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath}; 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()) { - const auto importQuestion = tr("An existing configuration from a legacy desktop client was detected.\n" - "Should an account import be attempted?"); - const auto messageBoxSelection = QMessageBox::question(nullptr, tr("Legacy import"), importQuestion); - - if (messageBoxSelection == QMessageBox::No) { - // User said don't import, return immediately + if (!forceLegacyImport() && accountsListSize > 0) { + 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->setAttribute(Qt::WA_DeleteOnClose); + importMessageBox->exec(); + if (importMessageBox->clickedButton() == skipButton) { return false; } } - auto oCSettings = std::make_unique(configFile, QSettings::IniFormat); - if (oCSettings->status() != QSettings::Status::NoError) { - qCInfo(lcAccountManager) << "Error reading legacy configuration file" << oCSettings->status(); - break; - } - // Check the theme url to see if it is the same url that the oC config was for const auto overrideUrl = Theme::instance()->overrideServerUrl(); const auto cleanOverrideUrl = overrideUrl.endsWith('/') ? overrideUrl.chopped(1) : overrideUrl; @@ -206,7 +214,6 @@ bool AccountManager::restoreFromLegacySettings() if (!cleanOverrideUrl.isEmpty()) { oCSettings->beginGroup(QLatin1String(accountsC)); const auto accountsChildGroups = oCSettings->childGroups(); - for (const auto &accountId : accountsChildGroups) { oCSettings->beginGroup(accountId); const auto oCUrl = oCSettings->value(QLatin1String(urlC)).toString(); @@ -250,10 +257,7 @@ bool AccountManager::restoreFromLegacySettings() for (const auto &accountId : childGroups) { settings->beginGroup(accountId); if (const auto acc = loadAccountHelper(*settings)) { - addAccount(acc); - QMessageBox::information(nullptr, - tr("Legacy import"), - tr("Successfully imported account from legacy client: %1").arg(acc->prettyName())); + addAccount(acc); } settings->endGroup(); } diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 18b01f6c2df7f..9712dabbda010 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -245,53 +245,8 @@ Application::Application(int &argc, char **argv) setApplicationName(_theme->appName()); setWindowIcon(_theme->applicationIcon()); - if (!ConfigFile().exists()) { - // 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 - QString oldDir = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - - // macOS 10.11.x does not like trailing slash for rename/move. - if (oldDir.endsWith('/')) { - oldDir.chop(1); - } - - QT_WARNING_POP - setApplicationName(_theme->appName()); - if (QFileInfo(oldDir).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" << 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 - } - } + if (ConfigFile().exists()) { + setupConfigFile(); } if (_theme->doNotUseProxy()) { @@ -328,12 +283,13 @@ 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()) { - return; + qCWarning(lcApplication) << "Config version migration was not possible."; } ConfigFile cfg; - { auto shouldExit = false; @@ -357,74 +313,53 @@ Application::Application(int &argc, char **argv) } } - // The timeout is initialized with an environment variable, if not, override with the value from the config - if (!AbstractNetworkJob::httpTimeout) + if (!AbstractNetworkJob::httpTimeout) { AbstractNetworkJob::httpTimeout = cfg.timeout(); + } // Check vfs plugins if (Theme::instance()->showVirtualFilesOption() && bestAvailableVfsMode() == Vfs::Off) { qCWarning(lcApplication) << "Theme wants to show vfs mode, but no vfs plugins are available"; } + if (isVfsPluginAvailable(Vfs::WindowsCfApi)) { qCInfo(lcApplication) << "VFS windows plugin is available"; } + if (isVfsPluginAvailable(Vfs::WithSuffix)) { qCInfo(lcApplication) << "VFS suffix plugin is available"; } - _folderManager.reset(new FolderMan); + _theme->setSystrayUseMonoIcons(ConfigFile().monoIcons()); + connect(_theme, &Theme::systrayUseMonoIconsChanged, this, &Application::slotUseMonoIconsChanged); + #if defined(Q_OS_WIN) _shellExtensionsServer.reset(new ShellExtensionsServer); #endif connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); - 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()), - tr("Quit %1").arg(Theme::instance()->appNameGUI())); - QTimer::singleShot(0, qApp, &QCoreApplication::quit); - return; - } - } - #if defined(BUILD_FILE_PROVIDER_MODULE) _fileProvider.reset(new Mac::FileProvider); #endif - FolderMan::instance()->setSyncEnabled(true); + // create accounts and folders from a legacy desktop client or from the current config file + setupAccountsAndFolders(); setQuitOnLastWindowClosed(false); - _theme->setSystrayUseMonoIcons(cfg.monoIcons()); - connect(_theme, &Theme::systrayUseMonoIconsChanged, this, &Application::slotUseMonoIconsChanged); - // Setting up the gui class will allow tray notifications for the // setup that follows, like folder setup _gui = new ownCloudGui(this); if (_showLogWindow) { _gui->slotToggleLogBrowser(); // _showLogWindow is set in parseOptions. } + #if WITH_LIBCLOUDPROVIDERS _gui->setupCloudProviders(); #endif - FolderMan::instance()->setupFolders(); _proxy.setupQtProxyFromConfig(); // folders have to be defined first, than we set up the Qt proxy. connect(AccountManager::instance(), &AccountManager::accountAdded, @@ -492,6 +427,128 @@ Application::~Application() AccountManager::instance()->shutdown(); } +void Application::setupAccountsAndFolders() +{ + const auto accountsRestoreResult = restoreLegacyAccount(); + + _folderManager.reset(new FolderMan); + FolderMan::instance()->setSyncEnabled(true); + 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(); + 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(); + } 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::DataLocation); + + // 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()), + tr("Quit %1").arg(Theme::instance()->appNameGUI())); + 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 bb9f97bef0b57..fb9634b5ec974 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -118,6 +118,10 @@ 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/gui/folder.h b/src/gui/folder.h index f0622f48c1b57..4b4e6e15a3b65 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -82,8 +82,9 @@ class FolderDefinition * Version 2: introduction of metadata_parent hash in 2.6.0 * (version remains readable by 2.5.1) * Version 3: introduction of new windows vfs mode in 2.6.0 + * Version 5: available in oC client 4.0.0 and 4.2.0 */ - static int maxSettingsVersion() { return 3; } + static int maxSettingsVersion() { return 5; } /// Ensure / as separator and trailing /. static QString prepareLocalPath(const QString &path); diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index fee5eaef61bac..40ec8412685d3 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -364,7 +364,7 @@ int FolderMan::setupFoldersMigration() for (const auto &fileName : dirFiles) { for (const auto &accountState : legacyAccounts) { const auto fullFilePath = dir.filePath(fileName); - setupFolderFromOldConfigFile(fullFilePath, accountState.data()); + setupLegacyFolder(fullFilePath, accountState.data()); } } @@ -481,7 +481,7 @@ QString FolderMan::unescapeAlias(const QString &alias) return a; } -void FolderMan::setupFolderFromOldConfigFile(const QString &fileNamePath, AccountState *accountState) +void FolderMan::setupLegacyFolder(const QString &fileNamePath, AccountState *accountState) { qCInfo(lcFolderMan) << " ` -> setting up:" << fileNamePath; QString escapedFileNamePath(fileNamePath); diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 731b37d98ac5d..65f23ec5e39be 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -122,7 +122,7 @@ class FolderMan : public QObject * Migrate accounts from owncloud * Creates a folder for a specific configuration, identified by alias. */ - void setupFolderFromOldConfigFile(const QString &, AccountState *account); + void setupLegacyFolder(const QString &, AccountState *account); /** * Ensures that a given directory does not contain a sync journal file.