diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 52a454e9cb9..79e8474edd7 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -210,7 +210,7 @@ int get_errno_from_http_errcode( int err, const QString & reason ) { DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent) - : QObject(parent), _subPath(path), _account(account), _ignoredFirst(false) + : QObject(parent), _subPath(path), _account(account), _ignoredFirst(false), _isRootPath(false) { } @@ -218,10 +218,15 @@ void DiscoverySingleDirectoryJob::start() { // Start the actual HTTP job LsColJob *lsColJob = new LsColJob(_account, _subPath, this); - lsColJob->setProperties(QList() << "resourcetype" << "getlastmodified" - << "getcontentlength" << "getetag" << "http://owncloud.org/ns:id" - << "http://owncloud.org/ns:downloadURL" << "http://owncloud.org/ns:dDC" - << "http://owncloud.org/ns:permissions"); + + QList props; + props << "resourcetype" << "getlastmodified" << "getcontentlength" << "getetag" + << "http://owncloud.org/ns:id" << "http://owncloud.org/ns:downloadURL" + << "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions"; + if (_isRootPath) + props << "http://owncloud.org/ns:data-fingerprint"; + + lsColJob->setProperties(props); QObject::connect(lsColJob, SIGNAL(directoryListingIterated(QString,QMap)), this, SLOT(directoryListingIteratedSlot(QString,QMap))); @@ -299,12 +304,14 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con { //qDebug() << Q_FUNC_INFO << _subPath << file << map.count() << map.keys() << _account->davPath() << _lsColJob->reply()->request().url().path(); if (!_ignoredFirst) { - // First result is the directory itself. Maybe should have a better check for that? FIXME + // The first entry is for the folder itself, we should process it differently. _ignoredFirst = true; if (map.contains("permissions")) { emit firstDirectoryPermissions(map.value("permissions")); } - + if (map.contains("data-fingerprint")) { + _dataFingerprint = map.value("data-fingerprint").toUtf8(); + } } else { // Remove /folder/ from /folder/subfile.txt file.remove(0, _lsColJob->reply()->request().url().path().length()); @@ -426,6 +433,11 @@ void DiscoveryMainThread::doOpendirSlot(const QString &subPath, DiscoveryDirecto this, SIGNAL(etagConcatenation(QString))); QObject::connect(_singleDirJob, SIGNAL(etag(QString)), this, SIGNAL(etag(QString))); + + if (!_firstFolderProcessed) { + _singleDirJob->setIsRootPath(); + } + _singleDirJob->start(); } @@ -441,7 +453,12 @@ void DiscoveryMainThread::singleDirectoryJobResultSlot(const QListlist = result; _currentDiscoveryDirectoryResult->code = 0; _currentDiscoveryDirectoryResult->listIndex = 0; - _currentDiscoveryDirectoryResult = 0; // the sync thread owns it now + _currentDiscoveryDirectoryResult = 0; // the sync thread owns it now + + if (!_firstFolderProcessed) { + _firstFolderProcessed = true; + _dataFingerprint = _singleDirJob->_dataFingerprint; + } _discoveryJob->_vioMutex.lock(); _discoveryJob->_vioWaitCondition.wakeAll(); diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 6786065ccd1..b442d9a2031 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -81,6 +81,8 @@ class DiscoverySingleDirectoryJob : public QObject { Q_OBJECT public: explicit DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent = 0); + // Specify thgat this is the root and we need to check the data-fingerprint + void setIsRootPath() { _isRootPath = true; } void start(); void abort(); // This is not actually a network job, it is just a job @@ -100,8 +102,15 @@ private slots: QString _etagConcatenation; QString _firstEtag; AccountPtr _account; + // The first result is for the directory itself and need to be ignored. + // This flag is true if it was already ignored. bool _ignoredFirst; + // Set to true if this is the root path and we need to check the data-fingerprint + bool _isRootPath; QPointer _lsColJob; + +public: + QByteArray _dataFingerprint; }; // Lives in main thread. Deleted by the SyncEngine @@ -115,13 +124,16 @@ class DiscoveryMainThread : public QObject { AccountPtr _account; DiscoveryDirectoryResult *_currentDiscoveryDirectoryResult; qint64 *_currentGetSizeResult; + bool _firstFolderProcessed; public: DiscoveryMainThread(AccountPtr account) : QObject(), _account(account), - _currentDiscoveryDirectoryResult(0), _currentGetSizeResult(0) + _currentDiscoveryDirectoryResult(0), _currentGetSizeResult(0), _firstFolderProcessed(false) { } void abort(); + QByteArray _dataFingerprint; + public slots: // From DiscoveryJob: diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index c5a4d6666a8..7de6b0ad216 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -402,7 +402,7 @@ void OwncloudPropagator::start(const SyncFileItemVector& items) connect(_rootJob.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), this, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &))); connect(_rootJob.data(), SIGNAL(progress(const SyncFileItem &,quint64)), this, SIGNAL(progress(const SyncFileItem &,quint64))); - connect(_rootJob.data(), SIGNAL(finished(SyncFileItem::Status)), this, SLOT(emitFinished())); + connect(_rootJob.data(), SIGNAL(finished(SyncFileItem::Status)), this, SLOT(emitFinished(SyncFileItem::Status))); connect(_rootJob.data(), SIGNAL(ready()), this, SLOT(scheduleNextJob()), Qt::QueuedConnection); qDebug() << "Using QNAM/HTTP parallel code path"; diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index cac625410d1..cad94c9eeef 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -321,7 +321,7 @@ class OwncloudPropagator : public QObject { if (_rootJob) { _rootJob->abort(); } - emitFinished(); + emitFinished(SyncFileItem::NormalError); } // timeout in seconds @@ -349,9 +349,9 @@ class OwncloudPropagator : public QObject { private slots: /** Emit the finished signal and make sure it is only emitted once */ - void emitFinished() { + void emitFinished(SyncFileItem::Status status) { if (!_finishedEmited) - emit finished(); + emit finished(status == SyncFileItem::Success); _finishedEmited = true; } @@ -360,7 +360,7 @@ private slots: signals: void itemCompleted(const SyncFileItem &, const PropagatorJob &); void progress(const SyncFileItem&, quint64 bytes); - void finished(); + void finished(bool success); /** Emitted when propagation has problems with a locked file. */ void seenLockedFile(const QString &fileName); diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index b609fefcf51..40df952c6cb 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -915,7 +915,16 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) return; } } - if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2) { + + 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) { + qDebug() << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint; + restoreOldFiles(); + } else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2) { qDebug() << "All the changes are bringing files in the past, asking the user"; // this typically happen when a backup is restored on the server bool restore = false; @@ -958,7 +967,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &))); connect(_propagator.data(), SIGNAL(progress(const SyncFileItem &,quint64)), this, SLOT(slotProgress(const SyncFileItem &,quint64))); - connect(_propagator.data(), SIGNAL(finished()), this, SLOT(slotFinished()), Qt::QueuedConnection); + 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))); @@ -1026,10 +1035,14 @@ void SyncEngine::slotItemCompleted(const SyncFileItem &item, const PropagatorJob emit itemCompleted(item, job); } -void SyncEngine::slotFinished() +void SyncEngine::slotFinished(bool success) { _anotherSyncNeeded = _anotherSyncNeeded || _propagator->_anotherSyncNeeded; + if (success) { + _journal->setDataFingerprint(_discoveryMainThread->_dataFingerprint); + } + // emit the treewalk results. if( ! _journal->postSyncCleanup( _seenFiles, _temporarilyUnavailablePaths ) ) { qDebug() << "Cleaning of synced "; @@ -1037,7 +1050,7 @@ void SyncEngine::slotFinished() _journal->commit("All Finished.", false); emit treeWalkResult(_syncedItems); - finalize(true); // FIXME: should it be true if there was errors? + finalize(success); } void SyncEngine::finalize(bool success) diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index f9e8ba3f663..b2e5152e945 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -152,7 +152,7 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject private slots: void slotRootEtagReceived(const QString &); void slotItemCompleted(const SyncFileItem& item, const PropagatorJob & job); - void slotFinished(); + void slotFinished(bool success); void slotProgress(const SyncFileItem& item, quint64 curent); void slotDiscoveryJobFinished(int updateResult); void slotCleanPollsJobAborted(const QString &error); diff --git a/src/libsync/syncjournaldb.cpp b/src/libsync/syncjournaldb.cpp index 7d7197c1c32..afea5ec7728 100644 --- a/src/libsync/syncjournaldb.cpp +++ b/src/libsync/syncjournaldb.cpp @@ -286,6 +286,13 @@ bool SyncJournalDb::checkConnect() return sqlFail("Create table version", createQuery); } + // create the checksumtype table. + createQuery.prepare("CREATE TABLE IF NOT EXISTS datafingerprint(" + "fingerprint TEXT UNIQUE" + ");"); + if (!createQuery.exec()) { + return sqlFail("Create table datafingerprint", createQuery); + } createQuery.prepare("CREATE TABLE IF NOT EXISTS version(" "major INTEGER(8)," @@ -436,6 +443,14 @@ bool SyncJournalDb::checkConnect() _insertChecksumTypeQuery.reset(new SqlQuery(_db)); _insertChecksumTypeQuery->prepare("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)"); + _getDataFingerprintQuery.reset(new SqlQuery(_db)); + _getDataFingerprintQuery->prepare("SELECT fingerprint FROM datafingerprint"); + + _setDataFingerprintQuery1.reset(new SqlQuery(_db)); + _setDataFingerprintQuery1->prepare("DELETE FROM datafingerprint;"); + _setDataFingerprintQuery2.reset(new SqlQuery(_db)); + _setDataFingerprintQuery2->prepare("INSERT INTO datafingerprint (fingerprint) VALUES (?1);"); + // don't start a new transaction now commitInternal(QString("checkConnect End"), false); @@ -472,6 +487,9 @@ void SyncJournalDb::close() _getChecksumTypeIdQuery.reset(0); _getChecksumTypeQuery.reset(0); _insertChecksumTypeQuery.reset(0); + _getDataFingerprintQuery.reset(0); + _setDataFingerprintQuery1.reset(0); + _setDataFingerprintQuery2.reset(0); _db.close(); _avoidReadFromDbOnNextSyncFilter.clear(); @@ -1602,6 +1620,49 @@ int SyncJournalDb::mapChecksumType(const QByteArray& checksumType) return _getChecksumTypeIdQuery->intValue(0); } +QByteArray SyncJournalDb::dataFingerprint() +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) { + return QByteArray(); + } + + _getDataFingerprintQuery->reset_and_clear_bindings(); + if (!_getDataFingerprintQuery->exec()) { + qWarning() << "Error SQL statement dataFingerprint: " + << _getDataFingerprintQuery->lastQuery() << " :" + << _getDataFingerprintQuery->error(); + return QByteArray(); + } + + if (!_getDataFingerprintQuery->next()) { + return QByteArray(); + } + return _getDataFingerprintQuery->baValue(0); +} + +void SyncJournalDb::setDataFingerprint(const QByteArray &dataFingerprint) +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) { + return; + } + + _setDataFingerprintQuery1->reset_and_clear_bindings(); + if (!_setDataFingerprintQuery1->exec()) { + qWarning() << "Error SQL statement setDataFingerprint1: " + << _setDataFingerprintQuery1->lastQuery() << " :" + << _setDataFingerprintQuery1->error(); + } + + _setDataFingerprintQuery2->reset_and_clear_bindings(); + _setDataFingerprintQuery2->bindValue(1, dataFingerprint); + if (!_setDataFingerprintQuery2->exec()) { + qWarning() << "Error SQL statement setDataFingerprint2: " + << _setDataFingerprintQuery2->lastQuery() << " :" + << _setDataFingerprintQuery2->error(); + } +} void SyncJournalDb::commit(const QString& context, bool startTrans) { diff --git a/src/libsync/syncjournaldb.h b/src/libsync/syncjournaldb.h index c6b40bf4a92..ef03cdb0c02 100644 --- a/src/libsync/syncjournaldb.h +++ b/src/libsync/syncjournaldb.h @@ -154,6 +154,12 @@ class OWNCLOUDSYNC_EXPORT SyncJournalDb : public QObject */ QByteArray getChecksumType(int checksumTypeId); + /** + * The data-fingerprint used to detect backup + */ + void setDataFingerprint(const QByteArray &dataFingerprint); + QByteArray dataFingerprint(); + private: bool updateDatabaseStructure(); bool updateMetadataTableStructure(); @@ -196,6 +202,9 @@ class OWNCLOUDSYNC_EXPORT SyncJournalDb : public QObject QScopedPointer _getChecksumTypeIdQuery; QScopedPointer _getChecksumTypeQuery; QScopedPointer _insertChecksumTypeQuery; + QScopedPointer _getDataFingerprintQuery; + QScopedPointer _setDataFingerprintQuery1; + QScopedPointer _setDataFingerprintQuery2; /* This is the list of paths we called avoidReadFromDbOnNextSync on. * It means that they should not be written to the DB in any case since doing