diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index fa9da6237..ff4375bc7 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -36,6 +36,10 @@ static const char caCertsKeyC[] = "CaCertificates"; static const char accountsC[] = "Accounts"; static const char versionC[] = "version"; static const char serverVersionC[] = "serverVersion"; + +// The maximum versions that this client can read +static const int maxAccountsVersion = 2; +static const int maxAccountVersion = 1; } @@ -79,6 +83,27 @@ bool AccountManager::restore() return true; } +QStringList AccountManager::backwardMigrationKeys() +{ + auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); + QStringList badKeys; + + const int accountsVersion = settings->value(QLatin1String(versionC)).toInt(); + if (accountsVersion <= maxAccountsVersion) { + foreach (const auto &accountId, settings->childGroups()) { + settings->beginGroup(accountId); + const int accountVersion = settings->value(QLatin1String(versionC), 1).toInt(); + if (accountVersion > maxAccountVersion) { + badKeys.append(settings->group()); + } + settings->endGroup(); + } + } else { + badKeys.append(settings->group()); + } + return badKeys; +} + bool AccountManager::restoreFromLegacySettings() { qCInfo(lcAccountManager) << "Migrate: restoreFromLegacySettings, checking settings group" @@ -139,7 +164,7 @@ bool AccountManager::restoreFromLegacySettings() void AccountManager::save(bool saveCredentials) { auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - settings->setValue(QLatin1String(versionC), 2); + settings->setValue(QLatin1String(versionC), maxAccountsVersion); for (const auto &acc : qAsConst(_accounts)) { settings->beginGroup(acc->account()->id()); saveAccountHelper(acc->account().data(), *settings, saveCredentials); @@ -177,6 +202,7 @@ void AccountManager::saveAccountState(AccountState *a) void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool saveCredentials) { + settings.setValue(QLatin1String(versionC), maxAccountVersion); settings.setValue(QLatin1String(urlC), acc->_url.toString()); settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion); if (acc->_credentials) { diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index e06148757..2a07eb3c0 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -76,6 +76,12 @@ public: */ static AccountPtr createAccount(); + /** + * Returns the list of settings keys that can't be read because + * they are from the future. + */ + static QStringList backwardMigrationKeys(); + private: // saving and loading Account to settings void saveAccountHelper(Account *account, QSettings &settings, bool saveCredentials = true); diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 2a97ecd02..306eeb181 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -102,6 +102,50 @@ namespace { // ---------------------------------------------------------------------------------- +bool Application::configBackwardMigration() +{ + auto accountKeys = AccountManager::backwardMigrationKeys(); + auto folderKeys = FolderMan::backwardMigrationKeys(); + + bool containsFutureData = !accountKeys.isEmpty() || !folderKeys.isEmpty(); + + // Deal with unreadable accounts + if (!containsFutureData) + return true; + + const auto backupFile = ConfigFile().backup(); + + QMessageBox box( + QMessageBox::Warning, + APPLICATION_SHORTNAME, + tr("Some settings were configured in newer versions of this client and " + "use features that are not available in this version.
" + "
" + "Continuing will mean losing these settings.
" + "
" + "The current configuration file was already backed up to %1.") + .arg(backupFile)); + box.addButton(tr("Quit"), QMessageBox::AcceptRole); + auto continueBtn = box.addButton(tr("Continue"), QMessageBox::DestructiveRole); + + box.exec(); + if (box.clickedButton() != continueBtn) { + QTimer::singleShot(0, qApp, SLOT(quit())); + return false; + } + + auto settings = ConfigFile::settingsWithGroup("foo"); + settings->endGroup(); + + // Wipe the keys from the future + for (const auto &badKey : accountKeys) + settings->remove(badKey); + for (const auto &badKey : folderKeys) + settings->remove(badKey); + + return true; +} + Application::Application(int &argc, char **argv) : SharedTools::QtSingleApplication(Theme::instance()->appName(), argc, argv) , _gui(nullptr) @@ -187,8 +231,12 @@ Application::Application(int &argc, char **argv) setupLogging(); setupTranslations(); - // The timeout is initialized with an environment variable, if not, override with the value from the config + if (!configBackwardMigration()) { + return; + } + ConfigFile cfg; + // The timeout is initialized with an environment variable, if not, override with the value from the config if (!AbstractNetworkJob::httpTimeout) AbstractNetworkJob::httpTimeout = cfg.timeout(); diff --git a/src/gui/application.h b/src/gui/application.h index d2b4e4963..5a3b6884a 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -103,6 +103,12 @@ protected slots: private: void setHelp(); + /** + * 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 configBackwardMigration(); + QPointer _gui; Theme *_theme; diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 8d4ba16ff..0e24fffdc 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -42,6 +42,8 @@ #include #include +static const char versionC[] = "version"; + namespace OCC { Q_LOGGING_CATEGORY(lcFolder, "nextcloud.gui.folder", QtInfoMsg) @@ -571,6 +573,8 @@ void Folder::saveToSettings() const } settings->beginGroup(settingsGroup); + // Note: Each of these groups might have a "version" tag, but that's + // currently unused. FolderDefinition::save(*settings, _definition); settings->sync(); @@ -1127,6 +1131,7 @@ void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder) settings.setValue(QLatin1String("paused"), folder.paused); settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles); settings.setValue(QLatin1String("usePlaceholders"), folder.useVirtualFiles); + settings.setValue(QLatin1String(versionC), maxSettingsVersion()); // Happens only on Windows when the explorer integration is enabled. if (!folder.navigationPaneClsid.isNull()) diff --git a/src/gui/folder.h b/src/gui/folder.h index c155ee195..e1d642f3e 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -72,6 +72,9 @@ public: static bool load(QSettings &settings, const QString &alias, FolderDefinition *folder); + /// The highest version in the settings that load() can read + static int maxSettingsVersion() { return 1; } + /// Ensure / as separator and trailing /. static QString prepareLocalPath(const QString &path); diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index a4ba88872..33f201f9c 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -39,6 +39,9 @@ #include #include +static const char versionC[] = "version"; +static const int maxFoldersVersion = 1; + namespace OCC { Q_LOGGING_CATEGORY(lcFolderMan, "nextcloud.gui.folder.manager", QtInfoMsg) @@ -303,6 +306,39 @@ int FolderMan::setupFoldersMigration() return _folderMap.size(); } +QStringList FolderMan::backwardMigrationKeys() +{ + QStringList badKeys; + auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts")); + + auto processSubgroup = [&](const QString &name) { + settings->beginGroup(name); + const int foldersVersion = settings->value(QLatin1String(versionC), 1).toInt(); + if (foldersVersion <= maxFoldersVersion) { + foreach (const auto &folderAlias, settings->childGroups()) { + settings->beginGroup(folderAlias); + const int folderVersion = settings->value(QLatin1String(versionC), 1).toInt(); + if (folderVersion > FolderDefinition::maxSettingsVersion()) { + badKeys.append(settings->group()); + } + settings->endGroup(); + } + } else { + badKeys.append(settings->group()); + } + settings->endGroup(); + }; + + for (const auto &accountId : settings->childGroups()) { + settings->beginGroup(accountId); + processSubgroup("Folders"); + processSubgroup("Multifolders"); + processSubgroup("FoldersWithPlaceholders"); + settings->endGroup(); + } + return badKeys; +} + bool FolderMan::ensureJournalGone(const QString &journalDbFile) { // remove the old journal file diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 8d81ace0d..7264e1f82 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -68,6 +68,12 @@ public: int setupFolders(); int setupFoldersMigration(); + /** + * Returns a list of keys that can't be read because they are from + * future versions. + */ + static QStringList backwardMigrationKeys(); + OCC::Folder::Map map(); /** Adds a folder for an account, ensures the journal is gone and saves it in the settings. diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 03b72ce95..468d207f3 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -411,6 +411,21 @@ QString ConfigFile::excludeFileFromSystem() return fi.absoluteFilePath(); } +QString ConfigFile::backup() const +{ + QString baseFile = configFile(); + QString backupFile = QString("%1.backup_%2").arg(baseFile, QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")); + + // 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!) + if (!QFile::exists(backupFile)) { + QFile f(baseFile); + f.copy(backupFile); + } + return backupFile; +} + QString ConfigFile::configFile() const { return configPath() + Theme::instance()->configFileName(); diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 307daf29c..66dd2fef4 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -48,6 +48,13 @@ public: QString excludeFile(Scope scope) const; static QString excludeFileFromSystem(); // doesn't access config dir + /** + * Creates a backup of the file + * + * Returns the path of the new backup. + */ + QString backup() const; + bool exists(); QString defaultConnection() const;