Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix account migration from legacy desktop clients (again) #5640

Merged
merged 12 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/gui/accountmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ constexpr auto userC = "user";
constexpr auto displayNameC = "displayName";
constexpr auto httpUserC = "http_user";
constexpr auto davUserC = "dav_user";
constexpr auto webflowUserC = "webflow_user";
constexpr auto shibbolethUserC = "shibboleth_shib_user";
constexpr auto caCertsKeyC = "CaCertificates";
constexpr auto accountsC = "Accounts";
Expand Down Expand Up @@ -70,7 +71,7 @@ AccountManager *AccountManager::instance()
return &instance;
}

bool AccountManager::restore(bool alsoRestoreLegacySettings)
AccountManager::AccountsRestoreResult AccountManager::restore(const bool alsoRestoreLegacySettings)
{
QStringList skipSettingsKeys;
backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys);
Expand All @@ -79,21 +80,22 @@ bool AccountManager::restore(bool alsoRestoreLegacySettings)
if (settings->status() != QSettings::NoError || !settings->isWritable()) {
qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName()
<< settings->status();
return false;
return AccountsRestoreFailure;
}

if (skipSettingsKeys.contains(settings->group())) {
// Should not happen: bad container keys should have been deleted
qCWarning(lcAccountManager) << "Accounts structure is too new, ignoring";
return true;
return AccountsRestoreSuccessWithSkipped;
}

// If there are no accounts, check the old format.
if (settings->childGroups().isEmpty() && !settings->contains(QLatin1String(versionC)) && alsoRestoreLegacySettings) {
restoreFromLegacySettings();
return true;
return AccountsRestoreSuccessFromLegacyVersion;
}

auto result = AccountsRestoreSuccess;
const auto settingsChildGroups = settings->childGroups();
for (const auto &accountId : settingsChildGroups) {
settings->beginGroup(accountId);
Expand All @@ -111,11 +113,12 @@ bool AccountManager::restore(bool alsoRestoreLegacySettings)
} else {
qCInfo(lcAccountManager) << "Account" << accountId << "is too new, ignoring";
_additionalBlockedAccountIds.insert(accountId);
result = AccountsRestoreSuccessWithSkipped;
}
settings->endGroup();
}

return true;
return result;
}

void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys)
Expand Down Expand Up @@ -220,6 +223,7 @@ bool AccountManager::restoreFromLegacySettings()
settings = std::move(oCSettings);
}

ConfigFile::setDiscoveredLegacyConfigPath(configFileInfo.canonicalPath());
break;
} else {
qCInfo(lcAccountManager) << "Migrate: could not read old config " << configFile;
Expand Down Expand Up @@ -359,6 +363,8 @@ AccountPtr AccountManager::loadAccountHelper(QSettings &settings)
authType = httpAuthTypeC;
} else if (settings.contains(QLatin1String(shibbolethUserC))) {
authType = shibbolethAuthTypeC;
} else if (settings.contains(webflowUserC)) {
authType = webflowAuthTypeC;
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/gui/accountmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ class AccountManager : public QObject
{
Q_OBJECT
public:
enum AccountsRestoreResult {
AccountsRestoreFailure = 0,
AccountsRestoreSuccess,
AccountsRestoreSuccessFromLegacyVersion,
AccountsRestoreSuccessWithSkipped
};
Q_ENUM (AccountsRestoreResult);

static AccountManager *instance();
~AccountManager() override = default;

Expand All @@ -41,7 +49,7 @@ class AccountManager : public QObject
* Returns false if there was an error reading the settings,
* but note that settings not existing is not an error.
*/
bool restore(bool alsoRestoreLegacySettings = true);
AccountsRestoreResult restore(const bool alsoRestoreLegacySettings = true);

/**
* Add this account in the list of saved accounts.
Expand Down
8 changes: 6 additions & 2 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,16 @@ Application::Application(int &argc, char **argv)

connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage);

if (!AccountManager::instance()->restore(cfg.overrideServerUrl().isEmpty())) {
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 (!AccountManager::instance()->restore(cfg.overrideServerUrl().isEmpty())) {
if (accountsRestoreResult = AccountManager::instance()->restore(tryMigrate);
accountsRestoreResult == AccountManager::AccountsRestoreFailure) {
qCCritical(lcApplication) << "Could not read the account settings, quitting";
QMessageBox::critical(
nullptr,
Expand Down
92 changes: 45 additions & 47 deletions src/gui/folderman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ int FolderMan::setupFolders()
auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts"));
const auto accountsWithSettings = settings->childGroups();
if (accountsWithSettings.isEmpty()) {
int r = setupFoldersMigration();
if (r > 0) {
const auto migratedFoldersCount = setupFoldersMigration();
if (migratedFoldersCount > 0) {
AccountManager::instance()->save(false); // don't save credentials, they had not been loaded from keychain
}
return r;
return migratedFoldersCount;
}

qCInfo(lcFolderMan) << "Setup folders from settings file";
Expand All @@ -197,7 +197,7 @@ int FolderMan::setupFolders()

// The "backwardsCompatible" flag here is related to migrating old
// database locations
auto process = [&](const QString &groupName, bool backwardsCompatible, bool foldersWithPlaceholders) {
auto process = [&](const QString &groupName, const bool backwardsCompatible, const bool foldersWithPlaceholders) {
settings->beginGroup(groupName);
if (skipSettingsKeys.contains(settings->group())) {
// Should not happen: bad container keys should have been deleted
Expand Down Expand Up @@ -284,8 +284,8 @@ void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account,
qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode;
}

Folder *f = addFolderInternal(folderDefinition, account.data(), std::move(vfs));
f->saveToSettings();
const auto folder = addFolderInternal(folderDefinition, account.data(), std::move(vfs));
folder->saveToSettings();

continue;
}
Expand Down Expand Up @@ -316,26 +316,25 @@ void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account,
qFatal("Could not load plugin");
}

Folder *f = addFolderInternal(std::move(folderDefinition), account.data(), std::move(vfs));
if (f) {
if (const auto folder = addFolderInternal(std::move(folderDefinition), account.data(), std::move(vfs))) {
if (switchToVfs) {
f->switchToVirtualFiles();
folder->switchToVirtualFiles();
claucambra marked this conversation as resolved.
Show resolved Hide resolved
}
// Migrate the old "usePlaceholders" setting to the root folder pin state
if (settings.value(QLatin1String(settingsVersionC), 1).toInt() == 1
&& settings.value(QLatin1String("usePlaceholders"), false).toBool()) {
qCInfo(lcFolderMan) << "Migrate: From usePlaceholders to PinState::OnlineOnly";
f->setRootPinState(PinState::OnlineOnly);
folder->setRootPinState(PinState::OnlineOnly);
}

// Migration: Mark folders that shall be saved in a backwards-compatible way
if (backwardsCompatible)
f->setSaveBackwardsCompatible(true);
folder->setSaveBackwardsCompatible(true);
if (foldersWithPlaceholders)
f->setSaveInFoldersWithPlaceholders();
folder->setSaveInFoldersWithPlaceholders();

scheduleFolder(f);
emit folderSyncStateChange(f);
scheduleFolder(folder);
emit folderSyncStateChange(folder);
}
}
settings.endGroup();
Expand All @@ -348,20 +347,24 @@ int FolderMan::setupFoldersMigration()
QDir storageDir(cfg.configPath());
_folderConfigPath = cfg.configPath();

qCInfo(lcFolderMan) << "Setup folders from " << _folderConfigPath << "(migration)";
const auto legacyConfigPath = ConfigFile::discoveredLegacyConfigPath();
const auto configPath = legacyConfigPath.isEmpty() ? _folderConfigPath : legacyConfigPath;

QDir dir(_folderConfigPath);
qCInfo(lcFolderMan) << "Setup folders from " << configPath << "(migration)";

QDir dir(configPath);
//We need to include hidden files just in case the alias starts with '.'
dir.setFilter(QDir::Files | QDir::Hidden);
const auto list = dir.entryList();
const auto dirFiles = dir.entryList();

// Normally there should be only one account when migrating.
AccountState *accountState = AccountManager::instance()->accounts().value(0).data();
for (const auto &alias : list) {
Folder *f = setupFolderFromOldConfigFile(alias, accountState);
if (f) {
scheduleFolder(f);
emit folderSyncStateChange(f);
// Normally there should be only one account when migrating. TODO: Change
claucambra marked this conversation as resolved.
Show resolved Hide resolved
const auto accountState = AccountManager::instance()->accounts().value(0).data();
for (const auto &fileName : dirFiles) {
const auto fullFilePath = dir.filePath(fileName);
const auto folder = setupFolderFromOldConfigFile(fullFilePath, accountState);
if (folder) {
scheduleFolder(folder);
emit folderSyncStateChange(folder);
}
}

Expand All @@ -377,11 +380,11 @@ void FolderMan::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringLi

auto processSubgroup = [&](const QString &name) {
settings->beginGroup(name);
const int foldersVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
const auto foldersVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
if (foldersVersion <= maxFoldersVersion) {
foreach (const auto &folderAlias, settings->childGroups()) {
for (const auto &folderAlias : settings->childGroups()) {
settings->beginGroup(folderAlias);
const int folderVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
const auto folderVersion = settings->value(QLatin1String(settingsVersionC), 1).toInt();
if (folderVersion > FolderDefinition::maxSettingsVersion()) {
ignoreKeys->append(settings->group());
}
Expand Down Expand Up @@ -478,39 +481,35 @@ QString FolderMan::unescapeAlias(const QString &alias)
return a;
}

// filename is the name of the file only, it does not include
// the configuration directory path
// WARNING: Do not remove this code, it is used for predefined/automated deployments (2016)
Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountState *accountState)
Folder *FolderMan::setupFolderFromOldConfigFile(const QString &fileNamePath, AccountState *accountState)
{
Folder *folder = nullptr;

qCInfo(lcFolderMan) << " ` -> setting up:" << file;
QString escapedAlias(file);
qCInfo(lcFolderMan) << " ` -> setting up:" << fileNamePath;
QString escapedFileNamePath(fileNamePath);
// check the unescaped variant (for the case when the filename comes out
// of the directory listing). If the file does not exist, escape the
// file and try again.
QFileInfo cfgFile(_folderConfigPath, file);
QFileInfo cfgFile(fileNamePath);

if (!cfgFile.exists()) {
// try the escaped variant.
escapedAlias = escapeAlias(file);
cfgFile.setFile(_folderConfigPath, escapedAlias);
escapedFileNamePath = escapeAlias(fileNamePath);
cfgFile.setFile(_folderConfigPath, escapedFileNamePath);
}
if (!cfgFile.isReadable()) {
qCWarning(lcFolderMan) << "Cannot read folder definition for alias " << cfgFile.filePath();
return folder;
return nullptr;
}

QSettings settings(_folderConfigPath + QLatin1Char('/') + escapedAlias, QSettings::IniFormat);
QSettings settings(escapedFileNamePath, QSettings::IniFormat);
qCInfo(lcFolderMan) << " -> file path: " << settings.fileName();

// Check if the filename is equal to the group setting. If not, use the group
// name as an alias.
const auto groups = settings.childGroups();
if (groups.isEmpty()) {
qCWarning(lcFolderMan) << "empty file:" << cfgFile.filePath();
return folder;
return nullptr;
}

if (!accountState) {
Expand Down Expand Up @@ -566,8 +565,7 @@ Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountStat
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles;

folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>());
if (folder) {
if (const auto folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>())) {
const auto blackList = settings.value(QLatin1String("blackList")).toStringList();
if (!blackList.empty()) {
//migrate settings
Expand All @@ -578,11 +576,10 @@ Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountStat
}

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

if (folder) {
qCInfo(lcFolderMan) << "Migrated!" << folder;
settings.sync();

return folder;
}

Expand All @@ -592,7 +589,8 @@ Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountStat
settings.endGroup();
settings.endGroup();
}
return folder;

return nullptr;
}

void FolderMan::slotFolderSyncPaused(Folder *f, bool paused)
Expand Down
19 changes: 17 additions & 2 deletions src/libsync/configfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ namespace chrono = std::chrono;

Q_LOGGING_CATEGORY(lcConfigFile, "nextcloud.sync.configfile", QtInfoMsg)

QString ConfigFile::_confDir = QString();
bool ConfigFile::_askedUser = false;
QString ConfigFile::_confDir = {};
QString ConfigFile::_discoveredLegacyConfigPath = {};

static chrono::milliseconds millisecondsValue(const QSettings &setting, const char *key,
chrono::milliseconds defaultValue)
Expand Down Expand Up @@ -1156,4 +1156,19 @@ void ConfigFile::setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles)
excludedFiles.addExcludeFilePath(userList);
}
}

QString ConfigFile::discoveredLegacyConfigPath()
{
return _discoveredLegacyConfigPath;
}

void ConfigFile::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath)
{
if (_discoveredLegacyConfigPath == discoveredLegacyConfigPath) {
return;
}

_discoveredLegacyConfigPath = discoveredLegacyConfigPath;
}

}
7 changes: 5 additions & 2 deletions src/libsync/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
/// Add the system and user exclude file path to the ExcludedFiles instance.
static void setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles);

/// Set during first time migration of legacy accounts in AccountManager
[[nodiscard]] static QString discoveredLegacyConfigPath();
static void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath);

protected:
[[nodiscard]] QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const;
void storeData(const QString &group, const QString &key, const QVariant &value);
Expand All @@ -236,9 +240,8 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
private:
using SharedCreds = QSharedPointer<AbstractCredentials>;

static bool _askedUser;
static QString _oCVersion;
static QString _confDir;
static QString _discoveredLegacyConfigPath;
};
}
#endif // CONFIGFILE_H