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
This commit is contained in:
Olivier Goffart 2016-01-05 11:47:17 +01:00
parent d4edab02b0
commit 1bb76f5343
4 changed files with 115 additions and 18 deletions

View file

@ -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)

View file

@ -222,7 +222,9 @@ public slots:
*/
void slotTerminateSync();
// connected to the corresponding signals in the SyncEngine
void slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool*);
void slotAboutToRestoreBackup(bool*);
/**

View file

@ -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);

View file

@ -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;