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;