2012-02-15 12:30:37 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2014-07-11 02:31:24 +04:00
|
|
|
#include "syncengine.h"
|
|
|
|
#include "account.h"
|
2013-05-03 21:11:00 +04:00
|
|
|
#include "owncloudpropagator.h"
|
2017-09-01 19:11:43 +03:00
|
|
|
#include "common/syncjournaldb.h"
|
|
|
|
#include "common/syncjournalfilerecord.h"
|
2014-08-11 19:47:16 +04:00
|
|
|
#include "discoveryphase.h"
|
2013-07-30 13:19:22 +04:00
|
|
|
#include "creds/abstractcredentials.h"
|
2014-10-22 12:20:38 +04:00
|
|
|
#include "syncfilestatus.h"
|
2014-09-19 12:58:52 +04:00
|
|
|
#include "csync_private.h"
|
2015-10-29 16:10:11 +03:00
|
|
|
#include "filesystem.h"
|
2017-01-20 15:59:13 +03:00
|
|
|
#include "propagateremotedelete.h"
|
2017-06-28 13:45:54 +03:00
|
|
|
#include "propagatedownload.h"
|
2017-09-01 19:11:43 +03:00
|
|
|
#include "common/asserts.h"
|
2012-05-21 18:48:49 +04:00
|
|
|
|
2012-12-06 19:24:53 +04:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
2014-10-09 13:03:07 +04:00
|
|
|
#include <climits>
|
2013-04-04 19:14:38 +04:00
|
|
|
#include <assert.h>
|
|
|
|
|
2015-06-15 18:38:12 +03:00
|
|
|
#include <QCoreApplication>
|
2013-01-14 15:13:29 +04:00
|
|
|
#include <QSslSocket>
|
2012-02-15 12:30:37 +04:00
|
|
|
#include <QDir>
|
2017-05-09 15:24:11 +03:00
|
|
|
#include <QLoggingCategory>
|
2012-02-15 12:30:37 +04:00
|
|
|
#include <QMutexLocker>
|
|
|
|
#include <QThread>
|
|
|
|
#include <QStringList>
|
|
|
|
#include <QTextStream>
|
2012-02-28 19:49:13 +04:00
|
|
|
#include <QTime>
|
2012-12-11 15:49:48 +04:00
|
|
|
#include <QUrl>
|
2012-12-13 22:52:07 +04:00
|
|
|
#include <QSslCertificate>
|
2014-07-22 20:07:02 +04:00
|
|
|
#include <QProcess>
|
2014-08-15 17:00:10 +04:00
|
|
|
#include <QElapsedTimer>
|
2015-01-05 15:54:31 +03:00
|
|
|
#include <qtextcodec.h>
|
2012-08-26 13:47:45 +04:00
|
|
|
|
2014-11-10 00:34:07 +03:00
|
|
|
namespace OCC {
|
2012-02-15 12:30:37 +04:00
|
|
|
|
2017-12-28 22:33:10 +03:00
|
|
|
Q_LOGGING_CATEGORY(lcEngine, "nextcloud.sync.engine", QtInfoMsg)
|
2017-05-09 15:24:11 +03:00
|
|
|
|
2017-07-24 09:03:05 +03:00
|
|
|
static const int s_touchedFilesMaxAgeMs = 15 * 1000;
|
2016-03-09 19:53:18 +03:00
|
|
|
bool SyncEngine::s_anySyncRunning = false;
|
2012-03-21 21:03:49 +04:00
|
|
|
|
2015-11-20 17:14:22 +03:00
|
|
|
qint64 SyncEngine::minimumFileAgeForUpload = 2000;
|
|
|
|
|
2016-02-10 23:55:16 +03:00
|
|
|
SyncEngine::SyncEngine(AccountPtr account, const QString &localPath,
|
2016-11-15 20:47:04 +03:00
|
|
|
const QString &remotePath, OCC::SyncJournalDb *journal)
|
2014-12-18 14:09:48 +03:00
|
|
|
: _account(account)
|
2014-06-20 15:25:39 +04:00
|
|
|
, _needsUpdate(false)
|
2016-03-09 19:53:18 +03:00
|
|
|
, _syncRunning(false)
|
2014-06-20 15:25:39 +04:00
|
|
|
, _localPath(localPath)
|
|
|
|
, _remotePath(remotePath)
|
|
|
|
, _journal(journal)
|
2015-01-30 15:36:20 +03:00
|
|
|
, _progressInfo(new ProgressInfo)
|
2014-07-15 13:22:16 +04:00
|
|
|
, _hasNoneFiles(false)
|
|
|
|
, _hasRemoveFile(false)
|
2016-01-05 13:47:17 +03:00
|
|
|
, _hasForwardInTimeFiles(false)
|
|
|
|
, _backInTimeFiles(0)
|
2014-06-20 15:25:39 +04:00
|
|
|
, _uploadLimit(0)
|
2014-06-23 14:48:34 +04:00
|
|
|
, _downloadLimit(0)
|
2016-12-06 15:18:59 +03:00
|
|
|
, _anotherSyncNeeded(NoFollowUpSync)
|
2012-02-15 12:30:37 +04:00
|
|
|
{
|
2013-05-16 15:54:22 +04:00
|
|
|
qRegisterMetaType<SyncFileItem>("SyncFileItem");
|
2017-01-25 13:12:38 +03:00
|
|
|
qRegisterMetaType<SyncFileItemPtr>("SyncFileItemPtr");
|
2013-10-28 13:47:10 +04:00
|
|
|
qRegisterMetaType<SyncFileItem::Status>("SyncFileItem::Status");
|
2016-08-04 15:59:46 +03:00
|
|
|
qRegisterMetaType<SyncFileStatus>("SyncFileStatus");
|
2016-12-20 13:43:39 +03:00
|
|
|
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
|
|
|
|
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
|
2014-02-05 23:18:03 +04:00
|
|
|
|
2016-08-04 15:59:46 +03:00
|
|
|
// Everything in the SyncEngine expects a trailing slash for the localPath.
|
2017-02-07 15:52:15 +03:00
|
|
|
ASSERT(localPath.endsWith(QLatin1Char('/')));
|
2016-02-10 23:55:16 +03:00
|
|
|
|
2017-09-14 16:50:13 +03:00
|
|
|
_csync_ctx.reset(new CSYNC(localPath.toUtf8().data(), journal));
|
2016-07-10 13:56:43 +03:00
|
|
|
|
2017-11-29 14:04:01 +03:00
|
|
|
_excludedFiles.reset(new ExcludedFiles);
|
|
|
|
_csync_ctx->exclude_traversal_fn = _excludedFiles->csyncTraversalMatchFun();
|
|
|
|
|
2016-03-17 14:26:44 +03:00
|
|
|
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this));
|
2016-02-10 23:55:16 +03:00
|
|
|
|
2016-06-09 13:07:18 +03:00
|
|
|
_clearTouchedFilesTimer.setSingleShot(true);
|
|
|
|
_clearTouchedFilesTimer.setInterval(30 * 1000);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(&_clearTouchedFilesTimer, &QTimer::timeout, this, &SyncEngine::slotClearTouchedFiles);
|
2016-06-09 13:07:18 +03:00
|
|
|
|
2015-06-06 11:49:24 +03:00
|
|
|
_thread.setObjectName("SyncEngine_Thread");
|
2012-02-15 12:30:37 +04:00
|
|
|
}
|
|
|
|
|
2014-03-17 14:34:51 +04:00
|
|
|
SyncEngine::~SyncEngine()
|
2012-02-15 12:30:37 +04:00
|
|
|
{
|
2016-03-09 19:53:18 +03:00
|
|
|
abort();
|
2014-02-05 23:18:03 +04:00
|
|
|
_thread.quit();
|
|
|
|
_thread.wait();
|
2016-06-15 18:01:00 +03:00
|
|
|
_excludedFiles.reset();
|
2012-02-15 12:30:37 +04:00
|
|
|
}
|
|
|
|
|
2013-06-19 20:17:32 +04:00
|
|
|
//Convert an error code from csync to a user readable string.
|
|
|
|
// Keep that function thread safe as it can be called from the sync thread or the main thread
|
2014-03-17 14:34:51 +04:00
|
|
|
QString SyncEngine::csyncErrorToString(CSYNC_STATUS err)
|
2012-12-20 19:45:56 +04:00
|
|
|
{
|
|
|
|
QString errStr;
|
|
|
|
|
|
|
|
switch (err) {
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_OK:
|
2012-12-20 19:45:56 +04:00
|
|
|
errStr = tr("Success.");
|
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_STATEDB_LOAD_ERROR:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("Failed to load or create the journal file. "
|
2015-09-07 09:51:22 +03:00
|
|
|
"Make sure you have read and write permissions in the local sync folder.");
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_UPDATE_ERROR:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("Discovery step failed.");
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_TIMEOUT:
|
2014-06-29 16:06:45 +04:00
|
|
|
errStr = tr("A network connection timeout happened.");
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_HTTP_ERROR:
|
2012-12-20 19:45:56 +04:00
|
|
|
errStr = tr("A HTTP transmission error happened.");
|
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_PERMISSION_DENIED:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("Permission denied.");
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_NOT_FOUND:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("File or directory not found:") + " "; // filename gets added.
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_FILE_EXISTS:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("Tried to create a folder that already exists.");
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_OUT_OF_SPACE:
|
2018-03-06 09:48:00 +03:00
|
|
|
errStr = tr("No space on %1 server available.").arg(qApp->applicationName());
|
2012-12-20 19:45:56 +04:00
|
|
|
break;
|
2013-08-19 18:15:20 +04:00
|
|
|
case CSYNC_STATUS_UNSUCCESSFUL:
|
2012-12-20 19:45:56 +04:00
|
|
|
errStr = tr("CSync unspecified error.");
|
2013-10-02 21:41:17 +04:00
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_ABORTED:
|
|
|
|
errStr = tr("Aborted by the user");
|
|
|
|
break;
|
2014-10-27 21:21:12 +03:00
|
|
|
case CSYNC_STATUS_SERVICE_UNAVAILABLE:
|
2015-02-25 10:57:39 +03:00
|
|
|
errStr = tr("The service is temporarily unavailable");
|
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_STORAGE_UNAVAILABLE:
|
2015-09-07 09:51:22 +03:00
|
|
|
errStr = tr("The mounted folder is temporarily not available on the server");
|
2015-01-15 14:19:06 +03:00
|
|
|
break;
|
2015-10-12 18:06:49 +03:00
|
|
|
case CSYNC_STATUS_FORBIDDEN:
|
|
|
|
errStr = tr("Access is forbidden");
|
|
|
|
break;
|
2014-12-02 14:25:51 +03:00
|
|
|
case CSYNC_STATUS_OPENDIR_ERROR:
|
2015-09-07 09:51:22 +03:00
|
|
|
errStr = tr("An error occurred while opening a folder");
|
2014-12-02 14:25:51 +03:00
|
|
|
break;
|
2015-08-04 23:13:38 +03:00
|
|
|
case CSYNC_STATUS_READDIR_ERROR:
|
2015-09-07 09:51:22 +03:00
|
|
|
errStr = tr("Error while reading folder.");
|
2015-08-04 23:13:38 +03:00
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_INVALID_CHARACTERS:
|
|
|
|
// Handled in callee
|
2012-12-20 19:45:56 +04:00
|
|
|
default:
|
2015-02-06 00:00:13 +03:00
|
|
|
errStr = tr("An internal error number %1 occurred.").arg((int)err);
|
2012-12-20 19:45:56 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return errStr;
|
|
|
|
}
|
|
|
|
|
2016-05-25 16:01:26 +03:00
|
|
|
/**
|
|
|
|
* Check if the item is in the blacklist.
|
|
|
|
* If it should not be sync'ed because of the blacklist, update the item with the error instruction
|
|
|
|
* and proper error message, and return true.
|
|
|
|
* If the item is not in the blacklist, or the blacklist is stale, return false.
|
|
|
|
*/
|
2015-04-15 16:19:11 +03:00
|
|
|
bool SyncEngine::checkErrorBlacklisting(SyncFileItem &item)
|
2013-11-20 16:44:01 +04:00
|
|
|
{
|
|
|
|
if (!_journal) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCCritical(lcEngine) << "Journal is undefined!";
|
2013-11-20 16:44:01 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-15 16:19:11 +03:00
|
|
|
SyncJournalErrorBlacklistRecord entry = _journal->errorBlacklistEntry(item._file);
|
|
|
|
item._hasBlacklistEntry = false;
|
2013-11-20 16:44:01 +04:00
|
|
|
|
2014-10-09 16:49:51 +04:00
|
|
|
if (!entry.isValid()) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-11-29 19:15:26 +04:00
|
|
|
|
2015-04-15 16:19:11 +03:00
|
|
|
item._hasBlacklistEntry = true;
|
2013-11-20 16:44:01 +04:00
|
|
|
|
2014-10-09 16:49:51 +04:00
|
|
|
// If duration has expired, it's not blacklisted anymore
|
2017-09-25 12:49:11 +03:00
|
|
|
time_t now = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
|
2017-03-16 11:27:17 +03:00
|
|
|
if (now >= entry._lastTryTime + entry._ignoreDuration) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "blacklist entry for " << item._file << " has expired!";
|
2014-10-09 16:49:51 +04:00
|
|
|
return false;
|
|
|
|
}
|
2013-11-29 19:15:26 +04:00
|
|
|
|
2014-10-09 16:49:51 +04:00
|
|
|
// If the file has changed locally or on the server, the blacklist
|
|
|
|
// entry no longer applies
|
2015-04-15 16:19:11 +03:00
|
|
|
if (item._direction == SyncFileItem::Up) { // check the modtime
|
|
|
|
if (item._modtime == 0 || entry._lastTryModtime == 0) {
|
2014-10-09 16:49:51 +04:00
|
|
|
return false;
|
2015-04-15 16:19:11 +03:00
|
|
|
} else if (item._modtime != entry._lastTryModtime) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << item._file << " is blacklisted, but has changed mtime!";
|
2014-10-09 16:49:51 +04:00
|
|
|
return false;
|
2016-05-25 16:01:26 +03:00
|
|
|
} else if (item._renameTarget != entry._renameTarget) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << item._file << " is blacklisted, but rename target changed from" << entry._renameTarget;
|
2016-05-25 16:01:26 +03:00
|
|
|
return false;
|
2014-10-09 16:49:51 +04:00
|
|
|
}
|
2015-04-15 16:19:11 +03:00
|
|
|
} else if (item._direction == SyncFileItem::Down) {
|
2014-10-09 16:49:51 +04:00
|
|
|
// download, check the etag.
|
2015-04-15 16:19:11 +03:00
|
|
|
if (item._etag.isEmpty() || entry._lastTryEtag.isEmpty()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << item._file << "one ETag is empty, no blacklisting";
|
2014-10-09 16:49:51 +04:00
|
|
|
return false;
|
2015-04-15 16:19:11 +03:00
|
|
|
} else if (item._etag != entry._lastTryEtag) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << item._file << " is blacklisted, but has changed etag!";
|
2014-10-09 16:49:51 +04:00
|
|
|
return false;
|
2013-11-20 16:44:01 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 15:17:26 +03:00
|
|
|
int waitSeconds = entry._lastTryTime + entry._ignoreDuration - now;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Item is on blacklist: " << entry._file
|
2014-10-09 16:49:51 +04:00
|
|
|
<< "retries:" << entry._retryCount
|
2017-06-27 15:17:26 +03:00
|
|
|
<< "for another" << waitSeconds << "s";
|
|
|
|
|
|
|
|
// We need to indicate that we skip this file due to blacklisting
|
|
|
|
// for reporting and for making sure we don't update the blacklist
|
|
|
|
// entry yet.
|
|
|
|
// Classification is this _instruction and _status
|
|
|
|
item._instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
item._status = SyncFileItem::BlacklistedError;
|
|
|
|
|
|
|
|
auto waitSecondsStr = Utility::durationToDescriptiveString1(1000 * waitSeconds);
|
|
|
|
item._errorString = tr("%1 (skipped due to earlier error, trying again in %2)").arg(entry._errorString, waitSecondsStr);
|
2014-10-09 16:49:51 +04:00
|
|
|
|
2017-07-07 16:11:00 +03:00
|
|
|
if (entry._errorCategory == SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage) {
|
|
|
|
slotInsufficientRemoteStorage();
|
|
|
|
}
|
|
|
|
|
2014-10-09 16:49:51 +04:00
|
|
|
return true;
|
2013-11-20 16:44:01 +04:00
|
|
|
}
|
|
|
|
|
2018-02-23 14:13:42 +03:00
|
|
|
static bool isFileTransferInstruction(csync_instructions_e instruction)
|
|
|
|
{
|
|
|
|
return instruction == CSYNC_INSTRUCTION_CONFLICT
|
|
|
|
|| instruction == CSYNC_INSTRUCTION_NEW
|
|
|
|
|| instruction == CSYNC_INSTRUCTION_SYNC
|
|
|
|
|| instruction == CSYNC_INSTRUCTION_TYPE_CHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isFileModifyingInstruction(csync_instructions_e instruction)
|
|
|
|
{
|
|
|
|
return isFileTransferInstruction(instruction)
|
|
|
|
|| instruction == CSYNC_INSTRUCTION_RENAME
|
|
|
|
|| instruction == CSYNC_INSTRUCTION_REMOVE;
|
|
|
|
}
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
void SyncEngine::deleteStaleDownloadInfos(const SyncFileItemVector &syncItems)
|
2014-09-03 14:11:03 +04:00
|
|
|
{
|
|
|
|
// Find all downloadinfo paths that we want to preserve.
|
|
|
|
QSet<QString> download_file_paths;
|
2017-01-25 13:28:18 +03:00
|
|
|
foreach (const SyncFileItemPtr &it, syncItems) {
|
2015-04-15 16:19:11 +03:00
|
|
|
if (it->_direction == SyncFileItem::Down
|
2018-02-23 14:13:42 +03:00
|
|
|
&& it->_type == ItemTypeFile
|
|
|
|
&& isFileTransferInstruction(it->_instruction)) {
|
2015-04-15 16:19:11 +03:00
|
|
|
download_file_paths.insert(it->_file);
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete from journal and from filesystem.
|
|
|
|
const QVector<SyncJournalDb::DownloadInfo> deleted_infos =
|
|
|
|
_journal->getAndDeleteStaleDownloadInfos(download_file_paths);
|
|
|
|
foreach (const SyncJournalDb::DownloadInfo &deleted_info, deleted_infos) {
|
|
|
|
const QString tmppath = _propagator->getFilePath(deleted_info._tmpfile);
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Deleting stale temporary file: " << tmppath;
|
2016-01-05 13:58:18 +03:00
|
|
|
FileSystem::remove(tmppath);
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
void SyncEngine::deleteStaleUploadInfos(const SyncFileItemVector &syncItems)
|
2014-09-03 14:11:03 +04:00
|
|
|
{
|
|
|
|
// Find all blacklisted paths that we want to preserve.
|
|
|
|
QSet<QString> upload_file_paths;
|
2017-01-25 13:28:18 +03:00
|
|
|
foreach (const SyncFileItemPtr &it, syncItems) {
|
2015-04-15 16:19:11 +03:00
|
|
|
if (it->_direction == SyncFileItem::Up
|
2018-02-23 14:13:42 +03:00
|
|
|
&& it->_type == ItemTypeFile
|
|
|
|
&& isFileTransferInstruction(it->_instruction)) {
|
2015-04-15 16:19:11 +03:00
|
|
|
upload_file_paths.insert(it->_file);
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete from journal.
|
2017-01-20 15:59:13 +03:00
|
|
|
auto ids = _journal->deleteStaleUploadInfos(upload_file_paths);
|
|
|
|
|
|
|
|
// Delete the stales chunk on the server.
|
|
|
|
if (account()->capabilities().chunkingNg()) {
|
|
|
|
foreach (uint transferId, ids) {
|
|
|
|
QUrl url = Utility::concatUrlPath(account()->url(), QLatin1String("remote.php/dav/uploads/") + account()->davUser() + QLatin1Char('/') + QString::number(transferId));
|
|
|
|
(new DeleteJob(account(), url, this))->start();
|
|
|
|
}
|
|
|
|
}
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
void SyncEngine::deleteStaleErrorBlacklistEntries(const SyncFileItemVector &syncItems)
|
2014-09-03 14:11:03 +04:00
|
|
|
{
|
|
|
|
// Find all blacklisted paths that we want to preserve.
|
|
|
|
QSet<QString> blacklist_file_paths;
|
2017-01-25 13:28:18 +03:00
|
|
|
foreach (const SyncFileItemPtr &it, syncItems) {
|
2015-04-15 16:19:11 +03:00
|
|
|
if (it->_hasBlacklistEntry)
|
|
|
|
blacklist_file_paths.insert(it->_file);
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete from journal.
|
2015-01-16 12:17:19 +03:00
|
|
|
_journal->deleteStaleErrorBlacklistEntries(blacklist_file_paths);
|
2014-09-03 14:11:03 +04:00
|
|
|
}
|
|
|
|
|
2017-12-02 13:40:43 +03:00
|
|
|
void SyncEngine::conflictRecordMaintenance()
|
|
|
|
{
|
|
|
|
// Remove stale conflict entries from the database
|
|
|
|
// by checking which files still exist and removing the
|
|
|
|
// missing ones.
|
|
|
|
auto conflictRecordPaths = _journal->conflictRecordPaths();
|
|
|
|
for (const auto &path : conflictRecordPaths) {
|
|
|
|
auto fsPath = _propagator->getFilePath(QString::fromUtf8(path));
|
|
|
|
if (!QFileInfo(fsPath).exists()) {
|
|
|
|
_journal->deleteConflictRecord(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Did the sync see any conflict files that don't yet have records?
|
|
|
|
// If so, add them now.
|
|
|
|
//
|
|
|
|
// This happens when the conflicts table is new or when conflict files
|
|
|
|
// are downlaoded but the server doesn't send conflict headers.
|
|
|
|
for (const auto &path : _seenFiles) {
|
|
|
|
if (!Utility::isConflictFile(path))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto bapath = path.toUtf8();
|
|
|
|
if (!conflictRecordPaths.contains(bapath)) {
|
|
|
|
ConflictRecord record;
|
|
|
|
record.path = bapath;
|
|
|
|
|
|
|
|
// Determine fileid of target file
|
|
|
|
auto basePath = Utility::conflictFileBaseName(bapath);
|
|
|
|
SyncJournalFileRecord baseRecord;
|
|
|
|
if (_journal->getFileRecord(basePath, &baseRecord) && baseRecord.isValid()) {
|
|
|
|
record.baseFileId = baseRecord._fileId;
|
|
|
|
}
|
|
|
|
|
|
|
|
_journal->setConflictRecord(record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-08 18:10:55 +03:00
|
|
|
/**
|
|
|
|
* The main function in the post-reconcile phase.
|
|
|
|
*
|
|
|
|
* Called on each entry in the local and remote trees by
|
|
|
|
* csync_walk_local_tree()/csync_walk_remote_tree().
|
|
|
|
*
|
2017-08-23 20:16:12 +03:00
|
|
|
* It merges the two csync file trees into a single map of SyncFileItems.
|
2016-11-08 18:10:55 +03:00
|
|
|
*
|
|
|
|
* See doc/dev/sync-algorithm.md for an overview.
|
|
|
|
*/
|
2017-08-17 15:39:23 +03:00
|
|
|
int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, bool remote)
|
2013-01-15 23:41:52 +04:00
|
|
|
{
|
|
|
|
if (!file)
|
|
|
|
return -1;
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2015-01-05 15:54:31 +03:00
|
|
|
auto instruction = file->instruction;
|
2018-02-19 15:41:57 +03:00
|
|
|
|
|
|
|
// Decode utf8 path and rename_path QByteArrays to QStrings
|
|
|
|
QString fileUtf8;
|
|
|
|
QString renameTarget;
|
|
|
|
bool utf8DecodeError = false;
|
|
|
|
{
|
|
|
|
const auto toUnicode = [](QByteArray utf8, QString *result) {
|
|
|
|
static QTextCodec *codec = QTextCodec::codecForName("UTF-8");
|
|
|
|
ASSERT(codec);
|
|
|
|
|
|
|
|
QTextCodec::ConverterState state;
|
|
|
|
*result = codec->toUnicode(utf8, utf8.size(), &state);
|
|
|
|
return !(state.invalidChars > 0 || state.remainingChars > 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!toUnicode(file->path, &fileUtf8)) {
|
|
|
|
qCWarning(lcEngine) << "File ignored because of invalid utf-8 sequence: " << file->path;
|
2015-02-17 20:40:17 +03:00
|
|
|
instruction = CSYNC_INSTRUCTION_IGNORE;
|
2018-02-19 15:41:57 +03:00
|
|
|
utf8DecodeError = true;
|
2015-02-17 20:40:17 +03:00
|
|
|
}
|
2018-02-19 15:41:57 +03:00
|
|
|
if (!toUnicode(file->rename_path, &renameTarget)) {
|
|
|
|
qCWarning(lcEngine) << "File ignored because of invalid utf-8 sequence in the rename_path: " << file->path << file->rename_path;
|
|
|
|
instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
utf8DecodeError = true;
|
2015-02-17 20:40:17 +03:00
|
|
|
}
|
2015-01-05 15:54:31 +03:00
|
|
|
}
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2018-02-19 15:41:57 +03:00
|
|
|
// key is the handle that the SyncFileItem will have in the map.
|
|
|
|
QString key = fileUtf8;
|
|
|
|
if (instruction == CSYNC_INSTRUCTION_RENAME) {
|
|
|
|
key = renameTarget;
|
|
|
|
}
|
|
|
|
|
2015-10-05 06:20:09 +03:00
|
|
|
// Gets a default-constructed SyncFileItemPtr or the one from the first walk (=local walk)
|
2015-04-15 16:19:11 +03:00
|
|
|
SyncFileItemPtr item = _syncItemMap.value(key);
|
|
|
|
if (!item)
|
|
|
|
item = SyncFileItemPtr(new SyncFileItem);
|
|
|
|
|
|
|
|
if (item->_file.isEmpty() || instruction == CSYNC_INSTRUCTION_RENAME) {
|
|
|
|
item->_file = fileUtf8;
|
2015-02-17 20:40:17 +03:00
|
|
|
}
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_originalFile = item->_file;
|
2018-04-04 01:41:11 +03:00
|
|
|
item->_encryptedFileName = file->e2eMangledName;
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2015-04-15 16:19:11 +03:00
|
|
|
if (item->_instruction == CSYNC_INSTRUCTION_NONE
|
|
|
|
|| (item->_instruction == CSYNC_INSTRUCTION_IGNORE && instruction != CSYNC_INSTRUCTION_NONE)) {
|
2017-06-23 17:53:23 +03:00
|
|
|
// Take values from side (local/remote) where instruction is not _NONE
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_instruction = instruction;
|
|
|
|
item->_modtime = file->modtime;
|
2017-06-23 17:53:23 +03:00
|
|
|
item->_size = file->size;
|
2017-09-15 14:30:29 +03:00
|
|
|
item->_checksumHeader = file->checksumHeader;
|
2018-01-17 12:59:47 +03:00
|
|
|
item->_type = file->type;
|
2014-08-07 12:14:14 +04:00
|
|
|
} else {
|
2015-01-05 15:54:31 +03:00
|
|
|
if (instruction != CSYNC_INSTRUCTION_NONE) {
|
2017-05-09 15:24:11 +03:00
|
|
|
qCWarning(lcEngine) << "ERROR: Instruction" << item->_instruction << "vs" << instruction << "for" << fileUtf8;
|
2017-02-07 15:52:15 +03:00
|
|
|
ASSERT(false);
|
|
|
|
// Set instruction to NONE for safety.
|
|
|
|
file->instruction = item->_instruction = instruction = CSYNC_INSTRUCTION_NONE;
|
|
|
|
return -1; // should lead to treewalk error
|
2014-08-07 12:14:14 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-17 15:39:23 +03:00
|
|
|
if (!file->file_id.isEmpty()) {
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_fileId = file->file_id;
|
2014-08-07 12:14:14 +04:00
|
|
|
}
|
2017-08-17 15:39:23 +03:00
|
|
|
if (!file->directDownloadUrl.isEmpty()) {
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_directDownloadUrl = QString::fromUtf8(file->directDownloadUrl);
|
2014-06-03 13:50:13 +04:00
|
|
|
}
|
2017-08-17 15:39:23 +03:00
|
|
|
if (!file->directDownloadCookies.isEmpty()) {
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_directDownloadCookies = QString::fromUtf8(file->directDownloadCookies);
|
2014-06-03 13:50:13 +04:00
|
|
|
}
|
2017-09-19 11:53:51 +03:00
|
|
|
if (!file->remotePerm.isNull()) {
|
2017-08-17 15:39:23 +03:00
|
|
|
item->_remotePerm = file->remotePerm;
|
2014-06-06 17:24:17 +04:00
|
|
|
}
|
2015-07-13 16:42:16 +03:00
|
|
|
|
|
|
|
/* The flag "serverHasIgnoredFiles" is true if item in question is a directory
|
|
|
|
* that has children which are ignored in sync, either because the files are
|
2015-10-05 06:20:09 +03:00
|
|
|
* matched by an ignore pattern, or because they are hidden.
|
2015-07-13 16:42:16 +03:00
|
|
|
*
|
|
|
|
* Only the information about the server side ignored files is stored to the
|
|
|
|
* database and thus written to the item here. For the local repository its
|
|
|
|
* generated by the walk through the real file tree by discovery phase.
|
|
|
|
*
|
|
|
|
* It needs to go to the sync journal becasue the stat information about remote
|
|
|
|
* files are often read from database rather than being pulled from remote.
|
|
|
|
*/
|
|
|
|
if (remote) {
|
2017-08-17 11:06:14 +03:00
|
|
|
item->_serverHasIgnoredFiles = file->has_ignored_files;
|
2015-07-13 16:42:16 +03:00
|
|
|
}
|
|
|
|
|
2013-11-20 16:44:01 +04:00
|
|
|
// record the seen files to be able to clean the journal later
|
2015-04-15 16:19:11 +03:00
|
|
|
_seenFiles.insert(item->_file);
|
2015-02-19 17:00:37 +03:00
|
|
|
if (!renameTarget.isEmpty()) {
|
2015-10-05 06:20:09 +03:00
|
|
|
// Yes, this records both the rename renameTarget and the original so we keep both in case of a rename
|
2015-02-19 17:00:37 +03:00
|
|
|
_seenFiles.insert(renameTarget);
|
|
|
|
}
|
2013-11-11 14:11:45 +04:00
|
|
|
|
2013-12-04 15:19:38 +04:00
|
|
|
switch (file->error_status) {
|
|
|
|
case CSYNC_STATUS_OK:
|
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK:
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_errorString = tr("Symbolic links are not supported in syncing.");
|
2013-12-04 15:19:38 +04:00
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_INDIVIDUAL_IGNORE_LIST:
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_errorString = tr("File is listed on the ignore list.");
|
2013-12-04 15:19:38 +04:00
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS:
|
2017-01-18 13:45:46 +03:00
|
|
|
if (item->_file.endsWith('.')) {
|
|
|
|
item->_errorString = tr("File names ending with a period are not supported on this file system.");
|
|
|
|
} else {
|
|
|
|
char invalid = '\0';
|
|
|
|
foreach (char x, QByteArray("\\:?*\"<>|")) {
|
|
|
|
if (item->_file.contains(x)) {
|
|
|
|
invalid = x;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (invalid) {
|
|
|
|
item->_errorString = tr("File names containing the character '%1' are not supported on this file system.")
|
|
|
|
.arg(QLatin1Char(invalid));
|
|
|
|
} else {
|
|
|
|
item->_errorString = tr("The file name is a reserved name on this file system.");
|
|
|
|
}
|
|
|
|
}
|
2013-12-04 15:19:38 +04:00
|
|
|
break;
|
2016-06-09 13:40:31 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_TRAILING_SPACE:
|
|
|
|
item->_errorString = tr("Filename contains trailing spaces.");
|
|
|
|
break;
|
2015-02-27 13:25:03 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_EXCLUDE_LONG_FILENAME:
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_errorString = tr("Filename is too long.");
|
2015-02-27 13:25:03 +03:00
|
|
|
break;
|
2015-07-08 19:06:38 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN:
|
2016-02-22 13:13:29 +03:00
|
|
|
item->_errorString = tr("File/Folder is ignored because it's hidden.");
|
2015-07-08 19:06:38 +03:00
|
|
|
break;
|
2017-04-28 14:16:19 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_TOO_DEEP:
|
|
|
|
item->_errorString = tr("Folder hierarchy is too deep");
|
|
|
|
break;
|
2017-11-21 16:16:01 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE:
|
|
|
|
item->_errorString = tr("The filename cannot be encoded on your file system.");
|
|
|
|
break;
|
2017-07-12 11:57:41 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE:
|
|
|
|
item->_status = SyncFileItem::Conflict;
|
2017-12-02 13:40:43 +03:00
|
|
|
if (account()->capabilities().uploadConflictFiles()) {
|
2017-09-18 11:23:20 +03:00
|
|
|
// For uploaded conflict files, files with no action performed on them should
|
|
|
|
// be displayed: but we mustn't overwrite the instruction if something happens
|
|
|
|
// to the file!
|
|
|
|
if (remote && item->_instruction == CSYNC_INSTRUCTION_NONE) {
|
|
|
|
item->_errorString = tr("Unresolved conflict.");
|
|
|
|
item->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
item->_errorString = tr("Conflict: Server version downloaded, local copy renamed and not uploaded.");
|
|
|
|
}
|
2017-07-12 11:57:41 +03:00
|
|
|
break;
|
2015-07-17 17:48:33 +03:00
|
|
|
case CSYNC_STATUS_INDIVIDUAL_STAT_FAILED:
|
2015-08-11 12:34:40 +03:00
|
|
|
item->_errorString = tr("Stat failed.");
|
2015-07-17 17:48:33 +03:00
|
|
|
break;
|
2014-10-27 21:21:12 +03:00
|
|
|
case CSYNC_STATUS_SERVICE_UNAVAILABLE:
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_errorString = QLatin1String("Server temporarily unavailable.");
|
2015-02-25 10:57:39 +03:00
|
|
|
break;
|
|
|
|
case CSYNC_STATUS_STORAGE_UNAVAILABLE:
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_errorString = QLatin1String("Directory temporarily not available on server.");
|
|
|
|
item->_status = SyncFileItem::SoftError;
|
|
|
|
_temporarilyUnavailablePaths.insert(item->_file);
|
2014-10-27 21:21:12 +03:00
|
|
|
break;
|
2015-10-12 18:06:49 +03:00
|
|
|
case CSYNC_STATUS_FORBIDDEN:
|
|
|
|
item->_errorString = QLatin1String("Access forbidden.");
|
|
|
|
item->_status = SyncFileItem::SoftError;
|
|
|
|
_temporarilyUnavailablePaths.insert(item->_file);
|
|
|
|
break;
|
2015-09-09 15:12:13 +03:00
|
|
|
case CSYNC_STATUS_PERMISSION_DENIED:
|
|
|
|
item->_errorString = QLatin1String("Directory not accessible on client, permission denied.");
|
|
|
|
item->_status = SyncFileItem::SoftError;
|
|
|
|
break;
|
2013-12-04 15:19:38 +04:00
|
|
|
default:
|
2017-02-07 15:52:15 +03:00
|
|
|
ASSERT(false, "Non handled error-status");
|
2013-12-04 15:19:38 +04:00
|
|
|
/* No error string */
|
2013-09-02 19:25:07 +04:00
|
|
|
}
|
2014-12-06 14:27:50 +03:00
|
|
|
|
2018-02-19 15:41:57 +03:00
|
|
|
if (item->_instruction == CSYNC_INSTRUCTION_IGNORE && utf8DecodeError) {
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_status = SyncFileItem::NormalError;
|
|
|
|
//item->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
item->_errorString = tr("Filename encoding is not valid");
|
2014-12-06 14:27:50 +03:00
|
|
|
}
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2017-12-14 17:08:53 +03:00
|
|
|
bool isDirectory = file->type == ItemTypeDirectory;
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2017-08-17 15:39:23 +03:00
|
|
|
if (!file->etag.isEmpty()) {
|
2015-10-21 15:17:30 +03:00
|
|
|
item->_etag = file->etag;
|
|
|
|
}
|
2017-06-23 17:53:23 +03:00
|
|
|
|
2014-05-06 14:55:54 +04:00
|
|
|
|
2016-01-20 15:44:30 +03:00
|
|
|
if (!item->_inode) {
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_inode = file->inode;
|
2014-08-07 12:14:14 +04:00
|
|
|
}
|
|
|
|
|
2015-07-09 16:57:56 +03:00
|
|
|
SyncFileItem::Direction dir = SyncFileItem::None;
|
2013-01-15 23:41:52 +04:00
|
|
|
|
|
|
|
int re = 0;
|
|
|
|
switch (file->instruction) {
|
2015-11-23 13:53:06 +03:00
|
|
|
case CSYNC_INSTRUCTION_NONE: {
|
2016-08-15 15:17:51 +03:00
|
|
|
// Any files that are instruction NONE?
|
2017-09-20 12:03:37 +03:00
|
|
|
if (!isDirectory && (!other || other->instruction == CSYNC_INSTRUCTION_NONE || other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA)) {
|
2016-08-15 15:17:51 +03:00
|
|
|
_hasNoneFiles = true;
|
|
|
|
}
|
2017-09-18 11:23:20 +03:00
|
|
|
// Put none-instruction conflict files into the syncfileitem list
|
2017-12-02 13:40:43 +03:00
|
|
|
if (account()->capabilities().uploadConflictFiles()
|
2017-09-18 11:23:20 +03:00
|
|
|
&& file->error_status == CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE
|
|
|
|
&& item->_instruction == CSYNC_INSTRUCTION_IGNORE) {
|
|
|
|
break;
|
|
|
|
}
|
2016-08-15 15:17:51 +03:00
|
|
|
// No syncing or update to be done.
|
|
|
|
return re;
|
|
|
|
}
|
|
|
|
case CSYNC_INSTRUCTION_UPDATE_METADATA:
|
|
|
|
dir = SyncFileItem::None;
|
|
|
|
// For directories, metadata-only updates will be done after all their files are propagated.
|
|
|
|
if (!isDirectory) {
|
|
|
|
|
2015-11-23 13:53:06 +03:00
|
|
|
// Update the database now already: New remote fileid or Etag or RemotePerm
|
2014-11-06 16:54:59 +03:00
|
|
|
// Or for files that were detected as "resolved conflict".
|
2016-08-15 15:17:51 +03:00
|
|
|
// Or a local inode/mtime change
|
2015-10-29 16:10:11 +03:00
|
|
|
|
|
|
|
// In case of "resolved conflict": there should have been a conflict because they
|
|
|
|
// both were new, or both had their local mtime or remote etag modified, but the
|
|
|
|
// size and mtime is the same on the server. This typically happens when the
|
|
|
|
// database is removed. Nothing will be done for those files, but we still need
|
|
|
|
// to update the database.
|
|
|
|
|
|
|
|
// This metadata update *could* be a propagation job of its own, but since it's
|
|
|
|
// quick to do and we don't want to create a potentially large number of
|
|
|
|
// mini-jobs later on, we just update metadata right now.
|
|
|
|
|
2016-08-15 15:17:51 +03:00
|
|
|
if (remote) {
|
|
|
|
QString filePath = _localPath + item->_file;
|
2014-11-06 16:54:59 +03:00
|
|
|
|
2017-08-17 15:39:23 +03:00
|
|
|
if (other) {
|
|
|
|
// Even if the mtime is different on the server, we always want to keep the mtime from
|
|
|
|
// the file system in the DB, this is to avoid spurious upload on the next sync
|
|
|
|
item->_modtime = other->modtime;
|
|
|
|
// same for the size
|
2017-09-13 07:26:56 +03:00
|
|
|
item->_size = other->size;
|
2017-08-17 15:39:23 +03:00
|
|
|
}
|
2015-10-29 16:10:11 +03:00
|
|
|
|
2016-08-15 15:17:51 +03:00
|
|
|
// If the 'W' remote permission changed, update the local filesystem
|
2017-09-13 20:02:38 +03:00
|
|
|
SyncJournalFileRecord prev;
|
|
|
|
if (_journal->getFileRecord(item->_file, &prev)
|
|
|
|
&& prev.isValid()
|
|
|
|
&& prev._remotePerm.hasPermission(RemotePermissions::CanWrite) != item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) {
|
2017-09-19 11:53:51 +03:00
|
|
|
const bool isReadOnly = !item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite);
|
2016-08-15 15:17:51 +03:00
|
|
|
FileSystem::setFileReadOnlyWeak(filePath, isReadOnly);
|
|
|
|
}
|
2015-11-23 13:53:06 +03:00
|
|
|
|
2017-08-30 12:17:23 +03:00
|
|
|
_journal->setFileRecordMetadata(item->toSyncJournalFileRecordWithInode(filePath));
|
2017-10-23 17:35:35 +03:00
|
|
|
|
|
|
|
// This might have changed the shared flag, so we must notify SyncFileStatusTracker for example
|
|
|
|
emit itemCompleted(item);
|
2016-08-15 15:17:51 +03:00
|
|
|
} else {
|
|
|
|
// The local tree is walked first and doesn't have all the info from the server.
|
|
|
|
// Update only outdated data from the disk.
|
|
|
|
_journal->updateLocalMetadata(item->_file, item->_modtime, item->_size, item->_inode);
|
2015-02-17 18:41:44 +03:00
|
|
|
}
|
2016-08-15 15:17:51 +03:00
|
|
|
|
2017-09-20 12:03:37 +03:00
|
|
|
if (!other || other->instruction == CSYNC_INSTRUCTION_NONE || other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {
|
|
|
|
_hasNoneFiles = true;
|
|
|
|
}
|
|
|
|
|
2016-08-15 15:17:51 +03:00
|
|
|
// Technically we're done with this item.
|
2013-10-03 22:00:58 +04:00
|
|
|
return re;
|
|
|
|
}
|
2013-01-15 23:41:52 +04:00
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_RENAME:
|
|
|
|
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_renameTarget = renameTarget;
|
2016-01-06 12:01:22 +03:00
|
|
|
if (isDirectory)
|
2015-04-15 16:19:11 +03:00
|
|
|
_renamedFolders.insert(item->_file, item->_renameTarget);
|
2013-01-15 23:41:52 +04:00
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_REMOVE:
|
2014-07-15 13:22:16 +04:00
|
|
|
_hasRemoveFile = true;
|
2013-01-15 23:41:52 +04:00
|
|
|
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
|
2014-07-15 13:22:16 +04:00
|
|
|
break;
|
2013-01-15 23:41:52 +04:00
|
|
|
case CSYNC_INSTRUCTION_CONFLICT:
|
|
|
|
case CSYNC_INSTRUCTION_ERROR:
|
|
|
|
dir = SyncFileItem::None;
|
|
|
|
break;
|
2016-01-06 12:01:22 +03:00
|
|
|
case CSYNC_INSTRUCTION_TYPE_CHANGE:
|
2013-01-15 23:41:52 +04:00
|
|
|
case CSYNC_INSTRUCTION_SYNC:
|
2016-01-05 13:47:17 +03:00
|
|
|
if (!remote) {
|
2014-07-15 13:22:16 +04:00
|
|
|
// An upload of an existing file means that the file was left unchanged on the server
|
2015-10-05 06:20:09 +03:00
|
|
|
// This counts as a NONE for detecting if all the files on the server were changed
|
2014-07-15 13:22:16 +04:00
|
|
|
_hasNoneFiles = true;
|
2016-05-24 16:17:35 +03:00
|
|
|
} else if (!isDirectory) {
|
2017-08-17 15:39:23 +03:00
|
|
|
auto difftime = std::difftime(file->modtime, other ? other->modtime : 0);
|
2016-10-21 11:32:19 +03:00
|
|
|
if (difftime < -3600 * 2) {
|
2016-01-05 13:47:17 +03:00
|
|
|
// We are going back on time
|
2016-10-21 11:32:19 +03:00
|
|
|
// We only increment if the difference is more than two hours to avoid clock skew
|
|
|
|
// issues or DST changes. (We simply ignore files that goes in the past less than
|
|
|
|
// two hours for the backup detection heuristics.)
|
2016-01-05 13:47:17 +03:00
|
|
|
_backInTimeFiles++;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << file->path << "has a timestamp earlier than the local file";
|
2016-10-21 11:32:19 +03:00
|
|
|
} else if (difftime > 0) {
|
2016-01-05 13:47:17 +03:00
|
|
|
_hasForwardInTimeFiles = true;
|
|
|
|
}
|
2014-07-15 13:22:16 +04:00
|
|
|
}
|
2016-01-05 13:47:17 +03:00
|
|
|
dir = remote ? SyncFileItem::Down : SyncFileItem::Up;
|
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_NEW:
|
|
|
|
case CSYNC_INSTRUCTION_EVAL:
|
|
|
|
case CSYNC_INSTRUCTION_STAT_ERROR:
|
2018-02-23 14:13:42 +03:00
|
|
|
case CSYNC_INSTRUCTION_IGNORE:
|
2016-01-05 13:47:17 +03:00
|
|
|
default:
|
|
|
|
dir = remote ? SyncFileItem::Down : SyncFileItem::Up;
|
2013-01-15 23:41:52 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-04-15 16:19:11 +03:00
|
|
|
item->_direction = dir;
|
2015-03-24 14:17:48 +03:00
|
|
|
if (instruction != CSYNC_INSTRUCTION_NONE) {
|
|
|
|
// check for blacklisting of this item.
|
|
|
|
// if the item is on blacklist, the instruction was set to ERROR
|
2015-04-15 16:19:11 +03:00
|
|
|
checkErrorBlacklisting(*item);
|
2015-03-24 14:17:48 +03:00
|
|
|
}
|
2013-11-29 19:15:26 +04:00
|
|
|
|
2014-03-14 21:29:23 +04:00
|
|
|
_needsUpdate = true;
|
|
|
|
|
2017-08-17 15:39:23 +03:00
|
|
|
if (other) {
|
2017-08-24 18:07:22 +03:00
|
|
|
item->_previousModtime = other->modtime;
|
|
|
|
item->_previousSize = other->size;
|
2017-08-17 15:39:23 +03:00
|
|
|
}
|
2014-03-26 21:06:25 +04:00
|
|
|
|
2017-12-02 13:40:43 +03:00
|
|
|
slotNewItem(item);
|
2015-02-17 20:40:17 +03:00
|
|
|
_syncItemMap.insert(key, item);
|
2013-01-15 23:41:52 +04:00
|
|
|
return re;
|
|
|
|
}
|
|
|
|
|
2014-03-17 14:34:51 +04:00
|
|
|
void SyncEngine::handleSyncError(CSYNC *ctx, const char *state)
|
|
|
|
{
|
2013-11-25 01:21:29 +04:00
|
|
|
CSYNC_STATUS err = csync_get_status(ctx);
|
2013-08-19 18:15:20 +04:00
|
|
|
const char *errMsg = csync_get_status_string(ctx);
|
2013-08-21 17:29:04 +04:00
|
|
|
QString errStr = csyncErrorToString(err);
|
2013-05-05 13:41:31 +04:00
|
|
|
if (errMsg) {
|
2013-11-25 21:22:41 +04:00
|
|
|
if (!errStr.endsWith(" ")) {
|
|
|
|
errStr.append(" ");
|
|
|
|
}
|
2013-05-05 13:41:31 +04:00
|
|
|
errStr += QString::fromUtf8(errMsg);
|
|
|
|
}
|
2015-08-04 23:13:38 +03:00
|
|
|
// Special handling CSYNC_STATUS_INVALID_CHARACTERS
|
|
|
|
if (err == CSYNC_STATUS_INVALID_CHARACTERS) {
|
|
|
|
errStr = tr("Invalid characters, please rename \"%1\"").arg(errMsg);
|
|
|
|
}
|
2013-11-25 21:22:41 +04:00
|
|
|
|
|
|
|
// if there is csyncs url modifier in the error message, replace it.
|
|
|
|
if (errStr.contains("ownclouds://"))
|
|
|
|
errStr.replace("ownclouds://", "https://");
|
|
|
|
if (errStr.contains("owncloud://"))
|
|
|
|
errStr.replace("owncloud://", "http://");
|
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "ERROR during " << state << ": " << errStr;
|
2013-08-19 18:15:20 +04:00
|
|
|
|
2013-11-25 01:21:29 +04:00
|
|
|
if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_ABORTED)) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Update phase was aborted by user!";
|
2018-03-06 09:04:02 +03:00
|
|
|
} else if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_SERVICE_UNAVAILABLE)) {
|
2013-02-15 22:29:27 +04:00
|
|
|
emit csyncUnavailable();
|
2013-08-19 18:15:20 +04:00
|
|
|
} else {
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(errStr);
|
2013-02-15 22:29:27 +04:00
|
|
|
}
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2013-02-15 22:29:27 +04:00
|
|
|
}
|
2013-01-15 23:41:52 +04:00
|
|
|
|
2017-07-11 16:54:01 +03:00
|
|
|
void SyncEngine::csyncError(const QString &message)
|
|
|
|
{
|
|
|
|
emit syncError(message, ErrorCategory::Normal);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-17 14:34:51 +04:00
|
|
|
void SyncEngine::startSync()
|
2012-02-15 12:30:37 +04:00
|
|
|
{
|
2014-07-28 14:12:52 +04:00
|
|
|
if (_journal->exists()) {
|
|
|
|
QVector<SyncJournalDb::PollInfo> pollInfos = _journal->getPollInfos();
|
|
|
|
if (!pollInfos.isEmpty()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Finish Poll jobs before starting a sync";
|
2014-12-18 14:09:48 +03:00
|
|
|
CleanupPollsJob *job = new CleanupPollsJob(pollInfos, _account,
|
2014-07-28 14:12:52 +04:00
|
|
|
_journal, _localPath, this);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(job, &CleanupPollsJob::finished, this, &SyncEngine::startSync);
|
|
|
|
connect(job, &CleanupPollsJob::aborted, this, &SyncEngine::slotCleanPollsJobAborted);
|
2014-07-28 14:12:52 +04:00
|
|
|
job->start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-07 15:52:15 +03:00
|
|
|
if (s_anySyncRunning || _syncRunning) {
|
|
|
|
ASSERT(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-09 19:53:18 +03:00
|
|
|
s_anySyncRunning = true;
|
2014-05-29 13:34:25 +04:00
|
|
|
_syncRunning = true;
|
2016-12-06 15:18:59 +03:00
|
|
|
_anotherSyncNeeded = NoFollowUpSync;
|
2016-06-09 13:07:18 +03:00
|
|
|
_clearTouchedFilesTimer.stop();
|
2013-04-04 19:14:38 +04:00
|
|
|
|
2016-05-20 16:07:54 +03:00
|
|
|
_progressInfo->reset();
|
|
|
|
|
2014-08-28 13:47:40 +04:00
|
|
|
if (!QDir(_localPath).exists()) {
|
2016-12-06 15:18:59 +03:00
|
|
|
_anotherSyncNeeded = DelayedFollowUp;
|
2014-08-28 13:47:40 +04:00
|
|
|
// No _tr, it should only occur in non-mirall
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError("Unable to find local sync folder.");
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2014-08-28 13:47:40 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-30 16:07:45 +03:00
|
|
|
// Check free size on disk first.
|
2015-10-01 12:39:09 +03:00
|
|
|
const qint64 minFree = criticalFreeSpaceLimit();
|
2015-09-30 16:07:45 +03:00
|
|
|
const qint64 freeBytes = Utility::freeDiskSpace(_localPath);
|
|
|
|
if (freeBytes >= 0) {
|
|
|
|
if (freeBytes < minFree) {
|
2017-09-22 12:06:29 +03:00
|
|
|
qCWarning(lcEngine()) << "Too little space available at" << _localPath << ". Have"
|
|
|
|
<< freeBytes << "bytes and require at least" << minFree << "bytes";
|
2016-12-06 15:18:59 +03:00
|
|
|
_anotherSyncNeeded = DelayedFollowUp;
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(tr("Only %1 are available, need at least %2 to start",
|
2016-01-06 18:50:59 +03:00
|
|
|
"Placeholders are postfixed with file sizes using Utility::octetsToString()")
|
2017-07-11 16:54:01 +03:00
|
|
|
.arg(
|
|
|
|
Utility::octetsToString(freeBytes),
|
|
|
|
Utility::octetsToString(minFree)));
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2015-09-30 16:07:45 +03:00
|
|
|
return;
|
2017-09-22 12:06:29 +03:00
|
|
|
} else {
|
|
|
|
qCInfo(lcEngine) << "There are" << freeBytes << "bytes available at" << _localPath;
|
2015-09-30 16:07:45 +03:00
|
|
|
}
|
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Could not determine free space available at" << _localPath;
|
2015-09-30 16:07:45 +03:00
|
|
|
}
|
|
|
|
|
2014-08-07 12:14:14 +04:00
|
|
|
_syncItemMap.clear();
|
2013-02-14 19:25:00 +04:00
|
|
|
_needsUpdate = false;
|
2014-01-20 19:29:26 +04:00
|
|
|
|
2017-09-04 20:06:13 +03:00
|
|
|
csync_resume(_csync_ctx.data());
|
2012-08-30 19:50:42 +04:00
|
|
|
|
2013-10-04 16:44:57 +04:00
|
|
|
if (!_journal->exists()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "New sync (no sync journal exists)";
|
2014-10-20 17:51:50 +04:00
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Sync with existing sync journal";
|
2014-10-20 17:51:50 +04:00
|
|
|
}
|
|
|
|
|
2017-05-09 15:24:11 +03:00
|
|
|
QString verStr("Using Qt ");
|
2016-07-28 17:30:40 +03:00
|
|
|
verStr.append(qVersion());
|
|
|
|
|
|
|
|
verStr.append(" SSL library ").append(QSslSocket::sslLibraryVersionString().toUtf8().data());
|
|
|
|
verStr.append(" on ").append(Utility::platformName());
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << verStr;
|
2014-11-12 11:21:05 +03:00
|
|
|
|
2017-09-14 17:34:14 +03:00
|
|
|
// This creates the DB if it does not exist yet.
|
|
|
|
if (!_journal->isConnected()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "No way to create a sync journal!";
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2014-10-20 17:51:50 +04:00
|
|
|
return;
|
|
|
|
// database creation error!
|
|
|
|
}
|
2014-10-20 16:25:22 +04:00
|
|
|
|
2017-12-02 13:40:43 +03:00
|
|
|
_csync_ctx->upload_conflict_files = _account->capabilities().uploadConflictFiles();
|
|
|
|
_excludedFiles->setExcludeConflictFiles(!_account->capabilities().uploadConflictFiles());
|
|
|
|
|
2015-05-13 14:15:53 +03:00
|
|
|
_csync_ctx->read_remote_from_db = true;
|
2018-02-21 15:22:54 +03:00
|
|
|
|
|
|
|
_lastLocalDiscoveryStyle = _localDiscoveryStyle;
|
|
|
|
_csync_ctx->should_discover_locally_fn = [this](const QByteArray &path) {
|
|
|
|
return shouldDiscoverLocally(path);
|
|
|
|
};
|
2014-10-20 17:51:50 +04:00
|
|
|
|
2016-04-06 16:01:28 +03:00
|
|
|
bool ok;
|
|
|
|
auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
|
|
|
|
if (ok) {
|
|
|
|
bool usingSelectiveSync = (!selectiveSyncBlackList.isEmpty());
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync");
|
2016-04-06 16:01:28 +03:00
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Could not retrieve selective sync list from DB";
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(tr("Unable to read the blacklist from the local database"));
|
2016-04-06 18:20:48 +03:00
|
|
|
finalize(false);
|
|
|
|
return;
|
2016-04-06 16:01:28 +03:00
|
|
|
}
|
2017-09-04 20:06:13 +03:00
|
|
|
csync_set_userdata(_csync_ctx.data(), this);
|
2014-05-02 15:04:53 +04:00
|
|
|
|
2015-11-23 13:53:06 +03:00
|
|
|
// Set up checksumming hook
|
|
|
|
_csync_ctx->callbacks.checksum_hook = &CSyncChecksumHook::hook;
|
|
|
|
_csync_ctx->callbacks.checksum_userdata = &_checksum_hook;
|
|
|
|
|
2014-03-26 21:06:25 +04:00
|
|
|
_stopWatch.start();
|
2017-07-11 13:52:40 +03:00
|
|
|
_progressInfo->_status = ProgressInfo::Starting;
|
|
|
|
emit transmissionProgress(*_progressInfo);
|
2014-02-19 00:41:20 +04:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "#### Discovery start ####################################################";
|
2017-07-11 13:52:40 +03:00
|
|
|
_progressInfo->_status = ProgressInfo::Discovery;
|
|
|
|
emit transmissionProgress(*_progressInfo);
|
2013-09-24 17:56:03 +04:00
|
|
|
|
2016-07-05 14:53:57 +03:00
|
|
|
// Usually the discovery runs in the background: We want to avoid
|
|
|
|
// stealing too much time from other processes that the user might
|
|
|
|
// be interacting with at the time.
|
|
|
|
_thread.start(QThread::LowPriority);
|
|
|
|
|
2014-12-02 14:25:51 +03:00
|
|
|
_discoveryMainThread = new DiscoveryMainThread(account());
|
|
|
|
_discoveryMainThread->setParent(this);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(this, &SyncEngine::finished, _discoveryMainThread.data(), &QObject::deleteLater);
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Server" << account()->serverVersion()
|
2017-05-22 15:41:06 +03:00
|
|
|
<< (account()->isHttp2Supported() ? "Using HTTP/2" : "");
|
2015-10-16 12:52:27 +03:00
|
|
|
if (account()->rootEtagChangesNotOnlySubFolderEtags()) {
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(_discoveryMainThread.data(), &DiscoveryMainThread::etag, this, &SyncEngine::slotRootEtagReceived);
|
2015-10-16 12:52:27 +03:00
|
|
|
} else {
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(_discoveryMainThread.data(), &DiscoveryMainThread::etagConcatenation, this, &SyncEngine::slotRootEtagReceived);
|
2015-10-16 12:52:27 +03:00
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
|
2017-09-04 20:06:13 +03:00
|
|
|
DiscoveryJob *discoveryJob = new DiscoveryJob(_csync_ctx.data());
|
2015-05-21 13:22:50 +03:00
|
|
|
discoveryJob->_selectiveSyncBlackList = selectiveSyncBlackList;
|
2015-05-21 15:50:30 +03:00
|
|
|
discoveryJob->_selectiveSyncWhiteList =
|
2016-04-06 16:01:28 +03:00
|
|
|
_journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok);
|
|
|
|
if (!ok) {
|
|
|
|
delete discoveryJob;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Unable to read selective sync list, aborting.";
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(tr("Unable to read from the sync journal."));
|
2016-04-06 18:20:48 +03:00
|
|
|
finalize(false);
|
2016-04-06 16:01:28 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-01-24 12:16:10 +03:00
|
|
|
discoveryJob->_syncOptions = _syncOptions;
|
2014-12-02 14:25:51 +03:00
|
|
|
discoveryJob->moveToThread(&_thread);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(discoveryJob, &DiscoveryJob::finished, this, &SyncEngine::slotDiscoveryJobFinished);
|
|
|
|
connect(discoveryJob, &DiscoveryJob::folderDiscovered,
|
|
|
|
this, &SyncEngine::slotFolderDiscovered);
|
2014-12-02 14:25:51 +03:00
|
|
|
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(discoveryJob, &DiscoveryJob::newBigFolder,
|
|
|
|
this, &SyncEngine::newBigFolder);
|
2015-05-21 15:50:30 +03:00
|
|
|
|
|
|
|
|
2014-12-02 14:25:51 +03:00
|
|
|
// This is used for the DiscoveryJob to be able to request the main thread/
|
|
|
|
// to read in directory contents.
|
2015-01-20 20:49:49 +03:00
|
|
|
_discoveryMainThread->setupHooks(discoveryJob, _remotePath);
|
2014-12-02 14:25:51 +03:00
|
|
|
|
|
|
|
// Starts the update in a seperate thread
|
|
|
|
QMetaObject::invokeMethod(discoveryJob, "start", Qt::QueuedConnection);
|
2014-02-05 23:18:03 +04:00
|
|
|
}
|
|
|
|
|
2018-02-21 11:39:55 +03:00
|
|
|
void SyncEngine::slotFolderDiscovered(bool local, const QString &folder)
|
2017-07-11 13:52:40 +03:00
|
|
|
{
|
2018-02-21 11:39:55 +03:00
|
|
|
// Currently remote and local discovery never run in parallel
|
|
|
|
// Note: Currently this slot is only called occasionally! See the throttling
|
|
|
|
// in DiscoveryJob::update_job_update_callback.
|
|
|
|
if (local) {
|
|
|
|
_progressInfo->_currentDiscoveredLocalFolder = folder;
|
|
|
|
_progressInfo->_currentDiscoveredRemoteFolder.clear();
|
|
|
|
} else {
|
|
|
|
_progressInfo->_currentDiscoveredRemoteFolder = folder;
|
|
|
|
_progressInfo->_currentDiscoveredLocalFolder.clear();
|
|
|
|
}
|
2017-07-11 13:52:40 +03:00
|
|
|
emit transmissionProgress(*_progressInfo);
|
|
|
|
}
|
|
|
|
|
2015-10-29 17:01:29 +03:00
|
|
|
void SyncEngine::slotRootEtagReceived(const QString &e)
|
|
|
|
{
|
2015-01-23 17:30:00 +03:00
|
|
|
if (_remoteRootEtag.isEmpty()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCDebug(lcEngine) << "Root etag:" << e;
|
2015-01-23 17:30:00 +03:00
|
|
|
_remoteRootEtag = e;
|
|
|
|
emit rootEtag(_remoteRootEtag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-02 13:40:43 +03:00
|
|
|
void SyncEngine::slotNewItem(const SyncFileItemPtr &item)
|
|
|
|
{
|
|
|
|
_progressInfo->adjustTotalsForFile(*item);
|
|
|
|
}
|
|
|
|
|
2014-08-11 19:47:16 +04:00
|
|
|
void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
2014-02-05 23:18:03 +04:00
|
|
|
{
|
2014-08-11 19:47:16 +04:00
|
|
|
if (discoveryResult < 0) {
|
2017-09-04 20:06:13 +03:00
|
|
|
handleSyncError(_csync_ctx.data(), "csync_update");
|
2013-02-15 22:29:27 +04:00
|
|
|
return;
|
2012-03-22 19:22:08 +04:00
|
|
|
}
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "#### Discovery end #################################################### " << _stopWatch.addLapTime(QLatin1String("Discovery Finished")) << "ms";
|
2012-02-15 12:30:37 +04:00
|
|
|
|
2014-10-20 18:50:55 +04:00
|
|
|
// Sanity check
|
|
|
|
if (!_journal->isConnected()) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Bailing out, DB failure";
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(tr("Cannot open the sync journal"));
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2014-10-20 18:50:55 +04:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// Commits a possibly existing (should not though) transaction and starts a new one for the propagate phase
|
2014-10-20 19:20:58 +04:00
|
|
|
_journal->commitIfNeededAndStartNewTransaction("Post discovery");
|
2014-10-20 18:50:55 +04:00
|
|
|
}
|
|
|
|
|
2018-02-21 11:39:55 +03:00
|
|
|
_progressInfo->_currentDiscoveredRemoteFolder.clear();
|
|
|
|
_progressInfo->_currentDiscoveredLocalFolder.clear();
|
2017-07-11 13:52:40 +03:00
|
|
|
_progressInfo->_status = ProgressInfo::Reconcile;
|
|
|
|
emit transmissionProgress(*_progressInfo);
|
|
|
|
|
2017-09-04 20:06:13 +03:00
|
|
|
if (csync_reconcile(_csync_ctx.data()) < 0) {
|
|
|
|
handleSyncError(_csync_ctx.data(), "csync_reconcile");
|
2013-02-15 22:29:27 +04:00
|
|
|
return;
|
2012-03-22 19:22:08 +04:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "#### Reconcile end #################################################### " << _stopWatch.addLapTime(QLatin1String("Reconcile Finished")) << "ms";
|
2013-11-25 19:16:33 +04:00
|
|
|
|
2014-07-15 13:22:16 +04:00
|
|
|
_hasNoneFiles = false;
|
|
|
|
_hasRemoveFile = false;
|
2016-08-04 17:06:10 +03:00
|
|
|
_hasForwardInTimeFiles = false;
|
|
|
|
_backInTimeFiles = 0;
|
2013-02-20 20:26:07 +04:00
|
|
|
bool walkOk = true;
|
2013-11-11 14:11:45 +04:00
|
|
|
_seenFiles.clear();
|
2015-04-08 11:50:08 +03:00
|
|
|
_temporarilyUnavailablePaths.clear();
|
2016-09-22 10:02:47 +03:00
|
|
|
_renamedFolders.clear();
|
2013-11-11 14:11:45 +04:00
|
|
|
|
2018-03-01 15:53:55 +03:00
|
|
|
if (csync_walk_local_tree(_csync_ctx.data(), [this](csync_file_stat_t *f, csync_file_stat_t *o) { return treewalkFile(f, o, false); } ) < 0) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Error in local treewalk.";
|
2013-02-20 20:26:07 +04:00
|
|
|
walkOk = false;
|
2013-01-15 23:41:52 +04:00
|
|
|
}
|
2018-03-01 15:53:55 +03:00
|
|
|
if (walkOk && csync_walk_remote_tree(_csync_ctx.data(), [this](csync_file_stat_t *f, csync_file_stat_t *o) { return treewalkFile(f, o, true); } ) < 0) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Error in remote treewalk.";
|
2013-01-15 23:41:52 +04:00
|
|
|
}
|
|
|
|
|
2017-09-19 11:53:51 +03:00
|
|
|
qCInfo(lcEngine) << "Permissions of the root folder: " << _csync_ctx->remote.root_perms.toString();
|
2015-03-02 17:08:21 +03:00
|
|
|
|
2014-08-07 12:14:14 +04:00
|
|
|
// The map was used for merging trees, convert it to a list:
|
2017-01-25 13:28:18 +03:00
|
|
|
SyncFileItemVector syncItems = _syncItemMap.values().toVector();
|
2015-03-02 17:08:21 +03:00
|
|
|
_syncItemMap.clear(); // free memory
|
2014-08-07 12:14:14 +04:00
|
|
|
|
2013-05-15 17:22:20 +04:00
|
|
|
// Adjust the paths for the renames.
|
2017-01-25 13:28:18 +03:00
|
|
|
for (SyncFileItemVector::iterator it = syncItems.begin();
|
|
|
|
it != syncItems.end(); ++it) {
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_file = adjustRenamedPath((*it)->_file);
|
2013-05-15 17:22:20 +04:00
|
|
|
}
|
|
|
|
|
2016-01-13 19:49:41 +03:00
|
|
|
// Check for invalid character in old server version
|
2017-10-13 15:56:40 +03:00
|
|
|
QString invalidFilenamePattern = _account->capabilities().invalidFilenameRegex();
|
|
|
|
if (invalidFilenamePattern.isNull()
|
|
|
|
&& _account->serverVersionInt() < Account::makeServerVersion(8, 1, 0)) {
|
|
|
|
// Server versions older than 8.1 don't support some characters in filenames.
|
|
|
|
// If the capability is not set, default to a pattern that avoids uploading
|
|
|
|
// files with names that contain these.
|
|
|
|
// It's important to respect the capability also for older servers -- the
|
|
|
|
// version check doesn't make sense for custom servers.
|
|
|
|
invalidFilenamePattern = "[\\\\:?*\"<>|]";
|
|
|
|
}
|
|
|
|
if (!invalidFilenamePattern.isEmpty()) {
|
|
|
|
const QRegExp invalidFilenameRx(invalidFilenamePattern);
|
2017-01-25 13:28:18 +03:00
|
|
|
for (auto it = syncItems.begin(); it != syncItems.end(); ++it) {
|
2018-02-23 14:13:42 +03:00
|
|
|
if ((*it)->_direction == SyncFileItem::Up
|
|
|
|
&& isFileModifyingInstruction((*it)->_instruction)
|
|
|
|
&& (*it)->destination().contains(invalidFilenameRx)) {
|
2016-01-13 19:49:41 +03:00
|
|
|
(*it)->_errorString = tr("File name contains at least one invalid character");
|
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 18:14:22 +03:00
|
|
|
if (!_hasNoneFiles && _hasRemoveFile) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "All the files are going to be changed, asking the user";
|
2016-01-05 13:47:17 +03:00
|
|
|
bool cancel = false;
|
2017-01-25 13:28:18 +03:00
|
|
|
emit aboutToRemoveAllFiles(syncItems.first()->_direction, &cancel);
|
2016-01-05 13:47:17 +03:00
|
|
|
if (cancel) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "User aborted sync";
|
2016-01-05 13:47:17 +03:00
|
|
|
finalize(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2016-08-02 11:30:49 +03:00
|
|
|
|
|
|
|
auto databaseFingerprint = _journal->dataFingerprint();
|
|
|
|
// If databaseFingerprint is null, this means that there was no information in the database
|
|
|
|
// (for example, upgrading from a previous version, or first sync)
|
|
|
|
// Note that an empty ("") fingerprint is valid and means it was empty on the server before.
|
|
|
|
if (!databaseFingerprint.isNull()
|
|
|
|
&& _discoveryMainThread->_dataFingerprint != databaseFingerprint) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint;
|
2017-01-25 13:28:18 +03:00
|
|
|
restoreOldFiles(syncItems);
|
2017-03-14 17:57:57 +03:00
|
|
|
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2
|
|
|
|
&& _account->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
|
2016-10-21 11:32:19 +03:00
|
|
|
// The server before ownCloud 9.1 did not have the data-fingerprint property. So in that
|
|
|
|
// case we use heuristics to detect restored backup. This is disabled with newer version
|
|
|
|
// because this causes troubles to the user and is not as reliable as the data-fingerprint.
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "All the changes are bringing files in the past, asking the user";
|
2016-01-05 13:47:17 +03:00
|
|
|
// this typically happen when a backup is restored on the server
|
|
|
|
bool restore = false;
|
|
|
|
emit aboutToRestoreBackup(&restore);
|
|
|
|
if (restore) {
|
2017-01-25 13:28:18 +03:00
|
|
|
restoreOldFiles(syncItems);
|
2016-01-05 13:47:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-27 15:34:15 +04:00
|
|
|
// Sort items per destination
|
2017-01-25 13:28:18 +03:00
|
|
|
std::sort(syncItems.begin(), syncItems.end());
|
2014-06-27 15:34:15 +04:00
|
|
|
|
2014-06-07 13:49:46 +04:00
|
|
|
// make sure everything is allowed
|
2017-01-25 13:28:18 +03:00
|
|
|
checkForPermission(syncItems);
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2017-08-24 17:48:40 +03:00
|
|
|
// Re-init the csync context to free memory
|
|
|
|
_csync_ctx->reinitialize();
|
2018-02-21 15:22:54 +03:00
|
|
|
_localDiscoveryPaths.clear();
|
2017-08-24 17:48:40 +03:00
|
|
|
|
2014-03-14 21:43:23 +04:00
|
|
|
// To announce the beginning of the sync
|
2017-01-25 13:28:18 +03:00
|
|
|
emit aboutToPropagate(syncItems);
|
2017-07-11 13:52:40 +03:00
|
|
|
|
2015-01-30 15:36:20 +03:00
|
|
|
// it's important to do this before ProgressInfo::start(), to announce start of new sync
|
2017-07-11 13:52:40 +03:00
|
|
|
_progressInfo->_status = ProgressInfo::Propagation;
|
2015-01-30 15:36:20 +03:00
|
|
|
emit transmissionProgress(*_progressInfo);
|
2016-05-20 16:07:54 +03:00
|
|
|
_progressInfo->startEstimateUpdates();
|
2014-03-14 21:29:23 +04:00
|
|
|
|
2014-07-22 20:07:02 +04:00
|
|
|
// post update phase script: allow to tweak stuff by a custom script in debug mode.
|
2017-09-29 11:31:41 +03:00
|
|
|
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_POST_UPDATE_SCRIPT")) {
|
2014-10-15 18:43:58 +04:00
|
|
|
#ifndef NDEBUG
|
2014-07-22 20:07:02 +04:00
|
|
|
QString script = qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT");
|
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCDebug(lcEngine) << "Post Update Script: " << script;
|
2014-07-22 20:07:02 +04:00
|
|
|
QProcess::execute(script.toUtf8());
|
2014-10-15 18:43:58 +04:00
|
|
|
#else
|
2017-05-09 15:24:11 +03:00
|
|
|
qCWarning(lcEngine) << "**** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG";
|
2014-07-22 20:07:02 +04:00
|
|
|
#endif
|
2014-10-15 18:43:58 +04:00
|
|
|
}
|
2014-08-14 13:28:34 +04:00
|
|
|
|
|
|
|
// do a database commit
|
|
|
|
_journal->commit("post treewalk");
|
|
|
|
|
2014-11-25 18:24:47 +03:00
|
|
|
_propagator = QSharedPointer<OwncloudPropagator>(
|
2016-11-15 20:47:04 +03:00
|
|
|
new OwncloudPropagator(_account, _localPath, _remotePath, _journal));
|
2017-03-24 17:01:50 +03:00
|
|
|
_propagator->setSyncOptions(_syncOptions);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(_propagator.data(), &OwncloudPropagator::itemCompleted,
|
|
|
|
this, &SyncEngine::slotItemCompleted);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::progress,
|
|
|
|
this, &SyncEngine::slotProgress);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::finished, this, &SyncEngine::slotFinished, Qt::QueuedConnection);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::touchedFile, this, &SyncEngine::slotAddTouchedFile);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage);
|
|
|
|
connect(_propagator.data(), &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage);
|
2017-12-02 13:40:43 +03:00
|
|
|
connect(_propagator.data(), &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem);
|
2013-05-16 15:54:22 +04:00
|
|
|
|
2014-06-07 13:49:41 +04:00
|
|
|
// apply the network limits to the propagator
|
|
|
|
setNetworkLimits(_uploadLimit, _downloadLimit);
|
2014-01-31 20:29:50 +04:00
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
deleteStaleDownloadInfos(syncItems);
|
|
|
|
deleteStaleUploadInfos(syncItems);
|
|
|
|
deleteStaleErrorBlacklistEntries(syncItems);
|
2014-09-03 14:11:03 +04:00
|
|
|
_journal->commit("post stale entry removal");
|
|
|
|
|
2014-11-07 13:41:21 +03:00
|
|
|
// Emit the started signal only after the propagator has been set up.
|
|
|
|
if (_needsUpdate)
|
|
|
|
emit(started());
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
_propagator->start(syncItems);
|
2015-04-02 16:18:19 +03:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QLatin1String("Post-Reconcile Finished")) << "ms";
|
2014-01-31 20:29:50 +04:00
|
|
|
}
|
|
|
|
|
2014-07-29 17:51:22 +04:00
|
|
|
void SyncEngine::slotCleanPollsJobAborted(const QString &error)
|
|
|
|
{
|
|
|
|
csyncError(error);
|
2015-10-29 18:43:30 +03:00
|
|
|
finalize(false);
|
2014-07-29 17:51:22 +04:00
|
|
|
}
|
|
|
|
|
2014-06-07 13:49:41 +04:00
|
|
|
void SyncEngine::setNetworkLimits(int upload, int download)
|
2014-01-31 20:29:50 +04:00
|
|
|
{
|
2014-06-07 13:49:41 +04:00
|
|
|
_uploadLimit = upload;
|
|
|
|
_downloadLimit = download;
|
2014-01-31 20:29:50 +04:00
|
|
|
|
|
|
|
if (!_propagator)
|
|
|
|
return;
|
|
|
|
|
2014-06-07 13:49:41 +04:00
|
|
|
_propagator->_uploadLimit = upload;
|
|
|
|
_propagator->_downloadLimit = download;
|
2013-08-14 21:59:16 +04:00
|
|
|
|
2017-09-13 07:26:56 +03:00
|
|
|
int propDownloadLimit = _propagator->_downloadLimit.load();
|
|
|
|
int propUploadLimit = _propagator->_uploadLimit.load();
|
2014-04-07 18:48:14 +04:00
|
|
|
|
2014-04-15 18:15:33 +04:00
|
|
|
if (propDownloadLimit != 0 || propUploadLimit != 0) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "Network Limits (down/up) " << propDownloadLimit << propUploadLimit;
|
2014-04-07 17:10:44 +04:00
|
|
|
}
|
2013-05-16 15:54:22 +04:00
|
|
|
}
|
2013-05-07 19:47:29 +04:00
|
|
|
|
2017-01-25 13:12:38 +03:00
|
|
|
void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item)
|
2013-05-16 15:54:22 +04:00
|
|
|
{
|
2017-01-25 13:12:38 +03:00
|
|
|
_progressInfo->setProgressComplete(*item);
|
2014-03-14 16:03:16 +04:00
|
|
|
|
2017-01-25 13:12:38 +03:00
|
|
|
if (item->_status == SyncFileItem::FatalError) {
|
2017-07-11 16:54:01 +03:00
|
|
|
csyncError(item->_errorString);
|
2013-10-04 17:13:36 +04:00
|
|
|
}
|
2014-03-14 16:03:16 +04:00
|
|
|
|
2015-01-30 15:36:20 +03:00
|
|
|
emit transmissionProgress(*_progressInfo);
|
2017-01-25 16:09:44 +03:00
|
|
|
emit itemCompleted(item);
|
2013-05-16 15:54:22 +04:00
|
|
|
}
|
2013-05-06 18:59:11 +04:00
|
|
|
|
2016-08-02 11:30:49 +03:00
|
|
|
void SyncEngine::slotFinished(bool success)
|
2013-05-16 15:54:22 +04:00
|
|
|
{
|
2016-12-06 15:18:59 +03:00
|
|
|
if (_propagator->_anotherSyncNeeded && _anotherSyncNeeded == NoFollowUpSync) {
|
|
|
|
_anotherSyncNeeded = ImmediateFollowUp;
|
|
|
|
}
|
2014-09-10 19:25:13 +04:00
|
|
|
|
2016-08-02 11:30:49 +03:00
|
|
|
if (success) {
|
|
|
|
_journal->setDataFingerprint(_discoveryMainThread->_dataFingerprint);
|
|
|
|
}
|
|
|
|
|
2015-04-08 11:50:08 +03:00
|
|
|
if (!_journal->postSyncCleanup(_seenFiles, _temporarilyUnavailablePaths)) {
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcEngine) << "Cleaning of synced ";
|
2013-11-11 14:11:45 +04:00
|
|
|
}
|
2014-06-17 18:29:38 +04:00
|
|
|
|
2017-12-02 13:40:43 +03:00
|
|
|
conflictRecordMaintenance();
|
|
|
|
|
2013-11-21 14:13:58 +04:00
|
|
|
_journal->commit("All Finished.", false);
|
2016-11-18 14:30:41 +03:00
|
|
|
|
|
|
|
// Send final progress information even if no
|
2017-02-16 17:09:06 +03:00
|
|
|
// files needed propagation, but clear the lastCompletedItem
|
|
|
|
// so we don't count this twice (like Recent Files)
|
|
|
|
_progressInfo->_lastCompletedItem = SyncFileItem();
|
2017-07-11 13:52:40 +03:00
|
|
|
_progressInfo->_status = ProgressInfo::Done;
|
2016-11-18 14:30:41 +03:00
|
|
|
emit transmissionProgress(*_progressInfo);
|
|
|
|
|
2016-08-02 11:30:49 +03:00
|
|
|
finalize(success);
|
2014-05-20 14:28:55 +04:00
|
|
|
}
|
2013-05-16 15:54:22 +04:00
|
|
|
|
2015-10-29 18:43:30 +03:00
|
|
|
void SyncEngine::finalize(bool success)
|
2014-05-20 14:28:55 +04:00
|
|
|
{
|
2014-06-18 17:04:55 +04:00
|
|
|
_thread.quit();
|
|
|
|
_thread.wait();
|
2015-03-02 17:08:21 +03:00
|
|
|
|
2017-09-04 20:06:13 +03:00
|
|
|
_csync_ctx->reinitialize();
|
2016-04-11 12:31:54 +03:00
|
|
|
_journal->close();
|
2013-05-16 15:54:22 +04:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcEngine) << "CSync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished")) << "ms";
|
2014-03-26 21:06:25 +04:00
|
|
|
_stopWatch.stop();
|
|
|
|
|
2016-03-09 19:53:18 +03:00
|
|
|
s_anySyncRunning = false;
|
2014-05-29 13:34:25 +04:00
|
|
|
_syncRunning = false;
|
2015-10-29 18:43:30 +03:00
|
|
|
emit finished(success);
|
2014-11-07 13:41:21 +03:00
|
|
|
|
|
|
|
// Delete the propagator only after emitting the signal.
|
2014-11-25 18:24:47 +03:00
|
|
|
_propagator.clear();
|
2017-01-26 16:25:31 +03:00
|
|
|
_seenFiles.clear();
|
|
|
|
_temporarilyUnavailablePaths.clear();
|
|
|
|
_renamedFolders.clear();
|
2017-06-28 13:45:54 +03:00
|
|
|
_uniqueErrors.clear();
|
2018-02-21 15:22:54 +03:00
|
|
|
_localDiscoveryPaths.clear();
|
|
|
|
_localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly;
|
2016-06-09 13:07:18 +03:00
|
|
|
|
|
|
|
_clearTouchedFilesTimer.start();
|
2012-02-28 19:49:13 +04:00
|
|
|
}
|
|
|
|
|
2014-03-17 14:34:51 +04:00
|
|
|
void SyncEngine::slotProgress(const SyncFileItem &item, quint64 current)
|
2014-01-07 18:42:21 +04:00
|
|
|
{
|
2015-01-30 15:36:20 +03:00
|
|
|
_progressInfo->setProgressItem(item, current);
|
|
|
|
emit transmissionProgress(*_progressInfo);
|
2014-01-07 18:42:21 +04:00
|
|
|
}
|
|
|
|
|
2013-11-26 15:22:28 +04:00
|
|
|
|
2013-05-15 17:22:20 +04:00
|
|
|
/* Given a path on the remote, give the path as it is when the rename is done */
|
2014-03-17 14:34:51 +04:00
|
|
|
QString SyncEngine::adjustRenamedPath(const QString &original)
|
2013-05-15 17:22:20 +04:00
|
|
|
{
|
|
|
|
int slashPos = original.size();
|
|
|
|
while ((slashPos = original.lastIndexOf('/', slashPos - 1)) > 0) {
|
|
|
|
QHash<QString, QString>::const_iterator it = _renamedFolders.constFind(original.left(slashPos));
|
|
|
|
if (it != _renamedFolders.constEnd()) {
|
|
|
|
return *it + original.mid(slashPos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return original;
|
|
|
|
}
|
2013-10-02 21:41:17 +04:00
|
|
|
|
2014-10-09 18:43:56 +04:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Make sure that we are allowed to do what we do by checking the permissions and the selective sync list
|
|
|
|
*
|
|
|
|
*/
|
2017-01-25 13:28:18 +03:00
|
|
|
void SyncEngine::checkForPermission(SyncFileItemVector &syncItems)
|
2014-06-07 13:49:46 +04:00
|
|
|
{
|
2016-04-06 16:01:28 +03:00
|
|
|
bool selectiveListOk;
|
|
|
|
auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &selectiveListOk);
|
2015-05-21 13:22:50 +03:00
|
|
|
std::sort(selectiveSyncBlackList.begin(), selectiveSyncBlackList.end());
|
2017-06-06 17:00:41 +03:00
|
|
|
SyncFileItemPtr needle;
|
2015-05-21 13:22:50 +03:00
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
for (SyncFileItemVector::iterator it = syncItems.begin(); it != syncItems.end(); ++it) {
|
2018-02-23 14:13:42 +03:00
|
|
|
if ((*it)->_direction != SyncFileItem::Up
|
|
|
|
|| !isFileModifyingInstruction((*it)->_instruction)) {
|
2014-06-07 13:49:46 +04:00
|
|
|
// Currently we only check server-side permissions
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-10-09 18:43:56 +04:00
|
|
|
// Do not propagate anything in the server if it is in the selective sync blacklist
|
2015-04-15 16:19:11 +03:00
|
|
|
const QString path = (*it)->destination() + QLatin1Char('/');
|
2016-04-06 16:01:28 +03:00
|
|
|
|
|
|
|
// if reading the selective sync list from db failed, lets ignore all rather than nothing.
|
|
|
|
if (!selectiveListOk || std::binary_search(selectiveSyncBlackList.constBegin(), selectiveSyncBlackList.constEnd(),
|
2014-10-09 18:43:56 +04:00
|
|
|
path)) {
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
(*it)->_status = SyncFileItem::FileIgnored;
|
|
|
|
(*it)->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist");
|
2014-10-09 18:43:56 +04:00
|
|
|
|
2017-08-24 18:31:46 +03:00
|
|
|
if ((*it)->isDirectory()) {
|
2017-06-06 17:00:41 +03:00
|
|
|
auto it_base = it;
|
2017-01-25 13:28:18 +03:00
|
|
|
for (SyncFileItemVector::iterator it_next = it + 1; it_next != syncItems.end() && (*it_next)->_file.startsWith(path); ++it_next) {
|
2014-10-09 18:43:56 +04:00
|
|
|
it = it_next;
|
2017-06-06 17:00:41 +03:00
|
|
|
// We want to ignore almost all instructions for items inside selective-sync excluded folders.
|
|
|
|
//The exception are DOWN/REMOVE actions that remove local files and folders that are
|
|
|
|
//guaranteed to be up-to-date with their server copies.
|
|
|
|
if ((*it)->_direction == SyncFileItem::Down && (*it)->_instruction == CSYNC_INSTRUCTION_REMOVE) {
|
|
|
|
// We need to keep the "delete" items. So we need to un-ignore parent directories
|
|
|
|
QString parentDir = (*it)->_file;
|
|
|
|
do {
|
|
|
|
parentDir = QFileInfo(parentDir).path();
|
|
|
|
if (parentDir.isEmpty() || !parentDir.startsWith((*it_base)->destination())) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Find the parent directory in the syncItems vector. Since the vector
|
|
|
|
// is sorted we can use a lower_bound, but we need a fake
|
|
|
|
// SyncFileItemPtr needle to compare against
|
|
|
|
if (!needle)
|
|
|
|
needle = SyncFileItemPtr::create();
|
|
|
|
needle->_file = parentDir;
|
|
|
|
auto parent_it = std::lower_bound(it_base, it, needle);
|
|
|
|
if (parent_it == syncItems.end() || (*parent_it)->destination() != parentDir) {
|
|
|
|
break;
|
|
|
|
}
|
2017-08-24 18:31:46 +03:00
|
|
|
ASSERT((*parent_it)->isDirectory());
|
2017-06-06 17:00:41 +03:00
|
|
|
if ((*parent_it)->_instruction != CSYNC_INSTRUCTION_IGNORE) {
|
|
|
|
break; // already changed
|
|
|
|
}
|
|
|
|
(*parent_it)->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
|
|
|
(*parent_it)->_status = SyncFileItem::NoStatus;
|
|
|
|
(*parent_it)->_errorString.clear();
|
|
|
|
|
|
|
|
} while (true);
|
|
|
|
continue;
|
|
|
|
}
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
|
|
|
(*it)->_status = SyncFileItem::FileIgnored;
|
|
|
|
(*it)->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist");
|
2014-10-09 18:43:56 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-15 16:19:11 +03:00
|
|
|
switch ((*it)->_instruction) {
|
2016-01-06 12:01:22 +03:00
|
|
|
case CSYNC_INSTRUCTION_TYPE_CHANGE:
|
2014-06-07 13:49:46 +04:00
|
|
|
case CSYNC_INSTRUCTION_NEW: {
|
2015-04-15 16:19:11 +03:00
|
|
|
int slashPos = (*it)->_file.lastIndexOf('/');
|
|
|
|
QString parentDir = slashPos <= 0 ? "" : (*it)->_file.mid(0, slashPos);
|
2017-09-19 11:53:51 +03:00
|
|
|
const auto perms = getPermissions(parentDir);
|
2014-06-07 13:49:46 +04:00
|
|
|
if (perms.isNull()) {
|
|
|
|
// No permissions set
|
|
|
|
break;
|
2017-09-19 11:53:51 +03:00
|
|
|
} else if ((*it)->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddSubDirectories)) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: ERROR" << (*it)->_file;
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
(*it)->_status = SyncFileItem::NormalError;
|
2015-09-14 14:57:40 +03:00
|
|
|
(*it)->_errorString = tr("Not allowed because you don't have permission to add subfolders to that folder");
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
for (SyncFileItemVector::iterator it_next = it + 1; it_next != syncItems.end() && (*it_next)->destination().startsWith(path); ++it_next) {
|
2014-06-07 13:49:46 +04:00
|
|
|
it = it_next;
|
2016-04-04 11:41:12 +03:00
|
|
|
if ((*it)->_instruction == CSYNC_INSTRUCTION_RENAME) {
|
|
|
|
// The file was most likely moved in this directory.
|
|
|
|
// If the file was read only or could not be moved or removed, it should
|
|
|
|
// be restored. Do that in the next sync by not considering as a rename
|
|
|
|
// but delete and upload. It will then be restored if needed.
|
|
|
|
_journal->avoidRenamesOnNextSync((*it)->_file);
|
2016-12-06 15:18:59 +03:00
|
|
|
_anotherSyncNeeded = ImmediateFollowUp;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "Moving of " << (*it)->_file << " canceled because no permission to add parent folder";
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
(*it)->_status = SyncFileItem::SoftError;
|
2015-09-07 09:51:22 +03:00
|
|
|
(*it)->_errorString = tr("Not allowed because you don't have permission to add parent folder");
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2017-09-19 11:53:51 +03:00
|
|
|
} else if (!(*it)->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddFile)) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: ERROR" << (*it)->_file;
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
(*it)->_status = SyncFileItem::NormalError;
|
2014-06-07 13:49:46 +04:00
|
|
|
(*it)->_errorString = tr("Not allowed because you don't have permission to add files in that folder");
|
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
break;
|
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
case CSYNC_INSTRUCTION_SYNC: {
|
2017-09-19 11:53:51 +03:00
|
|
|
const auto perms = getPermissions((*it)->_file);
|
2016-08-15 15:17:51 +03:00
|
|
|
if (perms.isNull()) {
|
2014-06-18 18:15:14 +04:00
|
|
|
// No permissions set
|
2014-06-07 13:49:46 +04:00
|
|
|
break;
|
|
|
|
}
|
2017-09-19 11:53:51 +03:00
|
|
|
if (!perms.hasPermission(RemotePermissions::CanWrite)) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: RESTORING" << (*it)->_file;
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_CONFLICT;
|
|
|
|
(*it)->_direction = SyncFileItem::Down;
|
|
|
|
(*it)->_isRestoration = true;
|
2017-08-24 18:07:22 +03:00
|
|
|
// Take the things to write to the db from the "other" node (i.e: info from server).
|
|
|
|
// Do a lookup into the csync remote tree to get the metadata we need to restore.
|
|
|
|
ASSERT(_csync_ctx->status != CSYNC_STATUS_INIT);
|
|
|
|
auto csyncIt = _csync_ctx->remote.files.find((*it)->_file.toUtf8());
|
|
|
|
if (csyncIt != _csync_ctx->remote.files.end()) {
|
|
|
|
(*it)->_modtime = csyncIt->second->modtime;
|
|
|
|
(*it)->_size = csyncIt->second->size;
|
|
|
|
(*it)->_fileId = csyncIt->second->file_id;
|
|
|
|
(*it)->_etag = csyncIt->second->etag;
|
|
|
|
}
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_errorString = tr("Not allowed to upload this file because it is read-only on the server, restoring");
|
2014-10-09 18:43:56 +04:00
|
|
|
continue;
|
|
|
|
}
|
2014-09-25 17:36:28 +04:00
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-09-25 17:36:28 +04:00
|
|
|
case CSYNC_INSTRUCTION_REMOVE: {
|
2017-09-19 11:53:51 +03:00
|
|
|
const auto perms = getPermissions((*it)->_file);
|
2017-01-25 13:28:18 +03:00
|
|
|
if (perms.isNull()) {
|
|
|
|
// No permissions set
|
2014-06-07 13:49:46 +04:00
|
|
|
break;
|
|
|
|
}
|
2017-09-19 11:53:51 +03:00
|
|
|
if (!perms.hasPermission(RemotePermissions::CanDelete)) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: RESTORING" << (*it)->_file;
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_NEW;
|
|
|
|
(*it)->_direction = SyncFileItem::Down;
|
|
|
|
(*it)->_isRestoration = true;
|
|
|
|
(*it)->_errorString = tr("Not allowed to remove, restoring");
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2017-08-24 18:31:46 +03:00
|
|
|
if ((*it)->isDirectory()) {
|
2014-06-07 13:49:46 +04:00
|
|
|
// restore all sub items
|
|
|
|
for (SyncFileItemVector::iterator it_next = it + 1;
|
2017-01-25 13:28:18 +03:00
|
|
|
it_next != syncItems.end() && (*it_next)->_file.startsWith(path); ++it_next) {
|
2014-06-07 13:49:46 +04:00
|
|
|
it = it_next;
|
|
|
|
|
|
|
|
if ((*it)->_instruction != CSYNC_INSTRUCTION_REMOVE) {
|
|
|
|
qCWarning(lcEngine) << "non-removed job within a removed folder"
|
2015-04-15 16:19:11 +03:00
|
|
|
<< (*it)->_file << (*it)->_instruction;
|
|
|
|
continue;
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: RESTORING" << (*it)->_file;
|
2014-06-07 13:49:46 +04:00
|
|
|
|
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_NEW;
|
|
|
|
(*it)->_direction = SyncFileItem::Down;
|
|
|
|
(*it)->_isRestoration = true;
|
|
|
|
(*it)->_errorString = tr("Not allowed to remove, restoring");
|
2015-04-15 16:19:11 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
2017-09-19 11:53:51 +03:00
|
|
|
} else if (perms.hasPermission(RemotePermissions::IsShared)
|
|
|
|
&& perms.hasPermission(RemotePermissions::CanDelete)) {
|
2014-09-25 17:36:28 +04:00
|
|
|
// this is a top level shared dir which can be removed to unshare it,
|
|
|
|
// regardless if it is a read only share or not.
|
|
|
|
// To avoid that we try to restore files underneath this dir which have
|
|
|
|
// not delete permission we fast forward the iterator and leave the
|
|
|
|
// delete jobs intact. It is not physically tried to remove this files
|
|
|
|
// underneath, propagator sees that.
|
2017-08-24 18:31:46 +03:00
|
|
|
if ((*it)->isDirectory()) {
|
2015-10-05 06:20:09 +03:00
|
|
|
// put a more descriptive message if a top level share dir really is removed.
|
2017-01-25 13:28:18 +03:00
|
|
|
if (it == syncItems.begin() || !(path.startsWith((*(it - 1))->_file))) {
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_errorString = tr("Local files and share folder removed.");
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
|
|
|
for (SyncFileItemVector::iterator it_next = it + 1;
|
2014-08-07 16:27:27 +04:00
|
|
|
it_next != syncItems.end() && (*it_next)->_file.startsWith(path); ++it_next) {
|
2014-09-25 17:36:28 +04:00
|
|
|
it = it_next;
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-07 16:27:27 +04:00
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2016-01-05 13:47:17 +03:00
|
|
|
case CSYNC_INSTRUCTION_RENAME: {
|
2015-04-15 16:19:11 +03:00
|
|
|
int slashPos = (*it)->_renameTarget.lastIndexOf('/');
|
2014-06-07 13:49:46 +04:00
|
|
|
const QString parentDir = slashPos <= 0 ? "" : (*it)->_renameTarget.mid(0, slashPos);
|
2017-09-19 11:53:51 +03:00
|
|
|
const auto destPerms = getPermissions(parentDir);
|
|
|
|
const auto filePerms = getPermissions((*it)->_file);
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2014-06-07 13:49:46 +04:00
|
|
|
//true when it is just a rename in the same directory. (not a move)
|
2015-04-15 16:19:11 +03:00
|
|
|
bool isRename = (*it)->_file.startsWith(parentDir) && (*it)->_file.lastIndexOf('/') == slashPos;
|
2017-05-17 11:55:42 +03:00
|
|
|
|
|
|
|
|
2014-06-07 13:49:46 +04:00
|
|
|
// Check if we are allowed to move to the destination.
|
|
|
|
bool destinationOK = true;
|
|
|
|
if (isRename || destPerms.isNull()) {
|
|
|
|
// no need to check for the destination dir permission
|
|
|
|
destinationOK = true;
|
2017-09-19 11:53:51 +03:00
|
|
|
} else if ((*it)->isDirectory() && !destPerms.hasPermission(RemotePermissions::CanAddSubDirectories)) {
|
2014-06-07 13:49:46 +04:00
|
|
|
destinationOK = false;
|
2017-09-19 11:53:51 +03:00
|
|
|
} else if (!(*it)->isDirectory() && !destPerms.hasPermission(RemotePermissions::CanAddFile)) {
|
2014-06-07 13:49:46 +04:00
|
|
|
destinationOK = false;
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
|
|
|
// check if we are allowed to move from the source
|
|
|
|
bool sourceOK = true;
|
|
|
|
if (!filePerms.isNull()
|
2017-09-19 11:53:51 +03:00
|
|
|
&& ((isRename && !filePerms.hasPermission(RemotePermissions::CanRename))
|
|
|
|
|| (!isRename && !filePerms.hasPermission(RemotePermissions::CanMove)))) {
|
2016-05-12 14:14:07 +03:00
|
|
|
// We are not allowed to move or rename this file
|
2014-06-07 13:49:46 +04:00
|
|
|
sourceOK = false;
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2017-09-19 11:53:51 +03:00
|
|
|
if (filePerms.hasPermission(RemotePermissions::CanDelete) && destinationOK) {
|
2014-06-07 13:49:46 +04:00
|
|
|
// but we are allowed to delete it
|
|
|
|
// TODO! simulate delete & upload
|
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
|
2016-05-12 14:14:07 +03:00
|
|
|
#ifdef OWNCLOUD_RESTORE_RENAME /* We don't like the idea of renaming behind user's back, as the user may be working with the files */
|
|
|
|
if (!sourceOK && (!destinationOK || isRename)
|
|
|
|
// (not for directory because that's more complicated with the contents that needs to be adjusted)
|
2017-08-24 18:31:46 +03:00
|
|
|
&& !(*it)->isDirectory()) {
|
2014-06-07 13:49:46 +04:00
|
|
|
// Both the source and the destination won't allow move. Move back to the original
|
2015-04-15 16:19:11 +03:00
|
|
|
std::swap((*it)->_file, (*it)->_renameTarget);
|
|
|
|
(*it)->_direction = SyncFileItem::Down;
|
|
|
|
(*it)->_errorString = tr("Move not allowed, item restored");
|
|
|
|
(*it)->_isRestoration = true;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: MOVING BACK" << (*it)->_file;
|
2016-05-12 14:14:07 +03:00
|
|
|
// in case something does wrong, we will not do it next time
|
|
|
|
_journal->avoidRenamesOnNextSync((*it)->_file);
|
2014-06-27 17:26:12 +04:00
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
if (!sourceOK || !destinationOK) {
|
2014-06-07 13:49:46 +04:00
|
|
|
// One of them is not possible, just throw an error
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
(*it)->_status = SyncFileItem::NormalError;
|
2014-06-07 13:49:46 +04:00
|
|
|
const QString errorString = tr("Move not allowed because %1 is read-only").arg(sourceOK ? tr("the destination") : tr("the source"));
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_errorString = errorString;
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: ERROR MOVING" << (*it)->_file << errorString;
|
2017-05-17 11:55:42 +03:00
|
|
|
|
2014-06-27 17:26:12 +04:00
|
|
|
// Avoid a rename on next sync:
|
|
|
|
// TODO: do the resolution now already so we don't need two sync
|
|
|
|
// At this point we would need to go back to the propagate phase on both remote to take
|
|
|
|
// the decision.
|
2015-04-15 16:19:11 +03:00
|
|
|
_journal->avoidRenamesOnNextSync((*it)->_file);
|
2016-12-06 15:18:59 +03:00
|
|
|
_anotherSyncNeeded = ImmediateFollowUp;
|
2017-05-17 11:55:42 +03:00
|
|
|
|
|
|
|
|
2017-08-24 18:31:46 +03:00
|
|
|
if ((*it)->isDirectory()) {
|
2014-06-07 13:49:46 +04:00
|
|
|
for (SyncFileItemVector::iterator it_next = it + 1;
|
2017-01-25 13:28:18 +03:00
|
|
|
it_next != syncItems.end() && (*it_next)->destination().startsWith(path); ++it_next) {
|
2014-06-07 13:49:46 +04:00
|
|
|
it = it_next;
|
2015-04-15 16:19:11 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
|
|
|
|
(*it)->_status = SyncFileItem::NormalError;
|
|
|
|
(*it)->_errorString = errorString;
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "checkForPermission: ERROR MOVING" << (*it)->_file;
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
break;
|
|
|
|
}
|
2014-06-07 13:49:46 +04:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-19 11:53:51 +03:00
|
|
|
RemotePermissions SyncEngine::getPermissions(const QString &file) const
|
2014-06-07 13:49:46 +04:00
|
|
|
{
|
2017-09-29 11:31:41 +03:00
|
|
|
static bool isTest = qEnvironmentVariableIntValue("OWNCLOUD_TEST_PERMISSIONS");
|
2014-06-07 13:49:46 +04:00
|
|
|
if (isTest) {
|
|
|
|
QRegExp rx("_PERM_([^_]*)_[^/]*$");
|
|
|
|
if (rx.indexIn(file) != -1) {
|
2017-09-19 11:53:51 +03:00
|
|
|
return RemotePermissions(rx.cap(1));
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
|
|
|
}
|
2017-08-24 17:48:40 +03:00
|
|
|
|
|
|
|
// Fetch from the csync context while we still have it.
|
|
|
|
ASSERT(_csync_ctx->status != CSYNC_STATUS_INIT);
|
|
|
|
|
|
|
|
if (file == QLatin1String(""))
|
|
|
|
return _csync_ctx->remote.root_perms;
|
|
|
|
|
|
|
|
auto it = _csync_ctx->remote.files.find(file.toUtf8());
|
|
|
|
if (it != _csync_ctx->remote.files.end()) {
|
|
|
|
return it->second->remotePerm;
|
|
|
|
}
|
2017-09-19 11:53:51 +03:00
|
|
|
return RemotePermissions();
|
2014-06-07 13:49:46 +04:00
|
|
|
}
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
void SyncEngine::restoreOldFiles(SyncFileItemVector &syncItems)
|
2016-01-05 13:47:17 +03:00
|
|
|
{
|
|
|
|
/* When the server is trying to send us lots of file in the past, this means that a backup
|
|
|
|
was restored in the server. In that case, we should not simply overwrite the newer file
|
|
|
|
on the file system with the older file from the backup on the server. Instead, we will
|
|
|
|
upload the client file. But we still downloaded the old file in a conflict file just in case
|
|
|
|
*/
|
|
|
|
|
2017-01-25 13:28:18 +03:00
|
|
|
for (auto it = syncItems.begin(); it != syncItems.end(); ++it) {
|
2016-01-05 13:47:17 +03:00
|
|
|
if ((*it)->_direction != SyncFileItem::Down)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch ((*it)->_instruction) {
|
|
|
|
case CSYNC_INSTRUCTION_SYNC:
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "restoreOldFiles: RESTORING" << (*it)->_file;
|
2016-01-05 13:47:17 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_CONFLICT;
|
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_REMOVE:
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcEngine) << "restoreOldFiles: RESTORING" << (*it)->_file;
|
2016-01-05 13:47:17 +03:00
|
|
|
(*it)->_instruction = CSYNC_INSTRUCTION_NEW;
|
|
|
|
(*it)->_direction = SyncFileItem::Up;
|
|
|
|
break;
|
|
|
|
case CSYNC_INSTRUCTION_RENAME:
|
|
|
|
case CSYNC_INSTRUCTION_NEW:
|
|
|
|
// Ideally we should try to revert the rename or remove, but this would be dangerous
|
|
|
|
// without re-doing the reconcile phase. So just let it happen.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-09 13:07:18 +03:00
|
|
|
void SyncEngine::slotAddTouchedFile(const QString &fn)
|
|
|
|
{
|
2017-07-24 09:03:05 +03:00
|
|
|
QElapsedTimer now;
|
|
|
|
now.start();
|
2016-06-09 13:07:18 +03:00
|
|
|
QString file = QDir::cleanPath(fn);
|
|
|
|
|
2017-07-24 09:03:05 +03:00
|
|
|
// Iterate from the oldest and remove anything older than 15 seconds.
|
|
|
|
while (true) {
|
|
|
|
auto first = _touchedFiles.begin();
|
|
|
|
if (first == _touchedFiles.end())
|
|
|
|
break;
|
|
|
|
// Compare to our new QElapsedTimer instead of using elapsed().
|
|
|
|
// This avoids querying the current time from the OS for every loop.
|
|
|
|
if (now.msecsSinceReference() - first.key().msecsSinceReference() <= s_touchedFilesMaxAgeMs) {
|
|
|
|
// We found the first path younger than 15 second, keep the rest.
|
|
|
|
break;
|
|
|
|
}
|
2016-06-09 13:07:18 +03:00
|
|
|
|
2017-07-24 09:03:05 +03:00
|
|
|
_touchedFiles.erase(first);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should be the largest QElapsedTimer yet, use constEnd() as hint.
|
|
|
|
_touchedFiles.insert(_touchedFiles.constEnd(), now, file);
|
2016-06-09 13:07:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void SyncEngine::slotClearTouchedFiles()
|
|
|
|
{
|
|
|
|
_touchedFiles.clear();
|
|
|
|
}
|
|
|
|
|
2017-07-24 09:03:05 +03:00
|
|
|
bool SyncEngine::wasFileTouched(const QString &fn) const
|
2014-11-07 13:41:21 +03:00
|
|
|
{
|
2017-07-24 09:03:05 +03:00
|
|
|
// Start from the end (most recent) and look for our path. Check the time just in case.
|
|
|
|
auto begin = _touchedFiles.constBegin();
|
|
|
|
for (auto it = _touchedFiles.constEnd(); it != begin; --it) {
|
|
|
|
if ((it-1).value() == fn)
|
|
|
|
return (it-1).key().elapsed() <= s_touchedFilesMaxAgeMs;
|
2014-11-07 13:41:21 +03:00
|
|
|
}
|
2017-07-24 09:03:05 +03:00
|
|
|
return false;
|
2014-11-07 13:41:21 +03:00
|
|
|
}
|
|
|
|
|
2014-12-18 17:39:20 +03:00
|
|
|
AccountPtr SyncEngine::account() const
|
|
|
|
{
|
|
|
|
return _account;
|
|
|
|
}
|
|
|
|
|
2017-09-14 17:43:23 +03:00
|
|
|
void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> dirs)
|
|
|
|
{
|
2018-02-21 15:22:54 +03:00
|
|
|
_localDiscoveryStyle = style;
|
|
|
|
_localDiscoveryPaths = std::move(dirs);
|
2017-09-14 17:43:23 +03:00
|
|
|
}
|
|
|
|
|
2018-02-21 15:22:54 +03:00
|
|
|
bool SyncEngine::shouldDiscoverLocally(const QByteArray &path) const
|
2018-02-21 14:18:52 +03:00
|
|
|
{
|
2018-02-21 15:22:54 +03:00
|
|
|
if (_localDiscoveryStyle == LocalDiscoveryStyle::FilesystemOnly)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
auto it = _localDiscoveryPaths.lower_bound(path);
|
|
|
|
if (it == _localDiscoveryPaths.end() || !it->startsWith(path))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// maybe an exact match or an empty path?
|
|
|
|
if (it->size() == path.size() || path.isEmpty())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// check for a prefix + / match
|
|
|
|
forever {
|
|
|
|
if (it->size() > path.size() && it->at(path.size()) == '/')
|
|
|
|
return true;
|
|
|
|
++it;
|
|
|
|
if (it == _localDiscoveryPaths.end() || !it->startsWith(path))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
2017-09-14 17:43:23 +03:00
|
|
|
}
|
|
|
|
|
2014-03-17 14:34:51 +04:00
|
|
|
void SyncEngine::abort()
|
2013-10-02 21:41:17 +04:00
|
|
|
{
|
2017-05-11 16:36:47 +03:00
|
|
|
if (_propagator)
|
|
|
|
qCInfo(lcEngine) << "Aborting sync";
|
|
|
|
|
2016-06-15 18:01:00 +03:00
|
|
|
// Sets a flag for the update phase
|
2017-09-04 20:06:13 +03:00
|
|
|
csync_request_abort(_csync_ctx.data());
|
2017-05-11 16:36:47 +03:00
|
|
|
|
2014-12-02 14:25:51 +03:00
|
|
|
// Aborts the discovery phase job
|
|
|
|
if (_discoveryMainThread) {
|
|
|
|
_discoveryMainThread->abort();
|
|
|
|
}
|
|
|
|
// For the propagator
|
|
|
|
if (_propagator) {
|
2014-02-06 17:52:56 +04:00
|
|
|
_propagator->abort();
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
2013-10-02 21:41:17 +04:00
|
|
|
}
|
|
|
|
|
2017-06-28 13:45:54 +03:00
|
|
|
void SyncEngine::slotSummaryError(const QString &message)
|
|
|
|
{
|
|
|
|
if (_uniqueErrors.contains(message))
|
|
|
|
return;
|
|
|
|
|
|
|
|
_uniqueErrors.insert(message);
|
2017-07-11 16:54:01 +03:00
|
|
|
emit syncError(message, ErrorCategory::Normal);
|
2017-06-28 13:45:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void SyncEngine::slotInsufficientLocalStorage()
|
|
|
|
{
|
|
|
|
slotSummaryError(
|
|
|
|
tr("Disk space is low: Downloads that would reduce free space "
|
|
|
|
"below %1 were skipped.")
|
|
|
|
.arg(Utility::octetsToString(freeSpaceLimit())));
|
|
|
|
}
|
|
|
|
|
2017-07-07 16:11:00 +03:00
|
|
|
void SyncEngine::slotInsufficientRemoteStorage()
|
|
|
|
{
|
2017-07-11 16:54:01 +03:00
|
|
|
auto msg = tr("There is insufficient space available on the server for some uploads.");
|
|
|
|
if (_uniqueErrors.contains(msg))
|
|
|
|
return;
|
|
|
|
|
|
|
|
_uniqueErrors.insert(msg);
|
|
|
|
emit syncError(msg, ErrorCategory::InsufficientRemoteStorage);
|
2017-07-07 16:11:00 +03:00
|
|
|
}
|
|
|
|
|
2014-11-10 00:34:07 +03:00
|
|
|
} // namespace OCC
|