From 1bb76f534391c6f3f0709a2297b9ae6700b46efd Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 5 Jan 2016 11:47:17 +0100 Subject: [PATCH] Attempt to recover from backup restoration on the server If all the files bring us to past timestamp, it is possibly a backup restoration in the server. In which case we want don't want to just overwrite newer files with the older ones. Issue #2325 --- src/gui/folder.cpp | 21 +++++++++ src/gui/folder.h | 2 + src/libsync/syncengine.cpp | 89 ++++++++++++++++++++++++++++++-------- src/libsync/syncengine.h | 21 ++++++++- 4 files changed, 115 insertions(+), 18 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 4f262c232..5d620d298 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -914,6 +914,8 @@ void Folder::startSync(const QStringList &pathList) //direct connection so the message box is blocking the sync. connect(_engine.data(), SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)), SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*))); + connect(_engine.data(), SIGNAL(aboutToRestoreBackup(bool*)), + SLOT(slotAboutToRestoreBackup(bool*))); connect(_engine.data(), SIGNAL(folderDiscovered(bool,QString)), this, SLOT(slotFolderDiscovered(bool,QString))); connect(_engine.data(), SIGNAL(transmissionProgress(ProgressInfo)), this, SLOT(slotTransmissionProgress(ProgressInfo))); connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)), @@ -1179,6 +1181,25 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel) } } +void Folder::slotAboutToRestoreBackup(bool *restore) +{ + QString msg = + tr("This sync would reset the files to an erlier time in the sync folder '%1'.\n" + "This might be because a backup was restored on the server.\n" + "Continuing the sync as normal will cause all your files to be overwritten by an older " + "file in an earlier state. " + "Do you want to keep your local most recent files as conflict files?"); + QMessageBox msgBox(QMessageBox::Warning, tr("Backup detected"), + msg.arg(alias())); + msgBox.addButton(tr("Normal Synchronisation"), QMessageBox::DestructiveRole); + QPushButton* keepBtn = msgBox.addButton(tr("Keep Local Files as Conflict"), QMessageBox::AcceptRole); + + if (msgBox.exec() == -1) { + *restore = true; + return; + } + *restore = msgBox.clickedButton() == keepBtn; +} void FolderDefinition::save(QSettings& settings, const FolderDefinition& folder) diff --git a/src/gui/folder.h b/src/gui/folder.h index ea7ad712c..1b8c71036 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -222,7 +222,9 @@ public slots: */ void slotTerminateSync(); + // connected to the corresponding signals in the SyncEngine void slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool*); + void slotAboutToRestoreBackup(bool*); /** diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index cf99c6365..54f33a3ec 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -68,6 +68,8 @@ SyncEngine::SyncEngine(AccountPtr account, CSYNC *ctx, const QString& localPath, , _progressInfo(new ProgressInfo) , _hasNoneFiles(false) , _hasRemoveFile(false) + , _hasForwardInTimeFiles(false) + , _backInTimeFiles(0) , _uploadLimit(0) , _downloadLimit(0) , _newBigFolderSizeLimit(-1) @@ -541,17 +543,27 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote ) case CSYNC_INSTRUCTION_ERROR: dir = SyncFileItem::None; break; - case CSYNC_INSTRUCTION_EVAL: - case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_SYNC: - case CSYNC_INSTRUCTION_STAT_ERROR: - default: - dir = remote ? SyncFileItem::Down : SyncFileItem::Up; - if (!remote && file->instruction == CSYNC_INSTRUCTION_SYNC) { + if (!remote) { // An upload of an existing file means that the file was left unchanged on the server // This counts as a NONE for detecting if all the files on the server were changed _hasNoneFiles = true; + } else if (!item->_isDirectory) { + if (std::difftime(file->modtime, file->other.modtime) < 0) { + // We are going back on time + _backInTimeFiles++; + qDebug() << file->path << "has a timestamp earlier than the local file"; + } else { + _hasForwardInTimeFiles = true; + } } + dir = remote ? SyncFileItem::Down : SyncFileItem::Up; + break; + case CSYNC_INSTRUCTION_NEW: + case CSYNC_INSTRUCTION_EVAL: + case CSYNC_INSTRUCTION_STAT_ERROR: + default: + dir = remote ? SyncFileItem::Down : SyncFileItem::Up; break; } @@ -812,6 +824,26 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) (*it)->_file = adjustRenamedPath((*it)->_file); } + if (!_hasNoneFiles && _hasRemoveFile) { + qDebug() << Q_FUNC_INFO << "All the files are going to be changed, asking the user"; + bool cancel = false; + emit aboutToRemoveAllFiles(_syncedItems.first()->_direction, &cancel); + if (cancel) { + qDebug() << Q_FUNC_INFO << "Abort sync"; + finalize(false); + return; + } + } + 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; + emit aboutToRestoreBackup(&restore); + if (restore) { + restoreOldFiles(); + } + } + // Sort items per destination std::sort(_syncedItems.begin(), _syncedItems.end()); @@ -824,17 +856,6 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) emit transmissionProgress(*_progressInfo); _progressInfo->start(); - if (!_hasNoneFiles && _hasRemoveFile) { - qDebug() << Q_FUNC_INFO << "All the files are going to be changed, asking the user"; - bool cancel = false; - emit aboutToRemoveAllFiles(_syncedItems.first()->_direction, &cancel); - if (cancel) { - qDebug() << Q_FUNC_INFO << "Abort sync"; - finalize(false); - return; - } - } - // post update phase script: allow to tweak stuff by a custom script in debug mode. if( !qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT").isEmpty() ) { #ifndef NDEBUG @@ -1212,6 +1233,40 @@ QByteArray SyncEngine::getPermissions(const QString& file) const return _remotePerms.value(file); } +void SyncEngine::restoreOldFiles() +{ + /* 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 + */ + + for (auto it = _syncedItems.begin(); it != _syncedItems.end(); ++it) { + if ((*it)->_direction != SyncFileItem::Down) + continue; + + switch ((*it)->_instruction) { + case CSYNC_INSTRUCTION_SYNC: + qDebug() << "restoreOldFiles: RESTORING" << (*it)->_file; + (*it)->_instruction = CSYNC_INSTRUCTION_CONFLICT; + break; + case CSYNC_INSTRUCTION_REMOVE: + qDebug() << "restoreOldFiles: RESTORING" << (*it)->_file; + (*it)->_should_update_metadata = true; + (*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; + } + } +} + bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s) { Q_UNUSED(t); diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index f235c4aa2..7e2930999 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -120,7 +120,18 @@ signals: void finished(bool success); void started(); + /** + * Emited when the sync engine detects that all the files have been removed or change. + * This usually happen when the server was reset or something. + * Set *cancel to true in a slot connected from this signal to abort the sync. + */ void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel); + /** + * Emited when the sync engine detects that all the files are changed to dates in the past. + * This usually happen when a backup was restored on the server from an earlier date. + * Set *restore to true in a slot connected from this signal to re-upload all files. + */ + void aboutToRestoreBackup(bool *restore); // A new folder was discovered and was not synced because of the confirmation feature void newBigFolder(const QString &folder); @@ -206,8 +217,16 @@ private: void checkForPermission(); QByteArray getPermissions(const QString& file) const; - bool _hasNoneFiles; // true if there is at least one file with instruction NONE + /** + * Instead of downloading files from the server, upload the files to the server + */ + void restoreOldFiles(); + + bool _hasNoneFiles; // true if there is at least one file which was not changed on the server bool _hasRemoveFile; // true if there is at leasr one file with instruction REMOVE + bool _hasForwardInTimeFiles; // true if there is at least one file from the server that goes forward in time + int _backInTimeFiles; // number of files which goes back in time from the server + int _uploadLimit; int _downloadLimit;