Skip to content

Commit

Permalink
Merge pull request #6072 from nextcloud/bugfix/legacy-migration
Browse files Browse the repository at this point in the history
Bugfix/legacy migration
  • Loading branch information
mgallien authored Sep 26, 2023
2 parents b2b26b9 + d517cbf commit a9925b4
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 103 deletions.
42 changes: 23 additions & 19 deletions src/gui/accountmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <QDir>
#include <QNetworkAccessManager>
#include <QMessageBox>
#include <QPushButton>

namespace {
constexpr auto urlC = "url";
Expand Down Expand Up @@ -178,26 +179,33 @@ bool AccountManager::restoreFromLegacySettings()
legacyCfgFileGrandParentFolder + legacyCfgFileRelativePath};

for (const auto &configFile : legacyLocations) {
auto oCSettings = std::make_unique<QSettings>(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<QSettings>(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;
Expand All @@ -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();
Expand Down Expand Up @@ -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();
}
Expand Down
217 changes: 137 additions & 80 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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<AccountStatePtr> &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) {
Expand Down
4 changes: 4 additions & 0 deletions src/gui/application.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/gui/folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit a9925b4

Please sign in to comment.