Skip to content

Commit

Permalink
Merge pull request #7807 from nextcloud/bugfix/migration
Browse files Browse the repository at this point in the history
Bugfix migration from legacy clients.
  • Loading branch information
mgallien authored Feb 11, 2025
2 parents c21d706 + 417f692 commit 29858d0
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 117 deletions.
45 changes: 8 additions & 37 deletions src/gui/accountmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ constexpr auto unbrandedRelativeConfigLocationC = "/Nextcloud/nextcloud.cfg";
constexpr auto unbrandedCfgFileNameC = "nextcloud.cfg";

// The maximum versions that this client can read
constexpr auto maxAccountsVersion = 2;
constexpr auto maxAccountVersion = 1;
constexpr auto maxAccountsVersion = 13;
constexpr auto maxAccountVersion = 13;

constexpr auto serverHasValidSubscriptionC = "serverHasValidSubscription";
}
Expand Down Expand Up @@ -150,6 +150,9 @@ void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStr
const auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
const auto accountsVersion = settings->value(QLatin1String(versionC)).toInt();

qCInfo(lcAccountManager) << "Checking for accounts versions.";
qCInfo(lcAccountManager) << "Config accounts version:" << accountsVersion;
qCInfo(lcAccountManager) << "Max accounts Version is set to:" << maxAccountsVersion;
if (accountsVersion <= maxAccountsVersion) {
const auto settingsChildGroups = settings->childGroups();
for (const auto &accountId : settingsChildGroups) {
Expand All @@ -158,6 +161,7 @@ void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStr

if (accountVersion > maxAccountVersion) {
ignoreKeys->append(settings->group());
qCInfo(lcAccountManager) << "Ignoring account" << accountId << "because of version" << accountVersion;
}
settings->endGroup();
}
Expand Down Expand Up @@ -236,41 +240,8 @@ bool AccountManager::restoreFromLegacySettings()
}
}

// 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;
qCInfo(lcAccountManager) << "Migrate: overrideUrl" << cleanOverrideUrl;

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();
const auto cleanOCUrl = oCUrl.endsWith('/') ? oCUrl.chopped(1) : oCUrl;

// in case the urls are equal reset the settings object to read from
// the ownCloud settings object
qCInfo(lcAccountManager) << "Migrate oC config if " << cleanOCUrl << " == " << cleanOverrideUrl << ":"
<< (cleanOCUrl == cleanOverrideUrl ? "Yes" : "No");
if (cleanOCUrl == cleanOverrideUrl) {
qCInfo(lcAccountManager) << "Copy settings" << oCSettings->allKeys().join(", ");
oCSettings->endGroup(); // current accountID group
oCSettings->endGroup(); // accounts group
settings = std::move(oCSettings);
break;
}

oCSettings->endGroup();
}

if (oCSettings) {
oCSettings->endGroup();
}
} else {
qCInfo(lcAccountManager) << "Copy settings" << oCSettings->allKeys().join(", ");
settings = std::move(oCSettings);
}
qCInfo(lcAccountManager) << "Copy settings" << oCSettings->allKeys().join(", ");
settings = std::move(oCSettings);

ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath());
break;
Expand Down
32 changes: 20 additions & 12 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ bool Application::configVersionMigration()
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;
}
Expand Down Expand Up @@ -194,13 +199,16 @@ bool Application::configVersionMigration()
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.";
}
}

Expand Down Expand Up @@ -236,40 +244,40 @@ Application::Application(int &argc, char **argv)
setWindowIcon(_theme->applicationIcon());

if (!ConfigFile().exists()) {
// Migrate from version <= 2.4
setApplicationName(_theme->appNameGUI());
// We need to use the deprecated QDesktopServices::storageLocation because of its Qt4
// behavior of adding "data" to the path
QString oldDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/data/" + organizationName() + "/" + applicationName();
if (oldDir.endsWith('/')) oldDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move.
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(oldDir).isDir()) {
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" << oldDir << "to" << confDir;
qCInfo(lcApplication) << "Migrating old config from" << legacyDir << "to" << confDir;

if (!QFile::rename(oldDir, confDir)) {
qCWarning(lcApplication) << "Failed to move the old config directory to its new location (" << oldDir << "to" << confDir << ")";
if (!QFile::rename(legacyDir, confDir)) {
qCWarning(lcApplication) << "Failed to move the old config directory to its new location (" << legacyDir << "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);
const QStringList filesList = QDir(legacyDir).entryList(QDir::Files);
qCInfo(lcApplication) << "Will move the individual files" << filesList;
for (const auto &name : filesList) {
if (!QFile::rename(oldDir + "/" + name, confDir + "/" + name)) {
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, oldDir);
QFile::link(confDir, legacyDir);
#endif
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ class FolderDefinition
* (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
* Version 13: available in oC client 5.2.0 and 5.3.1
*/
static int maxSettingsVersion() { return 5; }
static int maxSettingsVersion() { return 13; }

/// Ensure / as separator and trailing /.
static QString prepareLocalPath(const QString &path);
Expand Down
146 changes: 79 additions & 67 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
namespace {
constexpr auto settingsAccountsC = "Accounts";
constexpr auto settingsFoldersC = "Folders";
constexpr auto settingsFoldersWithPlaceholdersC = "FoldersWithPlaceholders";
constexpr auto settingsVersionC = "version";
constexpr auto maxFoldersVersion = 1;

Expand Down Expand Up @@ -388,16 +389,19 @@ void FolderMan::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringLi
auto processSubgroup = [&](const QString &name) {
settings->beginGroup(name);
const auto foldersVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
qCInfo(lcFolderMan) << "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(lcFolderMan) << "Ignoring folder:" << folderAlias << "version:" << folderVersion;
ignoreKeys->append(settings->group());
}
settings->endGroup();
}
} else {
qCInfo(lcFolderMan) << "Ignoring group:" << name << "version:" << foldersVersion;
deleteKeys->append(settings->group());
}
settings->endGroup();
Expand Down Expand Up @@ -523,89 +527,97 @@ void FolderMan::setupLegacyFolder(const QString &fileNamePath, AccountState *acc
return;
}

settings.beginGroup(settingsAccountsC);
qCDebug(lcFolderMan) << "try to migrate accountId:" << accountState->account()->id();
settings.beginGroup(accountState->account()->id());
settings.beginGroup(settingsFoldersC);

if (settings.childGroups().isEmpty()) {
qCDebug(lcFolderMan) << "there are no legacy folders for accountId:" << accountState->account()->id();
return;
}

const auto childGroups = settings.childGroups();
for (const auto &alias : childGroups) {
settings.beginGroup(alias);
qCDebug(lcFolderMan) << "try to migrate folder alias:" << alias;

const auto path = settings.value(QLatin1String("localPath")).toString();
const auto targetPath = settings.value(QLatin1String("targetPath")).toString();
const auto journalPath = settings.value(QLatin1String("journalPath")).toString();
const auto paused = settings.value(QLatin1String("paused"), false).toBool();
const auto ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), false).toBool();

if (path.isEmpty()) {
qCDebug(lcFolderMan) << "localPath is empty";
settings.endGroup();
continue;
}

if (targetPath.isEmpty()) {
qCDebug(lcFolderMan) << "targetPath is empty";
settings.endGroup();
continue;
auto migrateFoldersGroup = [&](const QString &folderGroupName) {
const auto childGroups = settings.childGroups();
if (childGroups.isEmpty()) {
qCDebug(lcFolderMan) << "There are no" << folderGroupName << "to migrate from account" << accountState->account()->id();
return;
}
for (const auto &alias : childGroups) {
settings.beginGroup(alias);
qCDebug(lcFolderMan) << "try to migrate" << folderGroupName << "alias:" << alias;

const auto path = settings.value(QLatin1String("localPath")).toString();
const auto targetPath = settings.value(QLatin1String("targetPath")).toString();
const auto journalPath = settings.value(QLatin1String("journalPath")).toString();
const auto paused = settings.value(QLatin1String("paused"), false).toBool();
const auto ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), false).toBool();

if (path.isEmpty()) {
qCDebug(lcFolderMan) << "localPath is empty";
settings.endGroup();
continue;
}

if (journalPath.isEmpty()) {
qCDebug(lcFolderMan) << "journalPath is empty";
settings.endGroup();
continue;
}
if (targetPath.isEmpty()) {
qCDebug(lcFolderMan) << "targetPath is empty";
settings.endGroup();
continue;
}

FolderDefinition folderDefinition;
folderDefinition.alias = alias;
folderDefinition.localPath = path;
folderDefinition.targetPath = targetPath;
folderDefinition.journalPath = journalPath;
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles;

if (const auto folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>())) {
auto ok = true;
auto legacyBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
&ok);
if (!ok) {
qCInfo(lcFolderMan) << "There was a problem retrieving the database selective sync for " << folder;
if (journalPath.isEmpty()) {
qCDebug(lcFolderMan) << "journalPath is empty";
settings.endGroup();
continue;
}

legacyBlacklist << settings.value(QLatin1String("blackList")).toStringList();
if (!legacyBlacklist.isEmpty()) {
qCInfo(lcFolderMan) << "Legacy selective sync list found:" << legacyBlacklist;
for (const auto &legacyFolder : legacyBlacklist) {
folder->migrateBlackListPath(legacyFolder);
qCDebug(lcFolderMan) << folderGroupName << "located at" << path;

FolderDefinition folderDefinition;
folderDefinition.alias = alias;
folderDefinition.localPath = path;
folderDefinition.targetPath = targetPath;
folderDefinition.journalPath = journalPath;
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles;

if (const auto folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>())) {
auto ok = true;
auto legacyBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
&ok);
if (!ok) {
qCInfo(lcFolderMan) << "There was a problem retrieving the database selective sync for " << folder;
}

legacyBlacklist << settings.value(QLatin1String("blackList")).toStringList();
if (!legacyBlacklist.isEmpty()) {
qCInfo(lcFolderMan) << "Legacy selective sync list found:" << legacyBlacklist;
for (const auto &legacyFolder : legacyBlacklist) {
folder->migrateBlackListPath(legacyFolder);
}
settings.remove(QLatin1String("blackList"));
}
settings.remove(QLatin1String("blackList"));
}

folder->saveToSettings();
folder->saveToSettings();

qCInfo(lcFolderMan) << "Migrated!" << folder->path();
settings.sync();
qCInfo(lcFolderMan) << "Migrated!" << folder->path();
settings.sync();

if (!folder) {
continue;
}
if (!folder) {
continue;
}

scheduleFolder(folder);
emit folderSyncStateChange(folder);
scheduleFolder(folder);
emit folderSyncStateChange(folder);
}
settings.endGroup(); // folder alias
}
};

settings.endGroup();
}
settings.beginGroup(settingsAccountsC);
qCDebug(lcFolderMan) << "try to migrate accountId:" << accountState->account()->id();
settings.beginGroup(accountState->account()->id());

settings.beginGroup(settingsFoldersWithPlaceholdersC);
migrateFoldersGroup(settingsFoldersWithPlaceholdersC);
settings.endGroup();

settings.beginGroup(settingsFoldersC);
migrateFoldersGroup(settingsFoldersC);
settings.endGroup();

settings.endGroup();
settings.endGroup();
return;
}

Expand Down
17 changes: 17 additions & 0 deletions src/libsync/configfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,17 @@ QString ConfigFile::excludeFileFromSystem()
return fi.absoluteFilePath();
}

void OCC::ConfigFile::cleanUpdaterConfiguration()
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.beginGroup("Updater");
settings.remove("autoUpdateAttempted");
settings.remove("updateTargetVersion");
settings.remove("updateTargetVersionString");
settings.remove("updateAvailable");
settings.sync();
}

QString ConfigFile::backup(const QString &fileName) const
{
const QString baseFilePath = configPath() + fileName;
Expand Down Expand Up @@ -1260,6 +1271,12 @@ void ConfigFile::setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles)
const auto userList = cfg.excludeFile(ConfigFile::UserScope);
const auto legacyList = cfg.excludeFile(ConfigFile::LegacyScope);

if (Theme::instance()->isBranded() && QFile::exists(systemList) && QFile::copy(systemList, userList)) {
qCInfo(lcConfigFile) << "Overwriting user list" << userList << "with system list" << systemList;
excludedFiles.addExcludeFilePath(systemList);
return;
}

if (!QFile::exists(userList)) {
qCInfo(lcConfigFile) << "User defined ignore list does not exist:" << userList;

Expand Down
2 changes: 2 additions & 0 deletions src/libsync/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
[[nodiscard]] QString excludeFile(Scope scope) const;
static QString excludeFileFromSystem(); // doesn't access config dir

void cleanUpdaterConfiguration();

/**
* Creates a backup of any given fileName in the config folder
*
Expand Down

0 comments on commit 29858d0

Please sign in to comment.