_rootJob;
diff --git a/src/libsync/owncloudtheme.cpp b/src/libsync/owncloudtheme.cpp
index 5528fe92d3..4f30571b24 100644
--- a/src/libsync/owncloudtheme.cpp
+++ b/src/libsync/owncloudtheme.cpp
@@ -44,7 +44,7 @@ QString ownCloudTheme::about() const
{
QString devString;
devString = trUtf8("Version %2. For more information visit https://%4
"
- "For known issues and help, please visit: https://central.owncloud.org
"
+ "For known issues and help, please visit: https://central.owncloud.org
"
"By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, "
" Jan-Christoph Borchardt, and others.
"
"Copyright ownCloud GmbH
"
diff --git a/src/libsync/progressdispatcher.cpp b/src/libsync/progressdispatcher.cpp
index 8473bc3ada..d5b3f20f50 100644
--- a/src/libsync/progressdispatcher.cpp
+++ b/src/libsync/progressdispatcher.cpp
@@ -90,7 +90,8 @@ bool Progress::isWarningKind(SyncFileItem::Status kind)
{
return kind == SyncFileItem::SoftError || kind == SyncFileItem::NormalError
|| kind == SyncFileItem::FatalError || kind == SyncFileItem::FileIgnored
- || kind == SyncFileItem::Conflict || kind == SyncFileItem::Restoration;
+ || kind == SyncFileItem::Conflict || kind == SyncFileItem::Restoration
+ || kind == SyncFileItem::BlacklistedError;
}
bool Progress::isIgnoredKind(SyncFileItem::Status kind)
@@ -135,6 +136,8 @@ ProgressInfo::ProgressInfo()
void ProgressInfo::reset()
{
+ _status = Starting;
+
_currentItems.clear();
_currentDiscoveredFolder.clear();
_sizeProgress = Progress();
@@ -150,6 +153,11 @@ void ProgressInfo::reset()
_lastCompletedItem = SyncFileItem();
}
+ProgressInfo::Status ProgressInfo::status() const
+{
+ return _status;
+}
+
void ProgressInfo::startEstimateUpdates()
{
_updateEstimatesTimer.start(1000);
diff --git a/src/libsync/progressdispatcher.h b/src/libsync/progressdispatcher.h
index 05aa06b4ff..f7015f6c55 100644
--- a/src/libsync/progressdispatcher.h
+++ b/src/libsync/progressdispatcher.h
@@ -41,6 +41,35 @@ public:
*/
void reset();
+ /** Records the status of the sync run
+ */
+ enum Status {
+ /// Emitted once at start
+ Starting,
+
+ /**
+ * Emitted once without _currentDiscoveredFolder when it starts,
+ * then for each folder.
+ */
+ Discovery,
+
+ /// Emitted once when reconcile starts
+ Reconcile,
+
+ /// Emitted during propagation, with progress data
+ Propagation,
+
+ /**
+ * Emitted once when done
+ *
+ * Except when SyncEngine jumps directly to finalize() without going
+ * through slotFinished().
+ */
+ Done
+ };
+
+ Status status() const;
+
/**
* Called when propagation starts.
*
@@ -140,6 +169,8 @@ public:
friend class ProgressInfo;
};
+ Status _status;
+
struct OWNCLOUDSYNC_EXPORT ProgressItem
{
SyncFileItem _item;
@@ -217,6 +248,16 @@ namespace Progress {
OWNCLOUDSYNC_EXPORT bool isIgnoredKind(SyncFileItem::Status);
}
+/** Type of error
+ *
+ * Used for ProgressDispatcher::syncError. May trigger error interactivity
+ * in IssuesWidget.
+ */
+enum class ErrorCategory {
+ Normal,
+ InsufficientRemoteStorage,
+};
+
/**
* @file progressdispatcher.h
* @brief A singleton class to provide sync progress information to other gui classes.
@@ -249,6 +290,11 @@ signals:
*/
void itemCompleted(const QString &folder, const SyncFileItemPtr &item);
+ /**
+ * @brief A new folder-wide sync error was seen.
+ */
+ void syncError(const QString &folder, const QString &message, ErrorCategory category);
+
protected:
void setProgressInfo(const QString &folder, const ProgressInfo &progress);
diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp
index 8cc87ccac9..2a6ca14394 100644
--- a/src/libsync/propagatedownload.cpp
+++ b/src/libsync/propagatedownload.cpp
@@ -118,6 +118,8 @@ void GETFileJob::start()
req.setRawHeader(it.key(), it.value());
}
+ req.setPriority(QNetworkRequest::LowPriority); // Long downloads must not block non-propagation jobs.
+
if (_directDownloadUrl.isEmpty()) {
sendRequest("GET", makeDavUrl(path()), req);
} else {
@@ -427,9 +429,12 @@ void PropagateDownloadFile::startDownload()
const auto diskSpaceResult = propagator()->diskSpaceCheck();
if (diskSpaceResult != OwncloudPropagator::DiskSpaceOk) {
if (diskSpaceResult == OwncloudPropagator::DiskSpaceFailure) {
- _item->_errorMayBeBlacklisted = true;
- done(SyncFileItem::NormalError,
- tr("The download would reduce free disk space below %1").arg(Utility::octetsToString(freeSpaceLimit())));
+ // Using BlacklistedError here will make the error not pop up in the account
+ // tab: instead we'll generate a general "disk space low" message and show
+ // these detail errors only in the error view.
+ done(SyncFileItem::BlacklistedError,
+ tr("The download would reduce free local disk space below the limit"));
+ emit propagator()->insufficientLocalStorage();
} else if (diskSpaceResult == OwncloudPropagator::DiskSpaceCritical) {
done(SyncFileItem::FatalError,
tr("Free space on disk is less than %1").arg(Utility::octetsToString(criticalFreeSpaceLimit())));
diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp
index f3b0e8336b..3032bb65fa 100644
--- a/src/libsync/propagateupload.cpp
+++ b/src/libsync/propagateupload.cpp
@@ -35,12 +35,6 @@
#include
#include
-#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
-namespace {
-const char owncloudShouldSoftCancelPropertyName[] = "owncloud-should-soft-cancel";
-}
-#endif
-
namespace OCC {
Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg)
@@ -79,6 +73,8 @@ void PUTFileJob::start()
req.setRawHeader(it.key(), it.value());
}
+ req.setPriority(QNetworkRequest::LowPriority); // Long uploads must not block non-propagation jobs.
+
if (_url.isValid()) {
sendRequest("PUT", _url, req, _device);
} else {
@@ -91,27 +87,10 @@ void PUTFileJob::start()
connect(reply(), SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(uploadProgress(qint64, qint64)));
connect(this, SIGNAL(networkActivity()), account().data(), SIGNAL(propagatorNetworkActivity()));
-
-// For Qt versions not including https://codereview.qt-project.org/110150
-// Also do the runtime check if compiled with an old Qt but running with fixed one.
-// (workaround disabled on windows and mac because the binaries we ship have patched qt)
-#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2) && !defined Q_OS_WIN && !defined Q_OS_MAC
- if (QLatin1String(qVersion()) < QLatin1String("5.4.2"))
- connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort()));
-#endif
-
_requestTimer.start();
AbstractNetworkJob::start();
}
-#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
-void PUTFileJob::slotSoftAbort()
-{
- reply()->setProperty(owncloudShouldSoftCancelPropertyName, true);
- reply()->abort();
-}
-#endif
-
void PollJob::start()
{
setTimeout(120 * 1000);
@@ -522,6 +501,38 @@ void PropagateUploadFileCommon::checkResettingErrors()
}
}
+void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job)
+{
+ QByteArray replyContent;
+ QString errorString = job->errorStringParsingBody(&replyContent);
+ qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug
+
+ if (_item->_httpErrorCode == 412) {
+ // Precondition Failed: Either an etag or a checksum mismatch.
+
+ // Maybe the bad etag is in the database, we need to clear the
+ // parent folder etag so we won't read from DB next sync.
+ propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file);
+ propagator()->_anotherSyncNeeded = true;
+ }
+
+ // Ensure errors that should eventually reset the chunked upload are tracked.
+ checkResettingErrors();
+
+ SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode,
+ &propagator()->_anotherSyncNeeded);
+
+ if (_item->_httpErrorCode == 507) {
+ // Insufficient remote storage.
+ _item->_errorMayBeBlacklisted = true;
+ status = SyncFileItem::BlacklistedError;
+ errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size));
+ emit propagator()->insufficientRemoteStorage();
+ }
+
+ abortWithError(status, errorString);
+}
+
void PropagateUploadFileCommon::slotJobDestroyed(QObject *job)
{
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end());
diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h
index 5e16b5ff98..dd75509254 100644
--- a/src/libsync/propagateupload.h
+++ b/src/libsync/propagateupload.h
@@ -154,10 +154,6 @@ signals:
void finishedSignal();
void uploadProgress(qint64, qint64);
-private slots:
-#if QT_VERSION < 0x050402
- void slotSoftAbort();
-#endif
};
/**
@@ -277,6 +273,11 @@ protected:
*/
void checkResettingErrors();
+ /**
+ * Error handling functionality that is shared between jobs.
+ */
+ void commonErrorHandling(AbstractNetworkJob *job);
+
// Bases headers that need to be sent with every chunk
QMap headers();
};
@@ -307,7 +308,11 @@ private:
int _chunkCount; /// Total number of chunks for this file
int _transferId; /// transfer id (part of the url)
- quint64 chunkSize() const { return propagator()->syncOptions()._initialChunkSize; }
+ quint64 chunkSize() const {
+ // Old chunking does not use dynamic chunking algorithm, and does not adjusts the chunk size respectively,
+ // thus this value should be used as the one classifing item to be chunked
+ return propagator()->syncOptions()._initialChunkSize;
+ }
public:
diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp
index 45768102da..7717203a14 100644
--- a/src/libsync/propagateuploadng.cpp
+++ b/src/libsync/propagateuploadng.cpp
@@ -284,9 +284,9 @@ void PropagateUploadFileNG::startNextChunk()
headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])";
}
if (!_transmissionChecksumHeader.isEmpty()) {
+ qCInfo(lcPropagateUpload) << destination << _transmissionChecksumHeader;
headers[checkSumHeaderC] = _transmissionChecksumHeader;
}
-
headers["OC-Total-Length"] = QByteArray::number(fileSize);
auto job = new MoveJob(propagator()->account(), Utility::concatUrlPath(chunkUrl(), "/.file"),
@@ -333,8 +333,6 @@ void PropagateUploadFileNG::startNextChunk()
job->start();
propagator()->_activeJobList.append(this);
_currentChunk++;
-
- // FIXME! parallel chunk?
}
void PropagateUploadFileNG::slotPutFinished()
@@ -366,16 +364,7 @@ void PropagateUploadFileNG::slotPutFinished()
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- QByteArray replyContent;
- QString errorString = job->errorStringParsingBody(&replyContent);
- qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug
-
- // Ensure errors that should eventually reset the chunked upload are tracked.
- checkResettingErrors();
-
- SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
- &propagator()->_anotherSyncNeeded);
- abortWithError(status, errorString);
+ commonErrorHandling(job);
return;
}
@@ -400,6 +389,7 @@ void PropagateUploadFileNG::slotPutFinished()
// the chunk sizes a bit.
quint64 targetSize = (propagator()->_chunkSize + predictedGoodSize) / 2;
+ // Adjust the dynamic chunk size _chunkSize used for sizing of the item's chunks to be send
propagator()->_chunkSize = qBound(
propagator()->syncOptions()._minChunkSize,
targetSize,
@@ -458,21 +448,7 @@ void PropagateUploadFileNG::slotMoveJobFinished()
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (err != QNetworkReply::NoError) {
- if (_item->_httpErrorCode == 412) {
- // Precondition Failed: Either an etag or a checksum mismatch.
-
- // Maybe the bad etag is in the database, we need to clear the
- // parent folder etag so we won't read from DB next sync.
- propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file);
- propagator()->_anotherSyncNeeded = true;
- }
-
- // Ensure errors that should eventually reset the chunked upload are tracked.
- checkResettingErrors();
-
- SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
- &propagator()->_anotherSyncNeeded);
- abortWithError(status, job->errorStringParsingBody());
+ commonErrorHandling(job);
return;
}
if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) {
diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp
index b48b433ab7..74a5f6e52e 100644
--- a/src/libsync/propagateuploadv1.cpp
+++ b/src/libsync/propagateuploadv1.cpp
@@ -104,6 +104,7 @@ void PropagateUploadFileV1::startNextChunk()
qCDebug(lcPropagateUpload) << _chunkCount << isFinalChunk << chunkStart << currentChunkSize;
if (isFinalChunk && !_transmissionChecksumHeader.isEmpty()) {
+ qCInfo(lcPropagateUpload) << propagator()->_remoteFolder + path << _transmissionChecksumHeader;
headers[checkSumHeaderC] = _transmissionChecksumHeader;
}
@@ -201,25 +202,7 @@ void PropagateUploadFileV1::slotPutFinished()
"It is restored and your edit is in the conflict file."))) {
return;
}
- QByteArray replyContent;
- QString errorString = job->errorStringParsingBody(&replyContent);
- qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug
-
- if (_item->_httpErrorCode == 412) {
- // Precondition Failed: Either an etag or a checksum mismatch.
-
- // Maybe the bad etag is in the database, we need to clear the
- // parent folder etag so we won't read from DB next sync.
- propagator()->_journal->avoidReadFromDbOnNextSync(_item->_file);
- propagator()->_anotherSyncNeeded = true;
- }
-
- // Ensure errors that should eventually reset the chunked upload are tracked.
- checkResettingErrors();
-
- SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
- &propagator()->_anotherSyncNeeded);
- abortWithError(status, errorString);
+ commonErrorHandling(job);
return;
}
diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp
index 5cf17b26ac..65d7ef86ad 100644
--- a/src/libsync/propagatorjobs.cpp
+++ b/src/libsync/propagatorjobs.cpp
@@ -42,6 +42,11 @@ Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfo
Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfoMsg)
+QByteArray localFileIdFromFullId(const QByteArray &id)
+{
+ return id.left(8);
+}
+
/**
* Code inspired from Qt5's QDir::removeRecursively
* The code will update the database in case of error.
diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp
index faf003fbf6..d4afcc57c6 100644
--- a/src/libsync/syncengine.cpp
+++ b/src/libsync/syncengine.cpp
@@ -24,6 +24,7 @@
#include "csync_private.h"
#include "filesystem.h"
#include "propagateremotedelete.h"
+#include "propagatedownload.h"
#include "asserts.h"
#ifdef Q_OS_WIN
@@ -54,6 +55,7 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg)
+static const int s_touchedFilesMaxAgeMs = 15 * 1000;
bool SyncEngine::s_anySyncRunning = false;
qint64 SyncEngine::minimumFileAgeForUpload = 2000;
@@ -256,12 +258,24 @@ bool SyncEngine::checkErrorBlacklisting(SyncFileItem &item)
}
}
+ int waitSeconds = entry._lastTryTime + entry._ignoreDuration - now;
qCInfo(lcEngine) << "Item is on blacklist: " << entry._file
<< "retries:" << entry._retryCount
- << "for another" << (entry._lastTryTime + entry._ignoreDuration - now) << "s";
- item._instruction = CSYNC_INSTRUCTION_ERROR;
- item._status = SyncFileItem::FileIgnored;
- item._errorString = tr("The item is not synced because of previous errors: %1").arg(entry._errorString);
+ << "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);
+
+ if (entry._errorCategory == SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage) {
+ slotInsufficientRemoteStorage();
+ }
return true;
}
@@ -382,8 +396,10 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote)
if (item->_instruction == CSYNC_INSTRUCTION_NONE
|| (item->_instruction == CSYNC_INSTRUCTION_IGNORE && instruction != CSYNC_INSTRUCTION_NONE)) {
+ // Take values from side (local/remote) where instruction is not _NONE
item->_instruction = instruction;
item->_modtime = file->modtime;
+ item->_size = file->size;
} else {
if (instruction != CSYNC_INSTRUCTION_NONE) {
qCWarning(lcEngine) << "ERROR: Instruction" << item->_instruction << "vs" << instruction << "for" << fileUtf8;
@@ -480,6 +496,10 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote)
case CSYNC_STATUS_INDIVIDUAL_TOO_DEEP:
item->_errorString = tr("Folder hierarchy is too deep");
break;
+ case CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE:
+ item->_status = SyncFileItem::Conflict;
+ item->_errorString = tr("Conflict: Server version downloaded, local copy renamed and not uploaded.");
+ break;
case CYSNC_STATUS_FILE_LOCKED_OR_OPEN:
item->_errorString = QLatin1String("File locked"); // don't translate, internal use!
break;
@@ -519,7 +539,7 @@ int SyncEngine::treewalkFile(TREE_WALK_FILE *file, bool remote)
if (file->etag && file->etag[0]) {
item->_etag = file->etag;
}
- item->_size = file->size;
+
if (!item->_inode) {
item->_inode = file->inode;
@@ -696,11 +716,17 @@ void SyncEngine::handleSyncError(CSYNC *ctx, const char *state)
} else if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_SERVICE_UNAVAILABLE) || CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_CONNECT_ERROR)) {
emit csyncUnavailable();
} else {
- emit csyncError(errStr);
+ csyncError(errStr);
}
finalize(false);
}
+void SyncEngine::csyncError(const QString &message)
+{
+ emit syncError(message, ErrorCategory::Normal);
+}
+
+
void SyncEngine::startSync()
{
if (_journal->exists()) {
@@ -731,7 +757,7 @@ void SyncEngine::startSync()
if (!QDir(_localPath).exists()) {
_anotherSyncNeeded = DelayedFollowUp;
// No _tr, it should only occur in non-mirall
- emit csyncError("Unable to find local sync folder.");
+ csyncError("Unable to find local sync folder.");
finalize(false);
return;
}
@@ -744,11 +770,11 @@ void SyncEngine::startSync()
<< "and at least" << minFree << "are required";
if (freeBytes < minFree) {
_anotherSyncNeeded = DelayedFollowUp;
- emit csyncError(tr("Only %1 are available, need at least %2 to start",
+ csyncError(tr("Only %1 are available, need at least %2 to start",
"Placeholders are postfixed with file sizes using Utility::octetsToString()")
- .arg(
- Utility::octetsToString(freeBytes),
- Utility::octetsToString(minFree)));
+ .arg(
+ Utility::octetsToString(freeBytes),
+ Utility::octetsToString(minFree)));
finalize(false);
return;
}
@@ -781,7 +807,7 @@ void SyncEngine::startSync()
if (fileRecordCount == -1) {
qCWarning(lcEngine) << "No way to create a sync journal!";
- emit csyncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
+ csyncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
finalize(false);
return;
// database creation error!
@@ -800,7 +826,7 @@ void SyncEngine::startSync()
qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync");
} else {
qCWarning(lcEngine) << "Could not retrieve selective sync list from DB";
- emit csyncError(tr("Unable to read the blacklist from the local database"));
+ csyncError(tr("Unable to read the blacklist from the local database"));
finalize(false);
return;
}
@@ -811,8 +837,12 @@ void SyncEngine::startSync()
_csync_ctx->callbacks.checksum_userdata = &_checksum_hook;
_stopWatch.start();
+ _progressInfo->_status = ProgressInfo::Starting;
+ emit transmissionProgress(*_progressInfo);
qCInfo(lcEngine) << "#### Discovery start ####################################################";
+ _progressInfo->_status = ProgressInfo::Discovery;
+ emit transmissionProgress(*_progressInfo);
// Usually the discovery runs in the background: We want to avoid
// stealing too much time from other processes that the user might
@@ -823,7 +853,7 @@ void SyncEngine::startSync()
_discoveryMainThread->setParent(this);
connect(this, SIGNAL(finished(bool)), _discoveryMainThread, SLOT(deleteLater()));
qCInfo(lcEngine) << "Server" << account()->serverVersion()
- << QString("rootEtagChangesNotOnlySubFolderEtags=%1").arg(account()->rootEtagChangesNotOnlySubFolderEtags());
+ << (account()->isHttp2Supported() ? "Using HTTP/2" : "");
if (account()->rootEtagChangesNotOnlySubFolderEtags()) {
connect(_discoveryMainThread, SIGNAL(etag(QString)), this, SLOT(slotRootEtagReceived(QString)));
} else {
@@ -837,7 +867,7 @@ void SyncEngine::startSync()
if (!ok) {
delete discoveryJob;
qCWarning(lcEngine) << "Unable to read selective sync list, aborting.";
- emit csyncError(tr("Unable to read from the sync journal."));
+ csyncError(tr("Unable to read from the sync journal."));
finalize(false);
return;
}
@@ -846,7 +876,7 @@ void SyncEngine::startSync()
discoveryJob->moveToThread(&_thread);
connect(discoveryJob, SIGNAL(finished(int)), this, SLOT(slotDiscoveryJobFinished(int)));
connect(discoveryJob, SIGNAL(folderDiscovered(bool, QString)),
- this, SIGNAL(folderDiscovered(bool, QString)));
+ this, SLOT(slotFolderDiscovered(bool, QString)));
connect(discoveryJob, SIGNAL(newBigFolder(QString, bool)),
this, SIGNAL(newBigFolder(QString, bool)));
@@ -860,6 +890,12 @@ void SyncEngine::startSync()
QMetaObject::invokeMethod(discoveryJob, "start", Qt::QueuedConnection);
}
+void SyncEngine::slotFolderDiscovered(bool /*local*/, const QString &folder)
+{
+ _progressInfo->_currentDiscoveredFolder = folder;
+ emit transmissionProgress(*_progressInfo);
+}
+
void SyncEngine::slotRootEtagReceived(const QString &e)
{
if (_remoteRootEtag.isEmpty()) {
@@ -871,9 +907,6 @@ void SyncEngine::slotRootEtagReceived(const QString &e)
void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
{
- // To clean the progress info
- emit folderDiscovered(false, QString());
-
if (discoveryResult < 0) {
handleSyncError(_csync_ctx, "csync_update");
return;
@@ -883,7 +916,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
// Sanity check
if (!_journal->isConnected()) {
qCWarning(lcEngine) << "Bailing out, DB failure";
- emit csyncError(tr("Cannot open the sync journal"));
+ csyncError(tr("Cannot open the sync journal"));
finalize(false);
return;
} else {
@@ -891,6 +924,10 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
_journal->commitIfNeededAndStartNewTransaction("Post discovery");
}
+ _progressInfo->_currentDiscoveredFolder.clear();
+ _progressInfo->_status = ProgressInfo::Reconcile;
+ emit transmissionProgress(*_progressInfo);
+
if (csync_reconcile(_csync_ctx) < 0) {
handleSyncError(_csync_ctx, "csync_reconcile");
return;
@@ -988,7 +1025,9 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
// To announce the beginning of the sync
emit aboutToPropagate(syncItems);
+
// it's important to do this before ProgressInfo::start(), to announce start of new sync
+ _progressInfo->_status = ProgressInfo::Propagation;
emit transmissionProgress(*_progressInfo);
_progressInfo->startEstimateUpdates();
@@ -1017,6 +1056,8 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
connect(_propagator.data(), SIGNAL(finished(bool)), this, SLOT(slotFinished(bool)), Qt::QueuedConnection);
connect(_propagator.data(), SIGNAL(seenLockedFile(QString)), SIGNAL(seenLockedFile(QString)));
connect(_propagator.data(), SIGNAL(touchedFile(QString)), SLOT(slotAddTouchedFile(QString)));
+ connect(_propagator.data(), SIGNAL(insufficientLocalStorage()), SLOT(slotInsufficientLocalStorage()));
+ connect(_propagator.data(), SIGNAL(insufficientRemoteStorage()), SLOT(slotInsufficientRemoteStorage()));
// apply the network limits to the propagator
setNetworkLimits(_uploadLimit, _downloadLimit);
@@ -1073,7 +1114,7 @@ void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item)
_progressInfo->setProgressComplete(*item);
if (item->_status == SyncFileItem::FatalError) {
- emit csyncError(item->_errorString);
+ csyncError(item->_errorString);
}
emit transmissionProgress(*_progressInfo);
@@ -1101,6 +1142,7 @@ void SyncEngine::slotFinished(bool success)
// files needed propagation, but clear the lastCompletedItem
// so we don't count this twice (like Recent Files)
_progressInfo->_lastCompletedItem = SyncFileItem();
+ _progressInfo->_status = ProgressInfo::Done;
emit transmissionProgress(*_progressInfo);
finalize(success);
@@ -1127,6 +1169,7 @@ void SyncEngine::finalize(bool success)
_seenFiles.clear();
_temporarilyUnavailablePaths.clear();
_renamedFolders.clear();
+ _uniqueErrors.clear();
_clearTouchedFilesTimer.start();
}
@@ -1470,12 +1513,27 @@ void SyncEngine::restoreOldFiles(SyncFileItemVector &syncItems)
void SyncEngine::slotAddTouchedFile(const QString &fn)
{
+ QElapsedTimer now;
+ now.start();
QString file = QDir::cleanPath(fn);
- QElapsedTimer timer;
- timer.start();
+ // 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;
+ }
- _touchedFiles.insert(file, timer);
+ _touchedFiles.erase(first);
+ }
+
+ // This should be the largest QElapsedTimer yet, use constEnd() as hint.
+ _touchedFiles.insert(_touchedFiles.constEnd(), now, file);
}
void SyncEngine::slotClearTouchedFiles()
@@ -1483,13 +1541,15 @@ void SyncEngine::slotClearTouchedFiles()
_touchedFiles.clear();
}
-qint64 SyncEngine::timeSinceFileTouched(const QString &fn) const
+bool SyncEngine::wasFileTouched(const QString &fn) const
{
- if (!_touchedFiles.contains(fn)) {
- return -1;
+ // 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;
}
-
- return _touchedFiles[fn].elapsed();
+ return false;
}
AccountPtr SyncEngine::account() const
@@ -1515,4 +1575,31 @@ void SyncEngine::abort()
}
}
+void SyncEngine::slotSummaryError(const QString &message)
+{
+ if (_uniqueErrors.contains(message))
+ return;
+
+ _uniqueErrors.insert(message);
+ emit syncError(message, ErrorCategory::Normal);
+}
+
+void SyncEngine::slotInsufficientLocalStorage()
+{
+ slotSummaryError(
+ tr("Disk space is low: Downloads that would reduce free space "
+ "below %1 were skipped.")
+ .arg(Utility::octetsToString(freeSpaceLimit())));
+}
+
+void SyncEngine::slotInsufficientRemoteStorage()
+{
+ 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);
+}
+
} // namespace OCC
diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h
index 8652ef2cb0..a275618a43 100644
--- a/src/libsync/syncengine.h
+++ b/src/libsync/syncengine.h
@@ -87,11 +87,7 @@ public:
/* Returns whether another sync is needed to complete the sync */
AnotherSyncNeeded isAnotherSyncNeeded() { return _anotherSyncNeeded; }
- /** Get the ms since a file was touched, or -1 if it wasn't.
- *
- * Thread-safe.
- */
- qint64 timeSinceFileTouched(const QString &fn) const;
+ bool wasFileTouched(const QString &fn) const;
AccountPtr account() const;
SyncJournalDb *journal() const { return _journal; }
@@ -104,12 +100,10 @@ public:
static qint64 minimumFileAgeForUpload; // in ms
signals:
- void csyncError(const QString &);
void csyncUnavailable();
// During update, before reconcile
void rootEtag(QString);
- void folderDiscovered(bool local, const QString &folderUrl);
// before actual syncing (after update+reconcile) for each item
void syncItemDiscovered(const SyncFileItem &);
@@ -121,6 +115,9 @@ signals:
void transmissionProgress(const ProgressInfo &progress);
+ /// We've produced a new sync error of a type.
+ void syncError(const QString &message, ErrorCategory category);
+
void finished(bool success);
void started();
@@ -147,6 +144,7 @@ signals:
void seenLockedFile(const QString &fileName);
private slots:
+ void slotFolderDiscovered(bool local, const QString &folder);
void slotRootEtagReceived(const QString &);
void slotItemCompleted(const SyncFileItemPtr &item);
void slotFinished(bool success);
@@ -160,8 +158,15 @@ private slots:
/** Wipes the _touchedFiles hash */
void slotClearTouchedFiles();
+ /** Emit a summary error, unless it was seen before */
+ void slotSummaryError(const QString &message);
+
+ void slotInsufficientLocalStorage();
+ void slotInsufficientRemoteStorage();
+
private:
void handleSyncError(CSYNC *ctx, const char *state);
+ void csyncError(const QString &message);
QString journalDbFilePath() const;
@@ -263,10 +268,13 @@ private:
AnotherSyncNeeded _anotherSyncNeeded;
/** Stores the time since a job touched a file. */
- QHash _touchedFiles;
+ QMultiMap _touchedFiles;
/** For clearing the _touchedFiles variable after sync finished */
QTimer _clearTouchedFilesTimer;
+
+ /** List of unique errors that occurred in a sync run. */
+ QSet _uniqueErrors;
};
}
diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h
index 3eefac5cee..baa8a75102 100644
--- a/src/libsync/syncfileitem.h
+++ b/src/libsync/syncfileitem.h
@@ -53,7 +53,7 @@ public:
SoftLink = CSYNC_FTW_TYPE_SLINK
};
- enum Status {
+ enum Status { // stored in 4 bits
NoStatus,
FatalError, ///< Error that causes the sync to stop
@@ -61,9 +61,25 @@ public:
SoftError, ///< More like an information
Success, ///< The file was properly synced
- Conflict, ///< The file was properly synced, but a conflict was created
+
+ /** Marks a conflict, old or new.
+ *
+ * With instruction:IGNORE: detected an old unresolved old conflict
+ * With instruction:CONFLICT: a new conflict this sync run
+ */
+ Conflict,
+
FileIgnored, ///< The file is in the ignored list (or blacklisted with no retries left)
- Restoration ///< The file was restored because what should have been done was not allowed
+ Restoration, ///< The file was restored because what should have been done was not allowed
+
+ /** For files whose errors were blacklisted.
+ *
+ * If _instruction == IGNORE, the file wasn't even reattempted.
+ *
+ * These errors should usually be shown as NormalErrors, but not be as loud
+ * as them.
+ */
+ BlacklistedError
};
SyncFileItem()
diff --git a/src/libsync/syncfilestatus.cpp b/src/libsync/syncfilestatus.cpp
index 12a9977860..8d1a5933fe 100644
--- a/src/libsync/syncfilestatus.cpp
+++ b/src/libsync/syncfilestatus.cpp
@@ -17,13 +17,13 @@
namespace OCC {
SyncFileStatus::SyncFileStatus()
: _tag(StatusNone)
- , _sharedWithMe(false)
+ , _shared(false)
{
}
SyncFileStatus::SyncFileStatus(SyncFileStatusTag tag)
: _tag(tag)
- , _sharedWithMe(false)
+ , _shared(false)
{
}
@@ -37,14 +37,14 @@ SyncFileStatus::SyncFileStatusTag SyncFileStatus::tag() const
return _tag;
}
-void SyncFileStatus::setSharedWithMe(bool isShared)
+void SyncFileStatus::setShared(bool isShared)
{
- _sharedWithMe = isShared;
+ _shared = isShared;
}
-bool SyncFileStatus::sharedWithMe() const
+bool SyncFileStatus::shared() const
{
- return _sharedWithMe;
+ return _shared;
}
QString SyncFileStatus::toSocketAPIString() const
@@ -71,7 +71,7 @@ QString SyncFileStatus::toSocketAPIString() const
statusString = QLatin1String("ERROR");
break;
}
- if (canBeShared && _sharedWithMe) {
+ if (canBeShared && _shared) {
statusString += QLatin1String("+SWM");
}
diff --git a/src/libsync/syncfilestatus.h b/src/libsync/syncfilestatus.h
index 27006a5722..e4743f7a3f 100644
--- a/src/libsync/syncfilestatus.h
+++ b/src/libsync/syncfilestatus.h
@@ -44,19 +44,19 @@ public:
void set(SyncFileStatusTag tag);
SyncFileStatusTag tag() const;
- void setSharedWithMe(bool isShared);
- bool sharedWithMe() const;
+ void setShared(bool isShared);
+ bool shared() const;
QString toSocketAPIString() const;
private:
SyncFileStatusTag _tag;
- bool _sharedWithMe;
+ bool _shared;
};
inline bool operator==(const SyncFileStatus &a, const SyncFileStatus &b)
{
- return a.tag() == b.tag() && a.sharedWithMe() == b.sharedWithMe();
+ return a.tag() == b.tag() && a.shared() == b.shared();
}
inline bool operator!=(const SyncFileStatus &a, const SyncFileStatus &b)
diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp
index db1f778c20..9d4fa6e37d 100644
--- a/src/libsync/syncfilestatustracker.cpp
+++ b/src/libsync/syncfilestatustracker.cpp
@@ -65,6 +65,7 @@ static inline bool showErrorInSocketApi(const SyncFileItem &item)
return item._instruction == CSYNC_INSTRUCTION_ERROR
|| status == SyncFileItem::NormalError
|| status == SyncFileItem::FatalError
+ || status == SyncFileItem::BlacklistedError
|| item._hasBlacklistEntry;
}
@@ -285,7 +286,7 @@ SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &r
ASSERT(sharedFlag != UnknownShared,
"The shared status needs to have been fetched from a SyncFileItem or the DB at this point.");
if (sharedFlag == Shared)
- status.setSharedWithMe(true);
+ status.setShared(true);
return status;
}
diff --git a/src/libsync/syncjournaldb.cpp b/src/libsync/syncjournaldb.cpp
index b6bcff4d0d..0eb34e6097 100644
--- a/src/libsync/syncjournaldb.cpp
+++ b/src/libsync/syncjournaldb.cpp
@@ -564,7 +564,7 @@ bool SyncJournalDb::checkConnect()
return sqlFail("prepare _deleteFileRecordRecursively", *_deleteFileRecordRecursively);
}
- QString sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget "
+ QString sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory "
"FROM blacklist WHERE path=?1");
if (Utility::fsCasePreserving()) {
// if the file system is case preserving we have to check the blacklist
@@ -578,8 +578,8 @@ bool SyncJournalDb::checkConnect()
_setErrorBlacklistQuery.reset(new SqlQuery(_db));
if (_setErrorBlacklistQuery->prepare("INSERT OR REPLACE INTO blacklist "
- "(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget) "
- "VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)")) {
+ "(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory) "
+ "VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)")) {
return sqlFail("prepare _setErrorBlacklistQuery", *_setErrorBlacklistQuery);
}
@@ -800,7 +800,17 @@ bool SyncJournalDb::updateErrorBlacklistTableStructure()
sqlFail("updateBlacklistTableStructure: Add renameTarget", query);
re = false;
}
- commitInternal("update database structure: add lastTryTime, ignoreDuration cols");
+ commitInternal("update database structure: add renameTarget col");
+ }
+
+ if (columns.indexOf(QLatin1String("errorCategory")) == -1) {
+ SqlQuery query(_db);
+ query.prepare("ALTER TABLE blacklist ADD COLUMN errorCategory INTEGER(8);");
+ if (!query.exec()) {
+ sqlFail("updateBlacklistTableStructure: Add errorCategory", query);
+ re = false;
+ }
+ commitInternal("update database structure: add errorCategory col");
}
SqlQuery query(_db);
@@ -1410,6 +1420,8 @@ SyncJournalErrorBlacklistRecord SyncJournalDb::errorBlacklistEntry(const QString
entry._lastTryTime = _getErrorBlacklistQuery->int64Value(4);
entry._ignoreDuration = _getErrorBlacklistQuery->int64Value(5);
entry._renameTarget = _getErrorBlacklistQuery->stringValue(6);
+ entry._errorCategory = static_cast(
+ _getErrorBlacklistQuery->intValue(7));
entry._file = file;
}
_getErrorBlacklistQuery->reset_and_clear_bindings();
@@ -1501,13 +1513,28 @@ void SyncJournalDb::wipeErrorBlacklistEntry(const QString &file)
}
}
-void SyncJournalDb::updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item)
+void SyncJournalDb::wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category category)
+{
+ QMutexLocker locker(&_mutex);
+ if (checkConnect()) {
+ SqlQuery query(_db);
+
+ query.prepare("DELETE FROM blacklist WHERE errorCategory=?1");
+ query.bindValue(1, category);
+ if (!query.exec()) {
+ sqlFail("Deletion of blacklist category failed.", query);
+ }
+ }
+}
+
+void SyncJournalDb::setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item)
{
QMutexLocker locker(&_mutex);
qCInfo(lcDb) << "Setting blacklist entry for " << item._file << item._retryCount
<< item._errorString << item._lastTryTime << item._ignoreDuration
- << item._lastTryModtime << item._lastTryEtag << item._renameTarget;
+ << item._lastTryModtime << item._lastTryEtag << item._renameTarget
+ << item._errorCategory;
if (!checkConnect()) {
return;
@@ -1521,6 +1548,7 @@ void SyncJournalDb::updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRec
_setErrorBlacklistQuery->bindValue(6, QString::number(item._lastTryTime));
_setErrorBlacklistQuery->bindValue(7, QString::number(item._ignoreDuration));
_setErrorBlacklistQuery->bindValue(8, item._renameTarget);
+ _setErrorBlacklistQuery->bindValue(9, item._errorCategory);
_setErrorBlacklistQuery->exec();
_setErrorBlacklistQuery->reset_and_clear_bindings();
}
diff --git a/src/libsync/syncjournaldb.h b/src/libsync/syncjournaldb.h
index 3f6c89e435..62295f62e1 100644
--- a/src/libsync/syncjournaldb.h
+++ b/src/libsync/syncjournaldb.h
@@ -22,10 +22,10 @@
#include "utility.h"
#include "ownsql.h"
+#include "syncjournalfilerecord.h"
namespace OCC {
class SyncJournalFileRecord;
-class SyncJournalErrorBlacklistRecord;
/**
* @brief Class that handles the sync database
@@ -71,8 +71,9 @@ public:
static qint64 getPHash(const QString &);
- void updateErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item);
+ void setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item);
void wipeErrorBlacklistEntry(const QString &file);
+ void wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category category);
int wipeErrorBlacklist();
int errorBlackListEntryCount();
diff --git a/src/libsync/syncjournalfilerecord.cpp b/src/libsync/syncjournalfilerecord.cpp
index b2c65931d4..024c327460 100644
--- a/src/libsync/syncjournalfilerecord.cpp
+++ b/src/libsync/syncjournalfilerecord.cpp
@@ -109,6 +109,31 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem()
return item;
}
+QByteArray SyncJournalFileRecord::numericFileId() const
+{
+ // Use the id up until the first non-numeric character
+ for (int i = 0; i < _fileId.size(); ++i) {
+ if (_fileId[i] < '0' || _fileId[i] > '9') {
+ return _fileId.left(i);
+ }
+ }
+ return _fileId;
+}
+
+SyncJournalErrorBlacklistRecord SyncJournalErrorBlacklistRecord::fromSyncFileItem(
+ const SyncFileItem &item)
+{
+ SyncJournalErrorBlacklistRecord record;
+ record._file = item._file;
+ record._errorString = item._errorString;
+ record._lastTryModtime = item._modtime;
+ record._lastTryEtag = item._etag;
+ record._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime());
+ record._renameTarget = item._renameTarget;
+ record._retryCount = 1;
+ return record;
+}
+
bool SyncJournalErrorBlacklistRecord::isValid() const
{
return !_file.isEmpty()
diff --git a/src/libsync/syncjournalfilerecord.h b/src/libsync/syncjournalfilerecord.h
index c7579683b9..6ef0ba169e 100644
--- a/src/libsync/syncjournalfilerecord.h
+++ b/src/libsync/syncjournalfilerecord.h
@@ -48,6 +48,14 @@ public:
return !_path.isEmpty();
}
+ /** Returns the numeric part of the full id in _fileId.
+ *
+ * On the server this is sometimes known as the internal file id.
+ *
+ * It is used in the construction of private links.
+ */
+ QByteArray numericFileId() const;
+
QString _path;
quint64 _inode;
QDateTime _modtime;
@@ -67,19 +75,32 @@ operator==(const SyncJournalFileRecord &lhs,
class SyncJournalErrorBlacklistRecord
{
public:
+ enum Category {
+ /// Normal errors have no special behavior
+ Normal = 0,
+ /// These get a special summary message
+ InsufficientRemoteStorage
+ };
+
SyncJournalErrorBlacklistRecord()
: _retryCount(0)
+ , _errorCategory(Category::Normal)
, _lastTryModtime(0)
, _lastTryTime(0)
, _ignoreDuration(0)
{
}
+ /// Create a record based on an item.
+ static SyncJournalErrorBlacklistRecord fromSyncFileItem(const SyncFileItem &item);
+
/// The number of times the operation was unsuccessful so far.
int _retryCount;
/// The last error string.
QString _errorString;
+ /// The error category. Sometimes used for special actions.
+ Category _errorCategory;
time_t _lastTryModtime;
QByteArray _lastTryEtag;
diff --git a/src/libsync/syncresult.cpp b/src/libsync/syncresult.cpp
index de5c81c54b..88e78320b0 100644
--- a/src/libsync/syncresult.cpp
+++ b/src/libsync/syncresult.cpp
@@ -25,7 +25,8 @@ SyncResult::SyncResult()
, _numRemovedItems(0)
, _numUpdatedItems(0)
, _numRenamedItems(0)
- , _numConflictItems(0)
+ , _numNewConflictItems(0)
+ , _numOldConflictItems(0)
, _numErrorItems(0)
{
@@ -147,9 +148,13 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item)
_firstItemError = item;
}
} else if (item->_status == SyncFileItem::Conflict) {
- _numConflictItems++;
- if (!_firstConflictItem) {
- _firstConflictItem = item;
+ if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) {
+ _numNewConflictItems++;
+ if (!_firstNewConflictItem) {
+ _firstNewConflictItem = item;
+ }
+ } else {
+ _numOldConflictItems++;
}
} else {
if (!item->hasErrorStatus() && item->_status != SyncFileItem::FileIgnored && item->_direction == SyncFileItem::Down) {
diff --git a/src/libsync/syncresult.h b/src/libsync/syncresult.h
index 7c5e501aa6..ab53c8dba0 100644
--- a/src/libsync/syncresult.h
+++ b/src/libsync/syncresult.h
@@ -66,14 +66,15 @@ public:
int numRemovedItems() const { return _numRemovedItems; }
int numUpdatedItems() const { return _numUpdatedItems; }
int numRenamedItems() const { return _numRenamedItems; }
- int numConflictItems() const { return _numConflictItems; }
+ int numNewConflictItems() const { return _numNewConflictItems; }
+ int numOldConflictItems() const { return _numOldConflictItems; }
int numErrorItems() const { return _numErrorItems; }
const SyncFileItemPtr &firstItemNew() const { return _firstItemNew; }
const SyncFileItemPtr &firstItemDeleted() const { return _firstItemDeleted; }
const SyncFileItemPtr &firstItemUpdated() const { return _firstItemUpdated; }
const SyncFileItemPtr &firstItemRenamed() const { return _firstItemRenamed; }
- const SyncFileItemPtr &firstConflictItem() const { return _firstConflictItem; }
+ const SyncFileItemPtr &firstNewConflictItem() const { return _firstNewConflictItem; }
const SyncFileItemPtr &firstItemError() const { return _firstItemError; }
void processCompletedItem(const SyncFileItemPtr &item);
@@ -95,14 +96,15 @@ private:
int _numRemovedItems;
int _numUpdatedItems;
int _numRenamedItems;
- int _numConflictItems;
+ int _numNewConflictItems;
+ int _numOldConflictItems;
int _numErrorItems;
SyncFileItemPtr _firstItemNew;
SyncFileItemPtr _firstItemDeleted;
SyncFileItemPtr _firstItemUpdated;
SyncFileItemPtr _firstItemRenamed;
- SyncFileItemPtr _firstConflictItem;
+ SyncFileItemPtr _firstNewConflictItem;
SyncFileItemPtr _firstItemError;
};
}
diff --git a/src/libsync/utility_win.cpp b/src/libsync/utility_win.cpp
index f46cde4d54..3762808b7b 100644
--- a/src/libsync/utility_win.cpp
+++ b/src/libsync/utility_win.cpp
@@ -28,8 +28,20 @@ namespace OCC {
static void setupFavLink_private(const QString &folder)
{
- // Windows Explorer: Place under "Favorites" (Links)
+ // First create a Desktop.ini so that the folder and favorite link show our application's icon.
+ QFile desktopIni(folder + QLatin1String("/Desktop.ini"));
+ if (desktopIni.exists()) {
+ qCWarning(lcUtility) << desktopIni.fileName() << "already exists, not overwriting it to set the folder icon.";
+ } else {
+ qCInfo(lcUtility) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer.";
+ desktopIni.open(QFile::WriteOnly);
+ desktopIni.write("[.ShellClassInfo]\r\nIconResource=");
+ desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8());
+ desktopIni.write(",0\r\n");
+ desktopIni.close();
+ }
+ // Windows Explorer: Place under "Favorites" (Links)
QString linkName;
QDir folderDir(QDir::fromNativeSeparators(folder));
@@ -41,7 +53,7 @@ static void setupFavLink_private(const QString &folder)
linkName = QDir(links).filePath(folderDir.dirName() + QLatin1String(".lnk"));
CoTaskMemFree(path);
}
- qCDebug(lcUtility) << " creating link from " << linkName << " to " << folder;
+ qCInfo(lcUtility) << "Creating favorite link from" << folder << "to" << linkName;
if (!QFile::link(folder, linkName))
qCWarning(lcUtility) << "linking" << folder << "to" << linkName << "failed!";
}
diff --git a/sync-exclude.lst b/sync-exclude.lst
index 9d58aa9fbd..bdf6c34415 100644
--- a/sync-exclude.lst
+++ b/sync-exclude.lst
@@ -8,7 +8,6 @@
].ds_store
._*
]Thumbs.db
-desktop.ini
System Volume Information
.*.sw?
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index ac0f1dca1e..860f140e9b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -55,6 +55,7 @@ list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp )
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
+list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
list(APPEND FolderMan_SRC stub.cpp )
owncloud_add_test(FolderMan "${FolderMan_SRC}")
diff --git a/test/owncloud_add_test.cmake b/test/owncloud_add_test.cmake
index bf15ab890b..cefe264eac 100644
--- a/test/owncloud_add_test.cmake
+++ b/test/owncloud_add_test.cmake
@@ -22,7 +22,7 @@ macro(owncloud_add_test test_class additional_cpp)
)
add_definitions(-DOWNCLOUD_TEST)
- add_definitions(-DOWNCLOUD_BIN_PATH=${CMAKE_BINARY_DIR}/bin)
+ add_definitions(-DOWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin")
add_test(NAME ${OWNCLOUD_TEST_CLASS}Test COMMAND ${OWNCLOUD_TEST_CLASS}Test)
endmacro()
@@ -51,5 +51,5 @@ macro(owncloud_add_benchmark test_class additional_cpp)
)
add_definitions(-DOWNCLOUD_TEST)
- add_definitions(-DOWNCLOUD_BIN_PATH=${CMAKE_BINARY_DIR}/bin)
+ add_definitions(-DOWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin")
endmacro()
diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h
index 997e5054e5..062b62cea7 100644
--- a/test/syncenginetestutils.h
+++ b/test/syncenginetestutils.h
@@ -289,6 +289,7 @@ public:
QString etag = generateEtag();
QByteArray fileId = generateFileId();
QByteArray checksums;
+ QByteArray extraDavProperties;
qint64 size = 0;
char contentChar = 'W';
@@ -360,6 +361,7 @@ public:
xml.writeTextElement(ocUri, QStringLiteral("permissions"), fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
xml.writeTextElement(ocUri, QStringLiteral("id"), fileInfo.fileId);
xml.writeTextElement(ocUri, QStringLiteral("checksums"), fileInfo.checksums);
+ buffer.write(fileInfo.extraDavProperties);
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK");
xml.writeEndElement(); // propstat
@@ -778,7 +780,7 @@ public:
FakeCredentials(QNetworkAccessManager *qnam) : _qnam{qnam} { }
virtual QString authType() const { return "test"; }
virtual QString user() const { return "admin"; }
- virtual QNetworkAccessManager* getQNAM() const { return _qnam; }
+ virtual QNetworkAccessManager *createQNAM() const { return _qnam; }
virtual bool ready() const { return true; }
virtual void fetchFromKeychain() { }
virtual void askFromUser() { }
@@ -826,7 +828,7 @@ public:
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
FileModifier &localModifier() { return _localModifier; }
- FileModifier &remoteModifier() { return _fakeQnam->currentRemoteState(); }
+ FileInfo &remoteModifier() { return _fakeQnam->currentRemoteState(); }
FileInfo currentLocalState() {
QDir rootDir{_tempDir.path()};
FileInfo rootTemplate;
diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp
index 1f48931da1..cfac432f96 100644
--- a/test/testsyncengine.cpp
+++ b/test/testsyncengine.cpp
@@ -385,6 +385,86 @@ private slots:
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(nGET, 1);
}
+
+ /**
+ * Checks whether SyncFileItems have the expected properties before start
+ * of propagation.
+ */
+ void testSyncFileItemProperties()
+ {
+ auto initialMtime = QDateTime::currentDateTime().addDays(-7);
+ auto changedMtime = QDateTime::currentDateTime().addDays(-4);
+ auto changedMtime2 = QDateTime::currentDateTime().addDays(-3);
+
+ // Base mtime with no ms content (filesystem is seconds only)
+ initialMtime.setMSecsSinceEpoch(initialMtime.toMSecsSinceEpoch() / 1000 * 1000);
+ changedMtime.setMSecsSinceEpoch(changedMtime.toMSecsSinceEpoch() / 1000 * 1000);
+ changedMtime2.setMSecsSinceEpoch(changedMtime2.toMSecsSinceEpoch() / 1000 * 1000);
+
+ // Ensure the initial mtimes are as expected
+ auto initialFileInfo = FileInfo::A12_B12_C12_S12();
+ initialFileInfo.setModTime("A/a1", initialMtime);
+ initialFileInfo.setModTime("B/b1", initialMtime);
+ initialFileInfo.setModTime("C/c1", initialMtime);
+
+ FakeFolder fakeFolder{ initialFileInfo };
+
+
+ // upload a
+ fakeFolder.localModifier().appendByte("A/a1");
+ fakeFolder.localModifier().setModTime("A/a1", changedMtime);
+ // download b
+ fakeFolder.remoteModifier().appendByte("B/b1");
+ fakeFolder.remoteModifier().setModTime("B/b1", changedMtime);
+ // conflict c
+ fakeFolder.localModifier().appendByte("C/c1");
+ fakeFolder.localModifier().appendByte("C/c1");
+ fakeFolder.localModifier().setModTime("C/c1", changedMtime);
+ fakeFolder.remoteModifier().appendByte("C/c1");
+ fakeFolder.remoteModifier().setModTime("C/c1", changedMtime2);
+
+ connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemVector &items) {
+ SyncFileItemPtr a1, b1, c1;
+ for (auto &item : items) {
+ if (item->_file == "A/a1")
+ a1 = item;
+ if (item->_file == "B/b1")
+ b1 = item;
+ if (item->_file == "C/c1")
+ c1 = item;
+ }
+
+ // a1: should have local size and modtime
+ QVERIFY(a1);
+ QCOMPARE(a1->_instruction, CSYNC_INSTRUCTION_SYNC);
+ QCOMPARE(a1->_direction, SyncFileItem::Up);
+ QCOMPARE(a1->_size, quint64(5));
+
+ QCOMPARE(Utility::qDateTimeFromTime_t(a1->_modtime), changedMtime);
+ QCOMPARE(a1->log._other_size, quint64(4));
+ QCOMPARE(Utility::qDateTimeFromTime_t(a1->log._other_modtime), initialMtime);
+
+ // b2: should have remote size and modtime
+ QVERIFY(b1);
+ QCOMPARE(b1->_instruction, CSYNC_INSTRUCTION_SYNC);
+ QCOMPARE(b1->_direction, SyncFileItem::Down);
+ QCOMPARE(b1->_size, quint64(17));
+ QCOMPARE(Utility::qDateTimeFromTime_t(b1->_modtime), changedMtime);
+ QCOMPARE(b1->log._other_size, quint64(16));
+ QCOMPARE(Utility::qDateTimeFromTime_t(b1->log._other_modtime), initialMtime);
+
+ // c1: conflicts are downloads, so remote size and modtime
+ QVERIFY(c1);
+ QCOMPARE(c1->_instruction, CSYNC_INSTRUCTION_CONFLICT);
+ QCOMPARE(c1->_direction, SyncFileItem::None);
+ QCOMPARE(c1->_size, quint64(25));
+ QCOMPARE(Utility::qDateTimeFromTime_t(c1->_modtime), changedMtime2);
+ QCOMPARE(c1->log._other_size, quint64(26));
+ QCOMPARE(Utility::qDateTimeFromTime_t(c1->log._other_modtime), changedMtime);
+ });
+
+ QVERIFY(fakeFolder.syncOnce());
+ }
};
QTEST_GUILESS_MAIN(TestSyncEngine)
diff --git a/test/testsyncfilestatustracker.cpp b/test/testsyncfilestatustracker.cpp
index bcb6ae2058..741ae1dc6d 100644
--- a/test/testsyncfilestatustracker.cpp
+++ b/test/testsyncfilestatustracker.cpp
@@ -411,11 +411,14 @@ private slots:
void sharedStatus() {
SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate);
- sharedUpToDateStatus.setSharedWithMe(true);
+ sharedUpToDateStatus.setShared(true);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("S/s0");
fakeFolder.remoteModifier().appendByte("S/s1");
+ fakeFolder.remoteModifier().insert("B/b3");
+ fakeFolder.remoteModifier().find("B/b3")->extraDavProperties = "0";
+
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
@@ -435,6 +438,8 @@ private slots:
QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue);
QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus);
+ QCOMPARE(statusSpy.statusOf("B/b1").shared(), false);
+ QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp
index ec673077dc..881bb865d2 100644
--- a/test/testsyncjournaldb.cpp
+++ b/test/testsyncjournaldb.cpp
@@ -17,10 +17,13 @@ class TestSyncJournalDB : public QObject
{
Q_OBJECT
+ QTemporaryDir _tempDir;
+
public:
TestSyncJournalDB()
- : _db("/tmp/csync-test.db")
+ : _db((_tempDir.path() + "/sync.db"))
{
+ QVERIFY(_tempDir.isValid());
}
QDateTime dropMsecs(QDateTime time)
diff --git a/test/testutility.cpp b/test/testutility.cpp
index 5a656ac846..5093ce76de 100644
--- a/test/testutility.cpp
+++ b/test/testutility.cpp
@@ -11,10 +11,6 @@
#include "utility.h"
-#define STR_(X) #X
-#define STR(X) STR_(X)
-#define BIN_PATH STR(OWNCLOUD_BIN_PATH)
-
using namespace OCC::Utility;
class TestUtility : public QObject
@@ -118,7 +114,7 @@ private slots:
}
// pass the binary name owncloud to the next call. This brakes branding,
// but branding is not supposed to work with this.
- QString ver = versionOfInstalledBinary(BIN_PATH+QLatin1String("/owncloud"));
+ QString ver = versionOfInstalledBinary(OWNCLOUD_BIN_PATH+QLatin1String("/owncloud"));
qDebug() << "Version of installed ownCloud Binary: " << ver;
QVERIFY( !ver.isEmpty());
diff --git a/theme/colored/owncloud-icon-1024.png b/theme/colored/owncloud-icon-1024.png
new file mode 100644
index 0000000000..e32847c1c9
Binary files /dev/null and b/theme/colored/owncloud-icon-1024.png differ
diff --git a/theme/colored/owncloud-icon-16.png b/theme/colored/owncloud-icon-16.png
new file mode 100644
index 0000000000..5c01c4fe9e
Binary files /dev/null and b/theme/colored/owncloud-icon-16.png differ
diff --git a/theme/colored/owncloud-sidebar-16.png b/theme/colored/owncloud-sidebar-16.png
new file mode 100644
index 0000000000..5c01c4fe9e
Binary files /dev/null and b/theme/colored/owncloud-sidebar-16.png differ
diff --git a/theme/colored/owncloud-sidebar-18.png b/theme/colored/owncloud-sidebar-18.png
new file mode 100644
index 0000000000..bff6175741
Binary files /dev/null and b/theme/colored/owncloud-sidebar-18.png differ
diff --git a/theme/colored/owncloud-sidebar-32.png b/theme/colored/owncloud-sidebar-32.png
new file mode 100644
index 0000000000..721c04e485
Binary files /dev/null and b/theme/colored/owncloud-sidebar-32.png differ
diff --git a/theme/colored/owncloud-sidebar-36.png b/theme/colored/owncloud-sidebar-36.png
new file mode 100644
index 0000000000..27e415d315
Binary files /dev/null and b/theme/colored/owncloud-sidebar-36.png differ
diff --git a/theme/colored/owncloud-sidebar-64.png b/theme/colored/owncloud-sidebar-64.png
new file mode 100644
index 0000000000..5c6f17f0cd
Binary files /dev/null and b/theme/colored/owncloud-sidebar-64.png differ
diff --git a/translations/client_ca.ts b/translations/client_ca.ts
index dce4d38608..d64d4eaf0b 100644
--- a/translations/client_ca.ts
+++ b/translations/client_ca.ts
@@ -2991,23 +2991,23 @@ No és aconsellada usar-la.
Error en llegir la carpeta.
-
+
File/Folder is ignored because it's hidden.
El fitxer/carpeta s'ha ignorat perquè és ocult.
-
+
Only %1 are available, need at least %2 to start
Placeholders are postfixed with file sizes using Utility::octetsToString()
-
+
Not allowed because you don't have permission to add parent folder
-
+
Not allowed because you don't have permission to add files in that folder
@@ -3067,124 +3067,124 @@ No és aconsellada usar-la.
L'element no s'ha sincronitzat degut a errors previs: %1
-
+
Symbolic links are not supported in syncing.
La sincronització d'enllaços simbòlics no està implementada.
-
+
File is listed on the ignore list.
El fitxer està a la llista d'ignorats.
-
+
File names ending with a period are not supported on this file system.
-
+
File names containing the character '%1' are not supported on this file system.
-
+
The file name is a reserved name on this file system.
-
+
Filename contains trailing spaces.
-
+
Filename is too long.
El nom de fitxer és massa llarg.
-
+
Stat failed.
-
+
Filename encoding is not valid
La codificació del nom de fitxer no és vàlida
-
+
Invalid characters, please rename "%1"
Caràcters no vàlids. Reanomeneu "%1"
-
+
Unable to initialize a sync journal.
No es pot inicialitzar un periòdic de sincronització
-
+
Unable to read the blacklist from the local database
-
+
Unable to read from the sync journal.
-
+
Cannot open the sync journal
No es pot obrir el diari de sincronització
-
+
File name contains at least one invalid character
El nom del fitxer conté al menys un caràcter invàlid
-
-
+
+
Ignored because of the "choose what to sync" blacklist
S'ignora degut al filtre a «Trieu què sincronitzar»
-
+
Not allowed because you don't have permission to add subfolders to that folder
-
+
Not allowed to upload this file because it is read-only on the server, restoring
No es permet pujar aquest fitxer perquè només és de lectura en el servidor, es restaura
-
-
+
+
Not allowed to remove, restoring
No es permet l'eliminació, es restaura
-
+
Local files and share folder removed.
Fitxers locals i carpeta compartida esborrats.
-
+
Move not allowed, item restored
No es permet moure'l, l'element es restaura
-
+
Move not allowed because %1 is read-only
No es permet moure perquè %1 només és de lectura
-
+
the destination
el destí
-
+
the source
l'origen
@@ -3453,8 +3453,8 @@ No és aconsellada usar-la.
OCC::ownCloudTheme
- <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p>
- <p>Versió %2. Per més informació visiteu <a href="%3">https://%4</a></p><p>Per errors coneguts i ajuda, visiteu: <a href="https://central.owncloud.org/c/help/desktop-file-sync">https://central.owncloud.org</a></p><p><small>Per Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt i altres.</small></p><p>Copyright ownCloud GmbH</p><p>amb llicència GNU General Public License (GPL) versió 2.0<br/>ownCloud i el logo d'ownCloud són marques registrades d'ownCloud GmbH als Estats Units, altres països, o ambdós.</p>
+ <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH in the United States, other countries, or both.</p>
+
diff --git a/translations/client_cs.ts b/translations/client_cs.ts
index 3c2eb0519f..c91112bfc6 100644
--- a/translations/client_cs.ts
+++ b/translations/client_cs.ts
@@ -2994,23 +2994,23 @@ Nedoporučuje se jí používat.
Chyba při čtení adresáře.
-