nextcloud-desktop/src/gui/folderman.cpp

1473 lines
48 KiB
C++
Raw Normal View History

/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "folderman.h"
2014-11-10 01:25:57 +03:00
#include "configfile.h"
#include "folder.h"
#include "syncresult.h"
#include "theme.h"
#include "socketapi.h"
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
#include "filesystem.h"
#include "lockwatcher.h"
2017-09-01 19:11:43 +03:00
#include "common/asserts.h"
#include <syncengine.h>
#ifdef Q_OS_MAC
#include <CoreServices/CoreServices.h>
#endif
2012-12-07 22:53:36 +04:00
#ifdef Q_OS_WIN
#include <shlobj.h>
#endif
#include <QMessageBox>
2012-05-21 18:48:49 +04:00
#include <QtCore>
#include <QMutableSetIterator>
#include <QSet>
2012-05-21 18:48:49 +04:00
2014-11-10 00:34:07 +03:00
namespace OCC {
Q_LOGGING_CATEGORY(lcFolderMan, "gui.folder.manager", QtInfoMsg)
2017-05-17 11:55:42 +03:00
FolderMan *FolderMan::_instance = 0;
FolderMan::FolderMan(QObject *parent)
: QObject(parent)
, _currentSyncFolder(0)
, _syncEnabled(true)
, _lockWatcher(new LockWatcher)
, _appRestartRequired(false)
{
ASSERT(!_instance);
_instance = this;
_socketApi.reset(new SocketApi);
ConfigFile cfg;
int polltime = cfg.remotePollInterval();
qCInfo(lcFolderMan) << "setting remote poll timer interval to" << polltime << "msec";
2017-05-17 11:55:42 +03:00
_etagPollTimer.setInterval(polltime);
QObject::connect(&_etagPollTimer, SIGNAL(timeout()), this, SLOT(slotEtagPollTimerTimeout()));
_etagPollTimer.start();
_startScheduledSyncTimer.setSingleShot(true);
connect(&_startScheduledSyncTimer, SIGNAL(timeout()),
2017-05-17 11:55:42 +03:00
SLOT(slotStartScheduledFolderSync()));
_timeScheduler.setInterval(5000);
_timeScheduler.setSingleShot(false);
connect(&_timeScheduler, SIGNAL(timeout()),
2017-05-17 11:55:42 +03:00
SLOT(slotScheduleFolderByTime()));
_timeScheduler.start();
2017-05-17 11:55:42 +03:00
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState *)),
SLOT(slotRemoveFoldersForAccount(AccountState *)));
connect(_lockWatcher.data(), SIGNAL(fileUnlocked(QString)),
2017-05-17 11:55:42 +03:00
SLOT(slotWatchedFileUnlocked(QString)));
}
FolderMan *FolderMan::instance()
{
return _instance;
}
FolderMan::~FolderMan()
{
2013-06-29 00:27:53 +04:00
qDeleteAll(_folderMap);
_instance = 0;
}
2014-11-10 00:34:07 +03:00
OCC::Folder::Map FolderMan::map()
{
return _folderMap;
}
2017-05-17 11:55:42 +03:00
void FolderMan::unloadFolder(Folder *f)
{
2017-05-17 11:55:42 +03:00
if (!f) {
return;
}
_socketApi->slotUnregisterPath(f->alias());
2017-05-17 11:55:42 +03:00
if (_folderWatchers.contains(f->alias())) {
_folderWatchers.remove(f->alias());
}
2017-05-17 11:55:42 +03:00
_folderMap.remove(f->alias());
disconnect(f, SIGNAL(syncStarted()),
2017-05-17 11:55:42 +03:00
this, SLOT(slotFolderSyncStarted()));
disconnect(f, SIGNAL(syncFinished(SyncResult)),
2017-05-17 11:55:42 +03:00
this, SLOT(slotFolderSyncFinished(SyncResult)));
disconnect(f, SIGNAL(syncStateChange()),
2017-05-17 11:55:42 +03:00
this, SLOT(slotForwardFolderSyncStateChange()));
disconnect(f, SIGNAL(syncPausedChanged(Folder *, bool)),
this, SLOT(slotFolderSyncPaused(Folder *, bool)));
disconnect(&f->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
2017-05-17 11:55:42 +03:00
_socketApi.data(), SLOT(broadcastStatusPushMessage(const QString &, SyncFileStatus)));
disconnect(f, SIGNAL(watchedFileChangedExternally(QString)),
2017-05-17 11:55:42 +03:00
&f->syncEngine().syncFileStatusTracker(), SLOT(slotPathTouched(QString)));
}
int FolderMan::unloadAndDeleteAllFolders()
{
int cnt = 0;
// clear the list of existing folders.
Folder::MapIterator i(_folderMap);
while (i.hasNext()) {
i.next();
2017-05-17 11:55:42 +03:00
Folder *f = i.value();
unloadFolder(f);
delete f;
cnt++;
}
ASSERT(_folderMap.isEmpty());
_lastSyncFolder = 0;
_currentSyncFolder = 0;
_scheduledFolders.clear();
emit folderListChanged(_folderMap);
emit scheduleQueueChanged();
return cnt;
}
// add a monitor to the local file system. If there is a change in the
// file system, the method slotFolderMonitorFired is triggered through
// the SignalMapper
2017-05-17 11:55:42 +03:00
void FolderMan::registerFolderMonitor(Folder *folder)
{
2017-05-17 11:55:42 +03:00
if (!folder)
return;
if (!QDir(folder->path()).exists())
return;
2017-05-17 11:55:42 +03:00
if (!_folderWatchers.contains(folder->alias())) {
FolderWatcher *fw = new FolderWatcher(folder->path(), folder);
// Connect the pathChanged signal, which comes with the changed path,
// to the signal mapper which maps to the folder alias. The changed path
// is lost this way, but we do not need it for the current implementation.
connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(slotWatchedPathChanged(QString)));
_folderWatchers.insert(folder->alias(), fw);
}
// register the folder with the socket API
if (folder->canSync())
_socketApi->slotRegisterPath(folder->alias());
}
2017-05-17 11:55:42 +03:00
void FolderMan::addMonitorPath(const QString &alias, const QString &path)
{
2017-05-17 11:55:42 +03:00
if (!alias.isEmpty() && _folderWatchers.contains(alias)) {
FolderWatcher *fw = _folderWatchers[alias];
2017-05-17 11:55:42 +03:00
if (fw) {
fw->addPath(path);
}
}
}
2017-05-17 11:55:42 +03:00
void FolderMan::removeMonitorPath(const QString &alias, const QString &path)
{
2017-05-17 11:55:42 +03:00
if (!alias.isEmpty() && _folderWatchers.contains(alias)) {
FolderWatcher *fw = _folderWatchers[alias];
2017-05-17 11:55:42 +03:00
if (fw) {
fw->removePath(path);
}
}
}
2013-11-18 12:35:18 +04:00
int FolderMan::setupFolders()
{
2015-04-24 11:18:33 +03:00
unloadAndDeleteAllFolders();
auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts"));
2015-04-24 11:18:33 +03:00
const auto accountsWithSettings = settings->childGroups();
if (accountsWithSettings.isEmpty()) {
int r = setupFoldersMigration();
if (r > 0) {
AccountManager::instance()->save(false); // don't save credentials, they had not been loaded from keychain
}
return r;
2015-04-24 11:18:33 +03:00
}
qCInfo(lcFolderMan) << "Setup folders from settings file";
2017-05-17 11:55:42 +03:00
foreach (const auto &account, AccountManager::instance()->accounts()) {
2015-04-24 11:18:33 +03:00
const auto id = account->account()->id();
if (!accountsWithSettings.contains(id)) {
continue;
}
settings->beginGroup(id);
2015-04-24 11:18:33 +03:00
settings->beginGroup(QLatin1String("Folders"));
setupFoldersHelper(*settings, account, true);
settings->endGroup();
// See Folder::saveToSettings for details about why this exists.
settings->beginGroup(QLatin1String("Multifolders"));
setupFoldersHelper(*settings, account, false);
settings->endGroup();
2015-04-24 11:18:33 +03:00
settings->endGroup(); // <account>
}
emit folderListChanged(_folderMap);
2015-04-24 11:18:33 +03:00
return _folderMap.size();
}
void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, bool backwardsCompatible)
{
2017-05-17 11:55:42 +03:00
foreach (const auto &folderAlias, settings.childGroups()) {
FolderDefinition folderDefinition;
if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) {
auto defaultJournalPath = folderDefinition.defaultJournalPath(account->account());
// Migration: Old settings don't have journalPath
if (folderDefinition.journalPath.isEmpty()) {
folderDefinition.journalPath = defaultJournalPath;
}
// Migration: ._ files sometimes don't work
// So if the configured journalPath is the default one ("._sync_*.db")
// but the current default doesn't have the underscore, switch to the
// new default. See SyncJournalDb::makeDbName().
if (folderDefinition.journalPath.startsWith("._sync_")
&& defaultJournalPath.startsWith(".sync_")) {
folderDefinition.journalPath = defaultJournalPath;
}
// Migration: If an old db is found, move it to the new name.
if (backwardsCompatible) {
SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath());
}
2017-05-17 11:55:42 +03:00
Folder *f = addFolderInternal(std::move(folderDefinition), account.data());
if (f) {
// Migration: Mark folders that shall be saved in a backwards-compatible way
if (backwardsCompatible) {
f->setSaveBackwardsCompatible(true);
}
scheduleFolder(f);
emit folderSyncStateChange(f);
}
}
}
}
2015-04-24 11:18:33 +03:00
int FolderMan::setupFoldersMigration()
{
ConfigFile cfg;
QDir storageDir(cfg.configPath());
_folderConfigPath = cfg.configPath() + QLatin1String("folders");
qCInfo(lcFolderMan) << "Setup folders from " << _folderConfigPath << "(migration)";
2015-04-24 11:18:33 +03:00
2017-05-17 11:55:42 +03:00
QDir dir(_folderConfigPath);
2015-04-24 11:18:33 +03:00
//We need to include hidden files just in case the alias starts with '.'
dir.setFilter(QDir::Files | QDir::Hidden);
QStringList list = dir.entryList();
// Normally there should be only one account when migrating.
2017-05-17 11:55:42 +03:00
AccountState *accountState = AccountManager::instance()->accounts().value(0).data();
foreach (const QString &alias, list) {
Folder *f = setupFolderFromOldConfigFile(alias, accountState);
if (f) {
scheduleFolder(f);
emit folderSyncStateChange(f);
2015-04-24 11:18:33 +03:00
}
}
emit folderListChanged(_folderMap);
2015-04-24 11:18:33 +03:00
// return the number of valid folders.
return _folderMap.size();
}
2017-05-17 11:55:42 +03:00
bool FolderMan::ensureJournalGone(const QString &journalDbFile)
{
2016-09-02 16:51:13 +03:00
// remove the old journal file
while (QFile::exists(journalDbFile) && !QFile::remove(journalDbFile)) {
qCWarning(lcFolderMan) << "Could not remove old db file at" << journalDbFile;
int ret = QMessageBox::warning(0, tr("Could not reset folder state"),
2017-05-17 11:55:42 +03:00
tr("An old sync journal '%1' was found, "
"but could not be removed. Please make sure "
"that no application is currently using it.")
.arg(QDir::fromNativeSeparators(QDir::cleanPath(journalDbFile))),
QMessageBox::Retry | QMessageBox::Abort);
if (ret == QMessageBox::Abort) {
2013-06-06 23:43:05 +04:00
return false;
}
}
2013-06-06 23:43:05 +04:00
return true;
}
2017-05-17 11:55:42 +03:00
#define SLASH_TAG QLatin1String("__SLASH__")
#define BSLASH_TAG QLatin1String("__BSLASH__")
#define QMARK_TAG QLatin1String("__QMARK__")
#define PERCENT_TAG QLatin1String("__PERCENT__")
2017-05-17 11:55:42 +03:00
#define STAR_TAG QLatin1String("__STAR__")
#define COLON_TAG QLatin1String("__COLON__")
#define PIPE_TAG QLatin1String("__PIPE__")
#define QUOTE_TAG QLatin1String("__QUOTE__")
#define LT_TAG QLatin1String("__LESS_THAN__")
#define GT_TAG QLatin1String("__GREATER_THAN__")
#define PAR_O_TAG QLatin1String("__PAR_OPEN__")
#define PAR_C_TAG QLatin1String("__PAR_CLOSE__")
QString FolderMan::escapeAlias(const QString &alias)
{
QString a(alias);
2017-05-17 11:55:42 +03:00
a.replace(QLatin1Char('/'), SLASH_TAG);
a.replace(QLatin1Char('\\'), BSLASH_TAG);
a.replace(QLatin1Char('?'), QMARK_TAG);
a.replace(QLatin1Char('%'), PERCENT_TAG);
a.replace(QLatin1Char('*'), STAR_TAG);
a.replace(QLatin1Char(':'), COLON_TAG);
a.replace(QLatin1Char('|'), PIPE_TAG);
a.replace(QLatin1Char('"'), QUOTE_TAG);
a.replace(QLatin1Char('<'), LT_TAG);
a.replace(QLatin1Char('>'), GT_TAG);
a.replace(QLatin1Char('['), PAR_O_TAG);
a.replace(QLatin1Char(']'), PAR_C_TAG);
return a;
}
SocketApi *FolderMan::socketApi()
{
return this->_socketApi.data();
}
2017-05-17 11:55:42 +03:00
QString FolderMan::unescapeAlias(const QString &alias)
{
QString a(alias);
2017-05-17 11:55:42 +03:00
a.replace(SLASH_TAG, QLatin1String("/"));
a.replace(BSLASH_TAG, QLatin1String("\\"));
a.replace(QMARK_TAG, QLatin1String("?"));
a.replace(PERCENT_TAG, QLatin1String("%"));
a.replace(STAR_TAG, QLatin1String("*"));
a.replace(COLON_TAG, QLatin1String(":"));
a.replace(PIPE_TAG, QLatin1String("|"));
a.replace(QUOTE_TAG, QLatin1String("\""));
a.replace(LT_TAG, QLatin1String("<"));
a.replace(GT_TAG, QLatin1String(">"));
a.replace(PAR_O_TAG, QLatin1String("["));
a.replace(PAR_C_TAG, QLatin1String("]"));
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)
2017-05-17 11:55:42 +03:00
Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountState *accountState)
{
Folder *folder = 0;
qCInfo(lcFolderMan) << " ` -> setting up:" << file;
QString escapedAlias(file);
// 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.
2017-05-17 11:55:42 +03:00
QFileInfo cfgFile(_folderConfigPath, file);
2017-05-17 11:55:42 +03:00
if (!cfgFile.exists()) {
// try the escaped variant.
escapedAlias = escapeAlias(file);
2017-05-17 11:55:42 +03:00
cfgFile.setFile(_folderConfigPath, escapedAlias);
}
2017-05-17 11:55:42 +03:00
if (!cfgFile.isReadable()) {
qCWarning(lcFolderMan) << "Cannot read folder definition for alias " << cfgFile.filePath();
return folder;
}
2017-05-17 11:55:42 +03:00
QSettings settings(_folderConfigPath + QLatin1Char('/') + escapedAlias, 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.
QStringList groups = settings.childGroups();
2017-05-17 11:55:42 +03:00
if (!groups.contains(escapedAlias) && groups.count() > 0) {
escapedAlias = groups.first();
}
2017-05-17 11:55:42 +03:00
settings.beginGroup(escapedAlias); // read the group with the same name as the file which is the folder alias
QString path = settings.value(QLatin1String("localPath")).toString();
2012-08-17 19:13:17 +04:00
QString backend = settings.value(QLatin1String("backend")).toString();
2017-05-17 11:55:42 +03:00
QString targetPath = settings.value(QLatin1String("targetPath")).toString();
bool paused = settings.value(QLatin1String("paused"), false).toBool();
// QString connection = settings.value( QLatin1String("connection") ).toString();
2017-05-17 11:55:42 +03:00
QString alias = unescapeAlias(escapedAlias);
2013-07-22 22:27:42 +04:00
if (backend.isEmpty() || backend != QLatin1String("owncloud")) {
qCWarning(lcFolderMan) << "obsolete configuration of type" << backend;
2013-07-22 22:27:42 +04:00
return 0;
}
2013-07-22 22:27:42 +04:00
// cut off the leading slash, oCUrl always has a trailing.
2017-05-17 11:55:42 +03:00
if (targetPath.startsWith(QLatin1Char('/'))) {
targetPath.remove(0, 1);
2013-07-22 22:27:42 +04:00
}
if (!accountState) {
qCCritical(lcFolderMan) << "can't create folder without an account";
return 0;
}
2015-04-24 11:18:33 +03:00
FolderDefinition folderDefinition;
folderDefinition.alias = alias;
folderDefinition.localPath = path;
folderDefinition.targetPath = targetPath;
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles();
folder = addFolderInternal(folderDefinition, accountState);
if (folder) {
2017-05-17 11:55:42 +03:00
QStringList blackList = settings.value(QLatin1String("blackList")).toStringList();
if (!blackList.empty()) {
//migrate settings
folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
settings.remove(QLatin1String("blackList"));
// FIXME: If you remove this codepath, you need to provide another way to do
// this via theme.h or the normal FolderMan::setupFolders
}
folder->saveToSettings();
}
qCInfo(lcFolderMan) << "Migrated!" << folder;
settings.sync();
return folder;
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotFolderSyncPaused(Folder *f, bool paused)
{
2017-05-17 11:55:42 +03:00
if (!f) {
qCCritical(lcFolderMan) << "slotFolderSyncPaused called with empty folder";
return;
}
if (!paused) {
_disabledFolders.remove(f);
scheduleFolder(f);
} else {
_disabledFolders.insert(f);
}
}
void FolderMan::slotFolderCanSyncChanged()
{
2017-05-17 11:55:42 +03:00
Folder *f = qobject_cast<Folder *>(sender());
ASSERT(f);
if (f->canSync()) {
_socketApi->slotRegisterPath(f->alias());
} else {
_socketApi->slotUnregisterPath(f->alias());
}
}
// this really terminates the current sync process
// ie. no questions, no prisoners
// csync still remains in a stable state, regardless of that.
void FolderMan::terminateSyncProcess()
{
Folder *f = _currentSyncFolder;
2017-05-17 11:55:42 +03:00
if (f) {
// This will, indirectly and eventually, call slotFolderSyncFinished
// and thereby clear _currentSyncFolder.
f->slotTerminateSync();
}
}
2017-05-17 11:55:42 +03:00
Folder *FolderMan::folder(const QString &alias)
{
2017-05-17 11:55:42 +03:00
if (!alias.isEmpty()) {
if (_folderMap.contains(alias)) {
return _folderMap[alias];
}
}
return 0;
}
void FolderMan::scheduleAllFolders()
{
2017-05-17 11:55:42 +03:00
foreach (Folder *f, _folderMap.values()) {
if (f && f->canSync()) {
2017-05-17 11:55:42 +03:00
scheduleFolder(f);
}
}
}
void FolderMan::slotScheduleAppRestart()
{
_appRestartRequired = true;
qCInfo(lcFolderMan) << "Application restart requested!";
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotSyncOnceFileUnlocks(const QString &path)
{
_lockWatcher->addFile(path);
}
/*
* if a folder wants to be synced, it calls this slot and is added
* to the queue. The slot to actually start a sync is called afterwards.
*/
2017-05-17 11:55:42 +03:00
void FolderMan::scheduleFolder(Folder *f)
{
2017-05-17 11:55:42 +03:00
if (!f) {
qCCritical(lcFolderMan) << "slotScheduleSync called with null folder";
return;
}
auto alias = f->alias();
qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync!";
2017-05-17 11:55:42 +03:00
if (!_scheduledFolders.contains(f)) {
if (!f->canSync()) {
qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!";
_socketApi->slotUpdateFolderView(f);
return;
}
f->prepareToSync();
emit folderSyncStateChange(f);
_scheduledFolders.enqueue(f);
emit scheduleQueueChanged();
} else {
qCInfo(lcFolderMan) << "Sync for folder " << alias << " already scheduled, do not enqueue!";
}
startScheduledSyncSoon();
}
2017-05-17 11:55:42 +03:00
void FolderMan::scheduleFolderNext(Folder *f)
{
auto alias = f->alias();
qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync! Front-of-queue.";
2017-05-17 11:55:42 +03:00
if (!f->canSync()) {
qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!";
return;
}
_scheduledFolders.removeAll(f);
f->prepareToSync();
emit folderSyncStateChange(f);
_scheduledFolders.prepend(f);
emit scheduleQueueChanged();
startScheduledSyncSoon();
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotScheduleETagJob(const QString & /*alias*/, RequestEtagJob *job)
{
2017-05-17 11:55:42 +03:00
QObject::connect(job, SIGNAL(destroyed(QObject *)), this, SLOT(slotEtagJobDestroyed(QObject *)));
QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection);
// maybe: add to queue
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotEtagJobDestroyed(QObject * /*o*/)
{
// _currentEtagJob is automatically cleared
// maybe: remove from queue
QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection);
}
void FolderMan::slotRunOneEtagJob()
{
if (_currentEtagJob.isNull()) {
2016-01-05 17:11:46 +03:00
Folder *folder;
2017-05-17 11:55:42 +03:00
foreach (Folder *f, _folderMap) {
if (f->etagJob()) {
// Caveat: always grabs the first folder with a job, but we think this is Ok for now and avoids us having a seperate queue.
_currentEtagJob = f->etagJob();
2016-01-05 17:11:46 +03:00
folder = f;
break;
}
}
if (_currentEtagJob.isNull()) {
//qCDebug(lcFolderMan) << "No more remote ETag check jobs to schedule.";
/* now it might be a good time to check for restarting... */
2017-05-17 11:55:42 +03:00
if (_currentSyncFolder == NULL && _appRestartRequired) {
restartApplication();
}
} else {
qCDebug(lcFolderMan) << "Scheduling" << folder->remoteUrl().toString() << "to check remote ETag";
_currentEtagJob->start(); // on destroy/end it will continue the queue via slotEtagJobDestroyed
}
}
}
void FolderMan::slotAccountStateChanged()
{
2017-05-17 11:55:42 +03:00
AccountState *accountState = qobject_cast<AccountState *>(sender());
if (!accountState) {
return;
}
QString accountName = accountState->account()->displayName();
if (accountState->isConnected()) {
qCInfo(lcFolderMan) << "Account" << accountName << "connected, scheduling its folders";
foreach (Folder *f, _folderMap.values()) {
if (f
2017-05-17 11:55:42 +03:00
&& f->canSync()
&& f->accountState() == accountState) {
scheduleFolder(f);
}
}
} else {
qCInfo(lcFolderMan) << "Account" << accountName << "disconnected or paused, "
2017-05-17 11:55:42 +03:00
"terminating or descheduling sync folders";
if (_currentSyncFolder
2017-05-17 11:55:42 +03:00
&& _currentSyncFolder->accountState() == accountState) {
_currentSyncFolder->slotTerminateSync();
}
2017-05-17 11:55:42 +03:00
QMutableListIterator<Folder *> it(_scheduledFolders);
while (it.hasNext()) {
2017-05-17 11:55:42 +03:00
Folder *f = it.next();
if (f->accountState() == accountState) {
it.remove();
}
}
emit scheduleQueueChanged();
}
}
// only enable or disable foldermans will schedule and do syncs.
// this is not the same as Pause and Resume of folders.
2017-05-17 11:55:42 +03:00
void FolderMan::setSyncEnabled(bool enabled)
{
if (!_syncEnabled && enabled && !_scheduledFolders.isEmpty()) {
// We have things in our queue that were waiting for the connection to come back on.
startScheduledSyncSoon();
}
_syncEnabled = enabled;
// force a redraw in case the network connect status changed
2017-05-17 11:55:42 +03:00
emit(folderSyncStateChange(0));
}
void FolderMan::startScheduledSyncSoon()
{
if (_startScheduledSyncTimer.isActive()) {
return;
}
if (_scheduledFolders.empty()) {
return;
}
if (_currentSyncFolder) {
return;
}
qint64 msDelay = 100; // 100ms minimum delay
qint64 msSinceLastSync = 0;
// Require a pause based on the duration of the last sync run.
2017-05-17 11:55:42 +03:00
if (Folder *lastFolder = _lastSyncFolder) {
msSinceLastSync = lastFolder->msecSinceLastSync();
// 1s -> 1.5s pause
// 10s -> 5s pause
// 1min -> 12s pause
// 1h -> 90s pause
qint64 pause = qSqrt(lastFolder->msecLastSyncDuration()) / 20.0 * 1000.0;
msDelay = qMax(msDelay, pause);
}
// Delays beyond one minute seem too big, particularly since there
// could be things later in the queue that shouldn't be punished by a
// long delay!
2017-05-17 11:55:42 +03:00
msDelay = qMin(msDelay, 60 * 1000ll);
// Time since the last sync run counts against the delay
msDelay = qMax(1ll, msDelay - msSinceLastSync);
2017-05-17 11:55:42 +03:00
qCInfo(lcFolderMan) << "Starting the next scheduled sync in" << (msDelay / 1000) << "seconds";
_startScheduledSyncTimer.start(msDelay);
}
/*
* slot to start folder syncs.
* It is either called from the slot where folders enqueue themselves for
* syncing or after a folder sync was finished.
*/
void FolderMan::slotStartScheduledFolderSync()
{
2017-05-17 11:55:42 +03:00
if (_currentSyncFolder) {
qCInfo(lcFolderMan) << "Currently folder " << _currentSyncFolder->remoteUrl().toString() << " is running, wait for finish!";
return;
}
2017-05-17 11:55:42 +03:00
if (!_syncEnabled) {
qCInfo(lcFolderMan) << "FolderMan: Syncing is disabled, no scheduling.";
return;
}
qCDebug(lcFolderMan) << "folderQueue size: " << _scheduledFolders.count();
2017-05-17 11:55:42 +03:00
if (_scheduledFolders.isEmpty()) {
return;
}
// Find the first folder in the queue that can be synced.
2017-05-17 11:55:42 +03:00
Folder *folder = 0;
while (!_scheduledFolders.isEmpty()) {
Folder *g = _scheduledFolders.dequeue();
if (g->canSync()) {
folder = g;
break;
}
}
emit scheduleQueueChanged();
// Start syncing this folder!
2017-05-17 11:55:42 +03:00
if (folder) {
// Safe to call several times, and necessary to try again if
// the folder path didn't exist previously.
registerFolderMonitor(folder);
_currentSyncFolder = folder;
2017-05-17 11:55:42 +03:00
folder->startSync(QStringList());
}
}
void FolderMan::slotEtagPollTimerTimeout()
{
ConfigFile cfg;
int polltime = cfg.remotePollInterval();
foreach (Folder *f, _folderMap) {
if (!f) {
continue;
}
if (_currentSyncFolder == f) {
continue;
}
if (_scheduledFolders.contains(f)) {
continue;
}
if (_disabledFolders.contains(f)) {
continue;
}
if (f->etagJob() || f->isBusy() || !f->canSync()) {
continue;
}
if (f->msecSinceLastSync() < polltime) {
continue;
}
QMetaObject::invokeMethod(f, "slotRunEtagJob", Qt::QueuedConnection);
}
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState)
{
QVarLengthArray<Folder *, 16> foldersToRemove;
Folder::MapIterator i(_folderMap);
while (i.hasNext()) {
i.next();
2017-05-17 11:55:42 +03:00
Folder *folder = i.value();
if (folder->accountState() == accountState) {
foldersToRemove.append(folder);
}
}
foreach (const auto &f, foldersToRemove) {
removeFolder(f);
}
}
void FolderMan::slotForwardFolderSyncStateChange()
{
2017-05-17 11:55:42 +03:00
if (Folder *f = qobject_cast<Folder *>(sender())) {
emit folderSyncStateChange(f);
}
}
void FolderMan::slotServerVersionChanged(Account *account)
{
// Pause folders if the server version is unsupported
if (account->serverVersionUnsupported()) {
qCWarning(lcFolderMan) << "The server version is unsupported:" << account->serverVersion()
2017-05-17 11:55:42 +03:00
<< "pausing all folders on the account";
2017-05-17 11:55:42 +03:00
foreach (auto &f, _folderMap) {
if (f->accountState()->account().data() == account) {
f->setSyncPaused(true);
}
}
}
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotWatchedFileUnlocked(const QString &path)
{
2017-05-17 11:55:42 +03:00
if (Folder *f = folderForPath(path)) {
f->scheduleThisFolderSoon();
}
}
void FolderMan::slotScheduleFolderByTime()
{
2017-05-17 11:55:42 +03:00
foreach (auto &f, _folderMap) {
// Never schedule if syncing is disabled or when we're currently
// querying the server for etags
if (!f->canSync() || f->etagJob()) {
continue;
}
auto msecsSinceSync = f->msecSinceLastSync();
// Possibly it's just time for a new sync run
bool forceSyncIntervalExpired =
2017-05-17 11:55:42 +03:00
quint64(msecsSinceSync) > ConfigFile().forceSyncInterval();
if (forceSyncIntervalExpired) {
qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
2017-05-17 11:55:42 +03:00
<< "because it has been" << msecsSinceSync << "ms "
<< "since the last sync";
scheduleFolder(f);
continue;
}
// Retry a couple of times after failure; or regularly if requested
bool syncAgain =
2017-05-17 11:55:42 +03:00
(f->consecutiveFailingSyncs() > 0 && f->consecutiveFailingSyncs() < 3)
|| f->syncEngine().isAnotherSyncNeeded() == DelayedFollowUp;
qint64 syncAgainDelay = 10 * 1000; // 10s for the first retry-after-fail
if (f->consecutiveFailingSyncs() > 1)
syncAgainDelay = 60 * 1000; // 60s for each further attempt
if (syncAgain
2017-05-17 11:55:42 +03:00
&& msecsSinceSync > syncAgainDelay) {
qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
2017-05-17 11:55:42 +03:00
<< ", the last" << f->consecutiveFailingSyncs() << "syncs failed"
<< ", anotherSyncNeeded" << f->syncEngine().isAnotherSyncNeeded()
<< ", last status:" << f->syncResult().statusString()
<< ", time since last sync:" << msecsSinceSync;
scheduleFolder(f);
continue;
}
// Do we want to retry failing syncs or another-sync-needed runs more often?
}
}
2017-05-17 11:55:42 +03:00
void FolderMan::slotFolderSyncStarted()
{
qCInfo(lcFolderMan, ">========== Sync started for folder [%s] of account [%s] with remote [%s]",
qPrintable(_currentSyncFolder->shortGuiLocalPath()),
qPrintable(_currentSyncFolder->accountState()->account()->displayName()),
qPrintable(_currentSyncFolder->remoteUrl().toString()));
}
/*
* a folder indicates that its syncing is finished.
* Start the next sync after the system had some milliseconds to breath.
* This delay is particularly useful to avoid late file change notifications
* (that we caused ourselves by syncing) from triggering another spurious sync.
*/
2017-05-17 11:55:42 +03:00
void FolderMan::slotFolderSyncFinished(const SyncResult &)
{
qCInfo(lcFolderMan, "<========== Sync finished for folder [%s] of account [%s] with remote [%s]",
qPrintable(_currentSyncFolder->shortGuiLocalPath()),
qPrintable(_currentSyncFolder->accountState()->account()->displayName()),
qPrintable(_currentSyncFolder->remoteUrl().toString()));
_lastSyncFolder = _currentSyncFolder;
_currentSyncFolder = 0;
startScheduledSyncSoon();
}
2017-05-17 11:55:42 +03:00
Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition &folderDefinition)
{
// Choose a db filename
auto definition = folderDefinition;
definition.journalPath = definition.defaultJournalPath(accountState->account());
if (!ensureJournalGone(definition.absoluteJournalPath())) {
2015-04-24 11:18:33 +03:00
return 0;
}
auto folder = addFolderInternal(definition, accountState);
// Migration: The first account that's configured for a local folder shall
// be saved in a backwards-compatible way.
bool oneAccountOnly = true;
2017-05-17 11:55:42 +03:00
foreach (Folder *other, FolderMan::instance()->map()) {
if (other != folder && other->cleanPath() == folder->cleanPath()) {
oneAccountOnly = false;
break;
}
}
folder->setSaveBackwardsCompatible(oneAccountOnly);
2017-05-17 11:55:42 +03:00
if (folder) {
folder->saveToSettings();
emit folderSyncStateChange(folder);
emit folderListChanged(_folderMap);
}
return folder;
2015-04-24 11:18:33 +03:00
}
2017-05-17 11:55:42 +03:00
Folder *FolderMan::addFolderInternal(FolderDefinition folderDefinition,
AccountState *accountState)
2015-04-24 11:18:33 +03:00
{
auto alias = folderDefinition.alias;
int count = 0;
while (folderDefinition.alias.isEmpty() || _folderMap.contains(folderDefinition.alias)) {
// There is already a folder configured with this name and folder names need to be unique
folderDefinition.alias = alias + QString::number(++count);
}
2017-05-17 11:55:42 +03:00
auto folder = new Folder(folderDefinition, accountState, this);
2015-04-24 11:18:33 +03:00
qCInfo(lcFolderMan) << "Adding folder to Folder Map " << folder << folder->alias();
2015-04-24 11:18:33 +03:00
_folderMap[folder->alias()] = folder;
if (folder->syncPaused()) {
2015-04-24 11:18:33 +03:00
_disabledFolders.insert(folder);
}
// See matching disconnects in unloadFolder().
2015-04-24 11:18:33 +03:00
connect(folder, SIGNAL(syncStarted()), SLOT(slotFolderSyncStarted()));
connect(folder, SIGNAL(syncFinished(SyncResult)), SLOT(slotFolderSyncFinished(SyncResult)));
connect(folder, SIGNAL(syncStateChange()), SLOT(slotForwardFolderSyncStateChange()));
2017-05-17 11:55:42 +03:00
connect(folder, SIGNAL(syncPausedChanged(Folder *, bool)), SLOT(slotFolderSyncPaused(Folder *, bool)));
connect(folder, SIGNAL(canSyncChanged()), SLOT(slotFolderCanSyncChanged()));
connect(&folder->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
2017-05-17 11:55:42 +03:00
_socketApi.data(), SLOT(broadcastStatusPushMessage(const QString &, SyncFileStatus)));
connect(folder, SIGNAL(watchedFileChangedExternally(QString)),
2017-05-17 11:55:42 +03:00
&folder->syncEngine().syncFileStatusTracker(), SLOT(slotPathTouched(QString)));
2015-04-24 11:18:33 +03:00
registerFolderMonitor(folder);
return folder;
}
Folder *FolderMan::folderForPath(const QString &path)
{
2017-05-17 11:55:42 +03:00
QString absolutePath = QDir::cleanPath(path) + QLatin1Char('/');
2017-05-17 11:55:42 +03:00
foreach (Folder *folder, this->map().values()) {
const QString folderPath = folder->cleanPath() + QLatin1Char('/');
2017-05-17 11:55:42 +03:00
if (absolutePath.startsWith(folderPath, (Utility::isWindows() || Utility::isMac()) ? Qt::CaseInsensitive : Qt::CaseSensitive)) {
return folder;
}
}
return 0;
}
2017-05-17 11:55:42 +03:00
QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const AccountPtr acc)
{
QStringList re;
2017-05-17 11:55:42 +03:00
foreach (Folder *folder, this->map().values()) {
if (acc != 0 && folder->accountState()->account() != acc) {
continue;
}
QString path = folder->cleanPath();
QString remRelPath;
2015-11-16 17:38:08 +03:00
// cut off the remote path from the server path.
remRelPath = relPath.mid(folder->remotePath().length());
path += "/";
path += remRelPath;
2017-05-17 11:55:42 +03:00
if (QFile::exists(path)) {
re.append(path);
}
}
return re;
}
2017-05-17 11:55:42 +03:00
void FolderMan::removeFolder(Folder *f)
{
2017-05-17 11:55:42 +03:00
if (!f) {
qCCritical(lcFolderMan) << "Can not remove null folder";
return;
}
qCInfo(lcFolderMan) << "Removing " << f->alias();
const bool currentlyRunning = (_currentSyncFolder == f);
2017-05-17 11:55:42 +03:00
if (currentlyRunning) {
// abort the sync now
terminateSyncProcess();
}
if (_scheduledFolders.removeAll(f) > 0) {
emit scheduleQueueChanged();
}
f->wipe();
f->setSyncPaused(true);
// remove the folder configuration
2015-04-24 11:18:33 +03:00
f->removeFromSettings();
2017-05-17 11:55:42 +03:00
unloadFolder(f);
if (currentlyRunning) {
// We want to schedule the next folder once this is done
connect(f, SIGNAL(syncFinished(SyncResult)),
2017-05-17 11:55:42 +03:00
SLOT(slotFolderSyncFinished(SyncResult)));
// Let the folder delete itself when done.
connect(f, SIGNAL(syncFinished(SyncResult)), f, SLOT(deleteLater()));
} else {
delete f;
}
emit folderListChanged(_folderMap);
}
2017-05-17 11:55:42 +03:00
QString FolderMan::getBackupName(QString fullPathName) const
{
if (fullPathName.endsWith("/"))
fullPathName.chop(1);
2017-05-17 11:55:42 +03:00
if (fullPathName.isEmpty())
return QString::null;
2017-05-17 11:55:42 +03:00
QString newName = fullPathName + tr(" (backup)");
QFileInfo fi(newName);
int cnt = 2;
do {
if (fi.exists()) {
newName = fullPathName + tr(" (backup %1)").arg(cnt++);
fi.setFile(newName);
}
} while (fi.exists());
2017-05-17 11:55:42 +03:00
return newName;
}
2017-05-17 11:55:42 +03:00
bool FolderMan::startFromScratch(const QString &localFolder)
{
2017-05-17 11:55:42 +03:00
if (localFolder.isEmpty()) {
return false;
}
2017-05-17 11:55:42 +03:00
QFileInfo fi(localFolder);
QDir parentDir(fi.dir());
QString folderName = fi.fileName();
// Adjust for case where localFolder ends with a /
2017-05-17 11:55:42 +03:00
if (fi.isDir()) {
folderName = parentDir.dirName();
parentDir.cdUp();
}
2017-05-17 11:55:42 +03:00
if (fi.exists()) {
// It exists, but is empty -> just reuse it.
2017-05-17 11:55:42 +03:00
if (fi.isDir() && fi.dir().count() == 0) {
qCDebug(lcFolderMan) << "startFromScratch: Directory is empty!";
return true;
}
// Disconnect the socket api from the database to avoid that locking of the
// db file does not allow to move this dir.
Folder *f = folderForPath(localFolder);
2017-05-17 11:55:42 +03:00
if (f) {
if (localFolder.startsWith(f->path())) {
_socketApi->slotUnregisterPath(f->alias());
}
f->journalDb()->close();
f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel
}
// Make a backup of the folder/file.
2017-05-17 11:55:42 +03:00
QString newName = getBackupName(parentDir.absoluteFilePath(folderName));
QString renameError;
2017-05-17 11:55:42 +03:00
if (!FileSystem::rename(fi.absoluteFilePath(), newName, &renameError)) {
qCWarning(lcFolderMan) << "startFromScratch: Could not rename" << fi.absoluteFilePath()
2017-05-17 11:55:42 +03:00
<< "to" << newName << "error:" << renameError;
return false;
}
}
2017-05-17 11:55:42 +03:00
if (!parentDir.mkdir(fi.absoluteFilePath())) {
qCWarning(lcFolderMan) << "startFromScratch: Could not mkdir" << fi.absoluteFilePath();
return false;
}
return true;
}
void FolderMan::setDirtyProxy(bool value)
{
2017-05-17 11:55:42 +03:00
foreach (Folder *f, _folderMap.values()) {
if (f) {
f->setProxyDirty(value);
if (f->accountState() && f->accountState()->account()
2017-05-17 11:55:42 +03:00
&& f->accountState()->account()->networkAccessManager()) {
// Need to do this so we do not use the old determined system proxy
f->accountState()->account()->networkAccessManager()->setProxy(
2017-05-17 11:55:42 +03:00
QNetworkProxy(QNetworkProxy::DefaultProxy));
}
}
}
}
void FolderMan::setDirtyNetworkLimits()
{
2017-05-17 11:55:42 +03:00
foreach (Folder *f, _folderMap.values()) {
// set only in busy folders. Otherwise they read the config anyway.
2017-05-17 11:55:42 +03:00
if (f && f->isBusy()) {
f->setDirtyNetworkLimits();
}
}
}
2017-05-17 11:55:42 +03:00
SyncResult FolderMan::accountStatus(const QList<Folder *> &folders)
{
SyncResult overallResult;
int cnt = folders.count();
// if one folder: show the state of the one folder.
// if more folders:
// if one of them has an error -> show error
// if one is paused, but others ok, show ok
// do not show "problem" in the tray
//
2017-05-17 11:55:42 +03:00
if (cnt == 1) {
Folder *folder = folders.at(0);
2017-05-17 11:55:42 +03:00
if (folder) {
if (folder->syncPaused()) {
overallResult.setStatus(SyncResult::Paused);
} else {
SyncResult::Status syncStatus = folder->syncResult().status();
2017-05-17 11:55:42 +03:00
switch (syncStatus) {
case SyncResult::Undefined:
overallResult.setStatus(SyncResult::Error);
break;
case SyncResult::NotYetStarted:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::NotYetStarted);
break;
case SyncResult::SyncPrepare:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::SyncPrepare);
break;
case SyncResult::SyncRunning:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::SyncRunning);
break;
case SyncResult::Problem: // don't show the problem icon in tray.
case SyncResult::Success:
2017-05-17 11:55:42 +03:00
if (overallResult.status() == SyncResult::Undefined)
overallResult.setStatus(SyncResult::Success);
break;
case SyncResult::Error:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::Error);
break;
case SyncResult::SetupError:
2017-05-17 11:55:42 +03:00
if (overallResult.status() != SyncResult::Error)
overallResult.setStatus(SyncResult::SetupError);
break;
case SyncResult::SyncAbortRequested:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::SyncAbortRequested);
break;
case SyncResult::Paused:
2017-05-17 11:55:42 +03:00
overallResult.setStatus(SyncResult::Paused);
break;
}
}
}
} else {
int errorsSeen = 0;
int goodSeen = 0;
int abortOrPausedSeen = 0;
int runSeen = 0;
int various = 0;
2017-05-17 11:55:42 +03:00
foreach (Folder *folder, folders) {
if (folder->syncPaused()) {
abortOrPausedSeen++;
} else {
SyncResult folderResult = folder->syncResult();
SyncResult::Status syncStatus = folderResult.status();
2017-05-17 11:55:42 +03:00
switch (syncStatus) {
case SyncResult::Undefined:
case SyncResult::NotYetStarted:
various++;
break;
case SyncResult::SyncPrepare:
case SyncResult::SyncRunning:
runSeen++;
break;
case SyncResult::Problem: // don't show the problem icon in tray.
case SyncResult::Success:
goodSeen++;
break;
case SyncResult::Error:
case SyncResult::SetupError:
errorsSeen++;
break;
case SyncResult::SyncAbortRequested:
case SyncResult::Paused:
abortOrPausedSeen++;
// no default case on purpose, check compiler warnings
}
}
}
bool set = false;
2017-05-17 11:55:42 +03:00
if (errorsSeen > 0) {
overallResult.setStatus(SyncResult::Error);
set = true;
}
2017-05-17 11:55:42 +03:00
if (!set && abortOrPausedSeen > 0 && abortOrPausedSeen == cnt) {
// only if all folders are paused
overallResult.setStatus(SyncResult::Paused);
set = true;
}
2017-05-17 11:55:42 +03:00
if (!set && runSeen > 0) {
overallResult.setStatus(SyncResult::SyncRunning);
set = true;
}
2017-05-17 11:55:42 +03:00
if (!set && goodSeen > 0) {
overallResult.setStatus(SyncResult::Success);
set = true;
}
}
return overallResult;
}
2017-05-17 11:55:42 +03:00
QString FolderMan::statusToString(SyncResult::Status syncStatus, bool paused) const
{
QString folderMessage;
2017-05-17 11:55:42 +03:00
switch (syncStatus) {
case SyncResult::Undefined:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Undefined State.");
break;
case SyncResult::NotYetStarted:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Waiting to start syncing.");
break;
case SyncResult::SyncPrepare:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Preparing for sync.");
break;
case SyncResult::SyncRunning:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Sync is running.");
break;
case SyncResult::Success:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Last Sync was successful.");
break;
case SyncResult::Error:
break;
case SyncResult::Problem:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Last Sync was successful, but with warnings on individual files.");
break;
case SyncResult::SetupError:
2017-05-17 11:55:42 +03:00
folderMessage = tr("Setup Error.");
break;
2013-10-03 18:21:54 +04:00
case SyncResult::SyncAbortRequested:
2017-05-17 11:55:42 +03:00
folderMessage = tr("User Abort.");
2013-10-03 18:21:54 +04:00
break;
case SyncResult::Paused:
folderMessage = tr("Sync is paused.");
break;
2017-05-17 11:55:42 +03:00
// no default case on purpose, check compiler warnings
}
2017-05-17 11:55:42 +03:00
if (paused) {
// sync is disabled.
2017-05-17 11:55:42 +03:00
folderMessage = tr("%1 (Sync is paused)").arg(folderMessage);
}
return folderMessage;
}
2017-05-17 11:55:42 +03:00
QString FolderMan::checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl, bool forNewDirectory) const
{
if (path.isEmpty()) {
return tr("No valid folder selected!");
}
2017-05-17 11:55:42 +03:00
QFileInfo selFile(path);
if (!selFile.exists()) {
return checkPathValidityForNewFolder(selFile.dir().path(), serverUrl, true);
}
2017-05-17 11:55:42 +03:00
if (!selFile.isDir()) {
return tr("The selected path is not a folder!");
}
2017-05-17 11:55:42 +03:00
if (!selFile.isWritable()) {
return tr("You have no permission to write to the selected folder!");
}
// check if the local directory isn't used yet in another ownCloud sync
Qt::CaseSensitivity cs = Qt::CaseSensitive;
2017-05-17 11:55:42 +03:00
if (Utility::fsCasePreserving()) {
cs = Qt::CaseInsensitive;
}
2017-05-17 11:55:42 +03:00
for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i) {
Folder *f = static_cast<Folder *>(i.value());
QString folderDir = QDir(f->path()).canonicalPath();
if (folderDir.isEmpty()) {
continue;
}
2017-05-17 11:55:42 +03:00
if (!folderDir.endsWith(QLatin1Char('/'), cs))
folderDir.append(QLatin1Char('/'));
2017-05-17 11:55:42 +03:00
const QString folderDirClean = QDir::cleanPath(folderDir) + '/';
const QString userDirClean = QDir::cleanPath(path) + '/';
// folderDir follows sym links, path not.
bool differentPathes = !Utility::fileNamesEqual(QDir::cleanPath(folderDir), QDir::cleanPath(path));
2017-05-17 11:55:42 +03:00
if (!forNewDirectory && differentPathes && folderDirClean.startsWith(userDirClean, cs)) {
return tr("The local folder %1 already contains a folder used in a folder sync connection. "
"Please pick another one!")
2017-05-17 11:55:42 +03:00
.arg(QDir::toNativeSeparators(path));
}
// QDir::cleanPath keeps links
// canonicalPath() remove symlinks and uses the symlink targets.
2017-05-17 11:55:42 +03:00
QString absCleanUserFolder = QDir::cleanPath(QDir(path).canonicalPath()) + '/';
2017-05-17 11:55:42 +03:00
if ((forNewDirectory || differentPathes) && userDirClean.startsWith(folderDirClean, cs)) {
return tr("The local folder %1 is already contained in a folder used in a folder sync connection. "
"Please pick another one!")
2017-05-17 11:55:42 +03:00
.arg(QDir::toNativeSeparators(path));
}
// both follow symlinks.
2017-05-17 11:55:42 +03:00
bool cleanUserEqualsCleanFolder = Utility::fileNamesEqual(absCleanUserFolder, folderDirClean);
if (differentPathes && absCleanUserFolder.startsWith(folderDirClean, cs) && !cleanUserEqualsCleanFolder) {
return tr("The local folder %1 is a symbolic link. "
"The link target is already contained in a folder used in a folder sync connection. "
"Please pick another one!")
2017-05-17 11:55:42 +03:00
.arg(QDir::toNativeSeparators(path));
}
2017-05-17 11:55:42 +03:00
if (differentPathes && folderDirClean.startsWith(absCleanUserFolder, cs) && !cleanUserEqualsCleanFolder && !forNewDirectory) {
return tr("The local folder %1 contains a symbolic link. "
"The link target contains an already synced folder "
"Please pick another one!")
2017-05-17 11:55:42 +03:00
.arg(QDir::toNativeSeparators(path));
}
// if both pathes are equal, the server url needs to be different
// otherwise it would mean that a new connection from the same local folder
// to the same account is added which is not wanted. The account must differ.
2017-05-17 11:55:42 +03:00
if (serverUrl.isValid() && Utility::fileNamesEqual(absCleanUserFolder, folderDir)) {
QUrl folderUrl = f->accountState()->account()->url();
QString user = f->accountState()->account()->credentials()->user();
folderUrl.setUserName(user);
2017-05-17 11:55:42 +03:00
if (serverUrl == folderUrl) {
return tr("There is already a sync from the server to this local folder. "
"Please pick another local folder!");
}
}
}
return QString();
}
QString FolderMan::findGoodPathForNewSyncFolder(const QString &basePath, const QUrl &serverUrl) const
{
QString folder = basePath;
// If the parent folder is a sync folder or contained in one, we can't
// possibly find a valid sync folder inside it.
// Example: Someone syncs their home directory. Then ~/foobar is not
// going to be an acceptable sync folder path for any value of foobar.
QString parentFolder = QFileInfo(folder).dir().canonicalPath();
if (FolderMan::instance()->folderForPath(parentFolder)) {
// Any path with that parent is going to be unacceptable,
// so just keep it as-is.
return basePath;
}
int attempt = 1;
forever {
const bool isGood =
2017-05-17 11:55:42 +03:00
!QFileInfo(folder).exists()
&& FolderMan::instance()->checkPathValidityForNewFolder(folder, serverUrl).isEmpty();
if (isGood) {
break;
}
// Count attempts and give up eventually
attempt++;
if (attempt > 100) {
return basePath;
}
folder = basePath + QString::number(attempt);
}
return folder;
}
bool FolderMan::ignoreHiddenFiles() const
{
if (_folderMap.empty()) {
return true;
}
return _folderMap.begin().value()->ignoreHiddenFiles();
}
void FolderMan::setIgnoreHiddenFiles(bool ignore)
{
// Note that the setting will revert to 'true' if all folders
// are deleted...
2017-05-17 11:55:42 +03:00
foreach (Folder *folder, _folderMap) {
folder->setIgnoreHiddenFiles(ignore);
folder->saveToSettings();
}
}
2017-05-17 11:55:42 +03:00
QQueue<Folder *> FolderMan::scheduleQueue() const
{
return _scheduledFolders;
}
Folder *FolderMan::currentSyncFolder() const
{
return _currentSyncFolder;
}
void FolderMan::restartApplication()
{
2017-05-17 11:55:42 +03:00
if (Utility::isLinux()) {
// restart:
qCInfo(lcFolderMan) << "Restarting application NOW, PID" << qApp->applicationPid() << "is ending.";
qApp->quit();
QStringList args = qApp->arguments();
QString prg = args.takeFirst();
QProcess::startDetached(prg, args);
} else {
qCDebug(lcFolderMan) << "On this platform we do not restart.";
}
}
2014-11-10 00:34:07 +03:00
} // namespace OCC