From: Christian Kamm Date: Tue, 22 Dec 2015 12:02:02 +0000 (+0100) Subject: Propagator: Deal with directories becoming files #4302 X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~1254^2~115 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=d4edab02b0ce9734fa946a73d63282b75a443989;p=nextcloud-desktop.git Propagator: Deal with directories becoming files #4302 Note, in particular the revised order of directory deletion jobs. --- diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 569997925..15bb001f5 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -543,4 +543,25 @@ QByteArray FileSystem::calcAdler32( const QString& filename ) } #endif +QString FileSystem::makeConflictFileName(const QString &fn, const QDateTime &dt) +{ + QString conflictFileName(fn); + // Add _conflict-XXXX before the extension. + int dotLocation = conflictFileName.lastIndexOf('.'); + // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) + if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { + dotLocation = conflictFileName.size(); + } + QString timeString = dt.toString("yyyyMMdd-hhmmss"); + + // Additional marker + QByteArray conflictFileUserName = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); + if (conflictFileUserName.isEmpty()) + conflictFileName.insert(dotLocation, "_conflict-" + timeString); + else + conflictFileName.insert(dotLocation, "_conflict_" + QString::fromUtf8(conflictFileUserName) + "-" + timeString); + + return conflictFileName; +} + } // namespace OCC diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 21e697536..877687aa2 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -169,6 +169,11 @@ QByteArray OWNCLOUDSYNC_EXPORT calcSha1( const QString& fileName ); QByteArray OWNCLOUDSYNC_EXPORT calcAdler32( const QString& fileName ); #endif +/** + * Returns a file name based on \a fn that's suitable for a conflict. + */ +QString OWNCLOUDSYNC_EXPORT makeConflictFileName(const QString &fn, const QDateTime &dt); + } /** @} */ diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index 9f86f8633..f01660145 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -346,7 +346,7 @@ void OwncloudPropagator::start(const SyncFileItemVector& items) if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { // We do the removal of directories at the end, because there might be moves from // these directories that will happen later. - directoriesToRemove.append(dir); + directoriesToRemove.prepend(dir); removedDirectory = item->_file + "/"; // We should not update the etag of parent directories of the removed directory @@ -362,7 +362,26 @@ void OwncloudPropagator::start(const SyncFileItemVector& items) } directories.push(qMakePair(item->destination() + "/" , dir)); } else if (PropagateItemJob* current = createJob(item)) { - directories.top().second->append(current); + // If the target of a job is currently a directory, we need to remove it! + // This can happen when what used to be a directory changed to a file on the + // server an a PropagateLocalRename or PropageDownload job wants to run. + if (item->_direction == SyncFileItem::Down + && QFileInfo(getFilePath(item->_file)).isDir()) { + // The DirectoryConflict job *must* run before the file propagation job + // and we also need to make sure other jobs that deal with the files + // in the directory (like removes or moves, in particular of other + // directories!) run first. + // Making it a directory job ensures that moves run first and that the + // (potential) directory rename happens before the file propagation. + // Prepending all jobs to directoriesToRemove ensures that removals of + // subdirectories happen before the directory is renamed. + PropagateDirectory *dir = new PropagateDirectory(this, item); + dir->_firstJob.reset(new PropagateLocalDirectoryConflict(this, item)); + dir->append(current); + directoriesToRemove.prepend(dir); + } else { + directories.top().second->append(current); + } } } diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 15189bbfe..718250d04 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -73,12 +73,16 @@ public: /** Jobs can be run in parallel to this job */ FullParallelism, - /** This job does not support parallelism, and no other job shall - be started until this one has finished */ + + /** No other job shall be started until this one has finished. + So this job is guaranteed to finish before any jobs below it + are executed. */ WaitForFinished, - /** This job supports parallelism with other jobs in the same directory, but it should - not be parallelized with jobs in other directories (typically a move operation) */ + /** A job with this parallelism will allow later jobs to start and + run in parallel as long as they aren't PropagateDirectory jobs. + When the first directory job is encountered, no further jobs + will be started until this one is finished. */ WaitForFinishedInParentDirectory }; diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 305f8977a..689caa2c3 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -551,28 +551,6 @@ void PropagateDownloadFileQNAM::slotChecksumFail( const QString& errMsg ) done(SyncFileItem::SoftError, errMsg ); // tr("The file downloaded with a broken checksum, will be redownloaded.")); } -QString makeConflictFileName(const QString &fn, const QDateTime &dt) -{ - QString conflictFileName(fn); - // Add _conflict-XXXX before the extension. - int dotLocation = conflictFileName.lastIndexOf('.'); - // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) - if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { - dotLocation = conflictFileName.size(); - } - QString timeString = dt.toString("yyyyMMdd-hhmmss"); - - // Additional marker - QByteArray conflictFileUserName = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); - if (conflictFileUserName.isEmpty()) - conflictFileName.insert(dotLocation, "_conflict-" + timeString); - else - conflictFileName.insert(dotLocation, "_conflict_" + QString::fromUtf8(conflictFileUserName) + "-" + timeString); - - return conflictFileName; -} - - namespace { // Anonymous namespace for the recall feature static QString makeRecallFileName(const QString &fn) { @@ -636,7 +614,7 @@ void PropagateDownloadFileQNAM::downloadFinished() && !FileSystem::fileEquals(fn, _tmpFile.fileName()); if (isConflict) { QString renameError; - QString conflictFileName = makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime)); + QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime)); if (!FileSystem::rename(fn, conflictFileName, &renameError)) { //If the rename fails, don't replace it. done(SyncFileItem::SoftError, renameError); diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 30914ed19..24754615b 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -256,4 +256,39 @@ void PropagateLocalRename::start() done(SyncFileItem::Success); } +void PropagateLocalDirectoryConflict::start() +{ + if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) + return; + + QString existingDir = _propagator->getFilePath(_item->_file); + if (!QFileInfo(existingDir).isDir()) { + done(SyncFileItem::Success); + return; + } + + // Delete the directory if it is empty! + QDir dir(existingDir); + if (dir.entryList(QDir::NoDotAndDotDot|QDir::AllEntries).count() == 0) { + if (dir.rmdir(existingDir)) { + done(SyncFileItem::Success); + return; + } + // on error, just try to move it away... + } + + QString conflictDir = FileSystem::makeConflictFileName( + existingDir, Utility::qDateTimeFromTime_t(_item->_modtime)); + + _propagator->addTouchedFile(existingDir); + _propagator->addTouchedFile(conflictDir); + QString renameError; + if (!FileSystem::rename(existingDir, conflictDir, &renameError)) { + done(SyncFileItem::NormalError, renameError); + return; + } + + done(SyncFileItem::Success); +} + } diff --git a/src/libsync/propagatorjobs.h b/src/libsync/propagatorjobs.h index 2b3b9a490..5a81d305f 100644 --- a/src/libsync/propagatorjobs.h +++ b/src/libsync/propagatorjobs.h @@ -82,5 +82,24 @@ public: JobParallelism parallelism() Q_DECL_OVERRIDE { return WaitForFinishedInParentDirectory; } }; +/** + * Moves away a local directory when it should become a file. + * + * Example: Locally there's directory foo/ with three files in it, + * one of them ignored, while the server has a file foo. + * In this case, foo/ fill be moved to foo-conflict.../ and the + * file will be downloaded to foo. + * + * If the directory is empty, it will be removed instead. + * + * @ingroup libsync + */ +class PropagateLocalDirectoryConflict : public PropagateItemJob { + Q_OBJECT +public: + PropagateLocalDirectoryConflict (OwncloudPropagator* propagator, const SyncFileItemPtr& item) : PropagateItemJob(propagator, item) {} + void start() Q_DECL_OVERRIDE; + JobParallelism parallelism() Q_DECL_OVERRIDE { return WaitForFinishedInParentDirectory; } +}; }