Skip to content

Commit bdde6f2

Browse files
committed
wip: add Migration class.
Signed-off-by: Camila Ayres <[email protected]>
1 parent a36d65d commit bdde6f2

File tree

3 files changed

+374
-1
lines changed

3 files changed

+374
-1
lines changed

src/libsync/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ ENDIF(NOT APPLE)
208208

209209
find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS WebSockets Xml Sql Gui Svg Widgets)
210210

211-
add_library(nextcloudsync SHARED ${libsync_SRCS})
211+
add_library(nextcloudsync SHARED ${libsync_SRCS}
212+
migration.h migration.cpp)
212213
add_library(Nextcloud::sync ALIAS nextcloudsync)
213214

214215
target_link_libraries(nextcloudsync

src/libsync/migration.cpp

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: GPL-2.0-or-later
4+
*/
5+
6+
#include "migration.h"
7+
8+
#include "theme.h"
9+
#include "config.h"
10+
#include "configfile.h"
11+
#include "common/utility.h"
12+
#include "version.h"
13+
#include "gui/folder.h"
14+
15+
#include <QSettings>
16+
#include <QDir>
17+
#include <QFile>
18+
#include <QFileInfo>
19+
#include <QLoggingCategory>
20+
#include <QStandardPaths>
21+
#include <QVersionNumber>
22+
23+
namespace {
24+
constexpr auto legacyRelativeConfigLocationC = "/ownCloud/owncloud.cfg";
25+
constexpr auto legacyCfgFileNameC = "owncloud.cfg";
26+
constexpr auto unbrandedRelativeConfigLocationC = "/Nextcloud/nextcloud.cfg";
27+
constexpr auto unbrandedCfgFileNameC = "nextcloud.cfg";
28+
29+
constexpr auto accountsC = "Accounts";
30+
constexpr auto versionC = "version";
31+
32+
constexpr auto maxAccountsVersion = 13;
33+
constexpr auto maxAccountVersion = 13;
34+
35+
constexpr auto settingsAccountsC = "Accounts";
36+
constexpr auto settingsFoldersC = "Folders";
37+
constexpr auto settingsFoldersWithPlaceholdersC = "FoldersWithPlaceholders";
38+
constexpr auto settingsVersionC = "version";
39+
constexpr auto maxFoldersVersion = 1;
40+
}
41+
42+
namespace OCC {
43+
44+
Q_LOGGING_CATEGORY(lcMigration, "nextcloud.gui.migration", QtInfoMsg);
45+
46+
Migration::Migration()
47+
{}
48+
49+
QString Migration::discoveredLegacyConfigPath() const
50+
{
51+
return _discoveredLegacyConfigPath;
52+
}
53+
54+
void Migration::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath)
55+
{
56+
if (_discoveredLegacyConfigPath == discoveredLegacyConfigPath) {
57+
return;
58+
}
59+
60+
_discoveredLegacyConfigPath = discoveredLegacyConfigPath;
61+
}
62+
63+
QString Migration::discoveredLegacyConfigFile() const
64+
{
65+
return _discoveredLegacyConfigFile;
66+
}
67+
68+
void Migration::setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile)
69+
{
70+
if (_discoveredLegacyConfigFile == discoveredLegacyConfigFile) {
71+
return;
72+
}
73+
74+
_discoveredLegacyConfigFile = discoveredLegacyConfigFile;
75+
}
76+
77+
void Migration::findLegacyClientConfigFile()
78+
{
79+
qCInfo(lcMigration) << "Migrate: findLegacyClientConfigFile" << Theme::instance()->appName();
80+
81+
// Legacy settings used QDesktopServices to get the location for the config folder in 2.4 and before
82+
const auto legacyStandardPaths = QString(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/data"));
83+
const auto legacyStandardPathsParentFolder = legacyStandardPaths.left(legacyStandardPaths.lastIndexOf('/'));
84+
85+
// 2.5+ (rest of 2.x series)
86+
const auto standardPaths = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation);
87+
const auto standardPathsParentFolder = standardPaths.left(standardPaths.lastIndexOf('/'));
88+
89+
// Now try the locations we use today
90+
const auto fullLegacyCfgFile = QDir::fromNativeSeparators(ConfigFile().configFile());
91+
const auto legacyCfgFileParentFolder = fullLegacyCfgFile.left(fullLegacyCfgFile.lastIndexOf('/'));
92+
const auto legacyCfgFileGrandParentFolder = legacyCfgFileParentFolder.left(legacyCfgFileParentFolder.lastIndexOf('/'));
93+
94+
const auto legacyCfgFileNamePath = QString(QStringLiteral("/") + legacyCfgFileNameC);
95+
const auto legacyCfgFileRelativePath = QString(legacyRelativeConfigLocationC);
96+
97+
auto legacyLocations = QVector<QString>{legacyStandardPathsParentFolder + legacyCfgFileRelativePath,
98+
standardPathsParentFolder + legacyCfgFileRelativePath,
99+
legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath,
100+
legacyCfgFileParentFolder + legacyCfgFileNamePath};
101+
102+
if (Theme::instance()->isBranded()) {
103+
const auto unbrandedCfgFileNamePath = QString(QStringLiteral("/") + unbrandedCfgFileNameC);
104+
const auto unbrandedCfgFileRelativePath = QString(unbrandedRelativeConfigLocationC);
105+
const auto brandedLegacyCfgFilePath = QString(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/") + APPLICATION_SHORTNAME + QStringLiteral("/"));
106+
const auto brandedLegacyCfgFile = QString(APPLICATION_CONFIG_NAME + QStringLiteral(".cfg"));
107+
legacyLocations.append({brandedLegacyCfgFilePath + brandedLegacyCfgFile,
108+
standardPathsParentFolder + unbrandedCfgFileRelativePath,
109+
legacyCfgFileParentFolder + unbrandedCfgFileNamePath,
110+
legacyCfgFileGrandParentFolder + unbrandedCfgFileRelativePath});
111+
}
112+
113+
qCDebug(lcMigration) << "Looking for existing config files at:" << legacyLocations;
114+
115+
for (const auto &configFile : legacyLocations) {
116+
auto oCSettings = std::make_unique<QSettings>(configFile, QSettings::IniFormat);
117+
if (oCSettings->status() != QSettings::Status::NoError) {
118+
qCInfo(lcMigration) << "Error reading legacy configuration file" << oCSettings->status();
119+
break;
120+
}
121+
122+
const QFileInfo configFileInfo(configFile);
123+
if (!configFileInfo.exists() || !configFileInfo.isReadable()) {
124+
qCInfo(lcMigration()) << "Migrate: could not read old config " << configFile;
125+
continue;
126+
}
127+
128+
qCInfo(lcMigration) << "Migrate: old config file" << configFile;
129+
setDiscoveredLegacyConfigFile(configFileInfo.filePath());
130+
setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath());
131+
break;
132+
}
133+
}
134+
135+
bool Migration::isUpgrade() const
136+
{
137+
return QVersionNumber::fromString(MIRALL_VERSION_STRING) > QVersionNumber::fromString(ConfigFile().clientVersionString());
138+
}
139+
140+
bool Migration::isDowngrade() const
141+
{
142+
return QVersionNumber::fromString(ConfigFile().clientVersionString()) > QVersionNumber::fromString(MIRALL_VERSION_STRING);
143+
}
144+
145+
QString Migration::configFileToRestore() const
146+
{
147+
const auto legacyConfigFile = discoveredLegacyConfigFile();
148+
if (legacyConfigFile.isEmpty()) {
149+
return ConfigFile().configFile();
150+
}
151+
152+
return legacyConfigFile;
153+
}
154+
155+
QString Migration::backup(const QString &fileName) const
156+
{
157+
const QString baseFilePath = ConfigFile().configPath() + fileName;
158+
auto versionString = ConfigFile().clientVersionString();
159+
160+
if (!versionString.isEmpty()) {
161+
versionString.prepend('_');
162+
}
163+
164+
QString backupFile =
165+
QStringLiteral("%1.backup_%2%3")
166+
.arg(baseFilePath)
167+
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"))
168+
.arg(versionString);
169+
170+
// If this exact file already exists it's most likely that a backup was
171+
// already done. (two backup calls directly after each other, potentially
172+
// even with source alterations in between!)
173+
// QFile does not overwrite backupFile
174+
if(!QFile::copy(baseFilePath, backupFile)) {
175+
qCWarning(lcMigration) << "Failed to create a backup of the config file" << baseFilePath;
176+
}
177+
178+
return backupFile;
179+
}
180+
181+
QStringList Migration::backupConfigFiles() const
182+
{
183+
// 'Launch on system startup' defaults to true > 3.11.x
184+
const auto theme = Theme::instance();
185+
ConfigFile().setLaunchOnSystemStartup(ConfigFile().launchOnSystemStartup());
186+
Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), ConfigFile().launchOnSystemStartup());
187+
188+
// default is now off to displaying dialog warning user of too many files deletion
189+
ConfigFile().setPromptDeleteFiles(false);
190+
191+
// back up all old config files
192+
QStringList backupFilesList;
193+
QDir configDir(ConfigFile().configPath());
194+
const auto anyConfigFileNameList = configDir.entryInfoList({"*.cfg"}, QDir::Files);
195+
for (const auto &oldConfig : anyConfigFileNameList) {
196+
const auto oldConfigFileName = oldConfig.fileName();
197+
const auto oldConfigFilePath = oldConfig.filePath();
198+
const auto newConfigFileName = ConfigFile().configFile();
199+
backupFilesList.append(backup(oldConfigFileName));
200+
if (oldConfigFilePath != newConfigFileName) {
201+
if (!QFile::rename(oldConfigFilePath, newConfigFileName)) {
202+
qCWarning(lcMigration) << "Failed to rename configuration file from" << oldConfigFilePath << "to" << newConfigFileName;
203+
}
204+
}
205+
}
206+
207+
return backupFilesList;
208+
}
209+
210+
void Migration::accountbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys)
211+
{
212+
const auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
213+
const auto accountsVersion = settings->value(QLatin1String(versionC)).toInt();
214+
215+
qCInfo(lcMigration) << "Checking for accounts versions.";
216+
qCInfo(lcMigration) << "Config accounts version:" << accountsVersion;
217+
qCInfo(lcMigration) << "Max accounts Version is set to:" << maxAccountsVersion;
218+
if (accountsVersion <= maxAccountsVersion) {
219+
const auto settingsChildGroups = settings->childGroups();
220+
for (const auto &accountId : settingsChildGroups) {
221+
settings->beginGroup(accountId);
222+
const auto accountVersion = settings->value(QLatin1String(versionC), 1).toInt();
223+
224+
if (accountVersion > maxAccountVersion) {
225+
ignoreKeys->append(settings->group());
226+
qCInfo(lcMigration) << "Ignoring account" << accountId << "because of version" << accountVersion;
227+
}
228+
settings->endGroup();
229+
}
230+
} else {
231+
deleteKeys->append(settings->group());
232+
}
233+
}
234+
235+
void Migration::folderbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys)
236+
{
237+
auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts"));
238+
239+
auto processSubgroup = [&](const QString &name) {
240+
settings->beginGroup(name);
241+
const auto foldersVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
242+
qCInfo(lcMigration) << "FolderDefinition::maxSettingsVersion:" << FolderDefinition::maxSettingsVersion();
243+
if (foldersVersion <= maxFoldersVersion) {
244+
for (const auto &folderAlias : settings->childGroups()) {
245+
settings->beginGroup(folderAlias);
246+
const auto folderVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
247+
if (folderVersion > FolderDefinition::maxSettingsVersion()) {
248+
qCInfo(lcMigration) << "Ignoring folder:" << folderAlias << "version:" << folderVersion;
249+
ignoreKeys->append(settings->group());
250+
}
251+
settings->endGroup();
252+
}
253+
} else {
254+
qCInfo(lcMigration) << "Ignoring group:" << name << "version:" << foldersVersion;
255+
deleteKeys->append(settings->group());
256+
}
257+
settings->endGroup();
258+
};
259+
260+
const auto settingsChildGroups = settings->childGroups();
261+
for (const auto &accountId : settingsChildGroups) {
262+
settings->beginGroup(accountId);
263+
processSubgroup("Folders");
264+
processSubgroup("Multifolders");
265+
processSubgroup("FoldersWithPlaceholders");
266+
settings->endGroup();
267+
}
268+
}
269+
270+
bool Migration::makeConfigSettingsBackwardCompatible() const
271+
{
272+
ConfigFile configFile;
273+
const auto didVersionChanged = configFile.isUpgrade() || configFile.isDowngrade();
274+
if (!didVersionChanged) {
275+
qCInfo(lcMigration) << "No upgrade or downgrade detected.";
276+
return true;
277+
}
278+
279+
configFile.cleanUpdaterConfiguration();
280+
281+
QStringList deleteKeys, ignoreKeys;
282+
// accountbackwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys);
283+
// folderbackwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys);
284+
if (!didVersionChanged && !(!deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && didVersionChanged))) {
285+
qCInfo(lcMigration) << "There are no settings to delete or to ignore. No need to change the current config file.";
286+
return true;
287+
}
288+
289+
const auto isDeleteKeysEmpty = deleteKeys.isEmpty();
290+
if (const auto backupFilesList = configFile.backupConfigFiles();
291+
configFile.showConfigBackupWarning()
292+
&& backupFilesList.size() > 0
293+
/*&& !confirmConfigChangesOrQuitApp(isDeleteKeysEmpty, backupFilesList)*/) {
294+
return false;
295+
}
296+
297+
if (!isDeleteKeysEmpty) {
298+
auto settings = configFile.settingsWithGroup("foo");
299+
settings->endGroup();
300+
301+
// Wipe confusing keys from the future, ignore the others
302+
for (const auto &badKey : std::as_const(deleteKeys)) {
303+
settings->remove(badKey);
304+
qCDebug(lcMigration) << "Migration: removed" << badKey << "key from settings.";
305+
}
306+
}
307+
308+
configFile.setClientVersionString(MIRALL_VERSION_STRING);
309+
qCDebug(lcMigration) << "Client version changed to" << configFile.clientVersionString();
310+
311+
return true;
312+
}
313+
}

src/libsync/migration.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#ifndef MIGRATION_H
2+
#define MIGRATION_H
3+
4+
#include <QString>
5+
6+
namespace OCC {
7+
8+
class Migration
9+
{
10+
public:
11+
Migration();
12+
/**
13+
* Looks for config files with different names from older client versions
14+
* in different locations
15+
*
16+
* Returns the found config file path found.
17+
*/
18+
void findLegacyClientConfigFile();
19+
20+
/**
21+
* Maybe a newer version of the client was used with this config file: if so, backup.
22+
* Return backup files list.
23+
*/
24+
[[nodiscard]] QStringList backupConfigFiles() const;
25+
[[nodiscard]] bool isUpgrade() const;
26+
[[nodiscard]] bool isDowngrade() const;
27+
[[nodiscard]] QString configFileToRestore() const;
28+
[[nodiscard]] QString findLegacyConfigFile() const;
29+
[[nodiscard]] bool makeConfigSettingsBackwardCompatible() const;
30+
31+
[[nodiscard]] QString backup(const QString &fileName) const;
32+
33+
/**
34+
* Returns the list of settings keys that can't be read because
35+
* they are from the future.
36+
*/
37+
void accountbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys);
38+
39+
/**
40+
* Returns a list of keys that can't be read because they are from
41+
* future versions.
42+
*/
43+
void folderbackwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys);
44+
45+
46+
/// Set during first time migration of legacy accounts in AccountManager
47+
[[nodiscard]] QString discoveredLegacyConfigPath() const;
48+
void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath);
49+
[[nodiscard]] QString discoveredLegacyConfigFile() const;
50+
void setDiscoveredLegacyConfigFile(const QString &discoveredLegacyConfigFile);
51+
52+
private:
53+
QString _confDir;
54+
QString _discoveredLegacyConfigPath;
55+
QString _discoveredLegacyConfigFile;
56+
57+
};
58+
}
59+
#endif // MIGRATION_H

0 commit comments

Comments
 (0)