fix behavior of folder rename with deep hierarchy inside it
authorMatthieu Gallien <matthieu.gallien@nextcloud.com>
Thu, 17 Nov 2022 13:45:11 +0000 (14:45 +0100)
committerMatthieu Gallien <matthieu.gallien@nextcloud.com>
Thu, 17 Nov 2022 13:47:01 +0000 (14:47 +0100)
propagate renaming of parent folders to child recursively

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
src/libsync/propagatorjobs.cpp
src/libsync/propagatorjobs.h

index 8f57eb18d30716cd2e9caeafa187fdeadf728c02..1013b802709ca71a728620f0a5b8818fbfc78a9d 100644 (file)
@@ -211,16 +211,26 @@ void PropagateLocalMkdir::startLocalMkdir()
     done(resultStatus);
 }
 
+PropagateLocalRename::PropagateLocalRename(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
+    : PropagateItemJob(propagator, item)
+{
+    qCDebug(lcPropagateLocalRename) << _item->_file << _item->_renameTarget << _item->_originalFile;
+}
+
 void PropagateLocalRename::start()
 {
     if (propagator()->_abortRequested)
         return;
 
-    QString existingFile = propagator()->fullLocalPath(propagator()->adjustRenamedPath(_item->_file));
-    QString targetFile = propagator()->fullLocalPath(_item->_renameTarget);
+    const auto previousNameInDb = propagator()->adjustRenamedPath(_item->_file);
+    const auto existingFile = propagator()->fullLocalPath(propagator()->adjustRenamedPath(_item->_file));
+    const auto targetFile = propagator()->fullLocalPath(_item->_renameTarget);
+
+    const auto fileAlreadyMoved = !QFileInfo::exists(propagator()->fullLocalPath(_item->_originalFile));
 
     // if the file is a file underneath a moved dir, the _item->file is equal
     // to _item->renameTarget and the file is not moved as a result.
+    qCDebug(lcPropagateLocalRename) << _item->_file << _item->_renameTarget << _item->_originalFile << previousNameInDb << (fileAlreadyMoved ? "original file has already moved" : "original file is still there");
     if (_item->_file != _item->_renameTarget) {
         propagator()->reportProgress(*_item, 0);
         qCDebug(lcPropagateLocalRename) << "MOVE " << existingFile << " => " << targetFile;
@@ -249,15 +259,37 @@ void PropagateLocalRename::start()
     }
 
     SyncJournalFileRecord oldRecord;
-    if (!propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord)) {
+    if (!propagator()->_journal->getFileRecord(fileAlreadyMoved ? previousNameInDb : _item->_originalFile, &oldRecord)) {
         qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << _item->_originalFile;
         done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(_item->_originalFile));
         return;
     }
-    if (!propagator()->_journal->deleteFileRecord(_item->_originalFile)) {
-        qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << _item->_originalFile;
-        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile));
-        return;
+
+    const auto deleteOldRecord = [this] (const QString &fileName) -> bool
+    {
+        SyncJournalFileRecord oldRecord;
+        if (!propagator()->_journal->getFileRecord(fileName, &oldRecord)) {
+            qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << fileName;
+            done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(fileName));
+            return false;
+        }
+        if (!propagator()->_journal->deleteFileRecord(fileName)) {
+            qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << fileName;
+            done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(fileName));
+            return false;
+        }
+
+        return true;
+    };
+
+    if (fileAlreadyMoved) {
+        if (!deleteOldRecord(previousNameInDb)) {
+            return;
+        }
+    } else {
+        if (!deleteOldRecord(_item->_originalFile)) {
+            return;
+        }
     }
 
     auto &vfs = propagator()->syncOptions()._vfs;
@@ -282,6 +314,40 @@ void PropagateLocalRename::start()
             return;
         }
     } else {
+        auto dbQueryResult = propagator()->_journal->getFilesBelowPath(oldFile.toUtf8(), [oldFile, this] (const SyncJournalFileRecord &record) -> void {
+            const auto oldFileName = record._path;
+            const auto oldFileNameString = QString::fromUtf8(oldFileName);
+            auto newFileNameString = oldFileNameString;
+            newFileNameString.replace(0, oldFile.length(), _item->_renameTarget);
+
+            if (oldFileNameString == newFileNameString) {
+                return;
+            }
+
+            SyncJournalFileRecord oldRecord;
+            if (!propagator()->_journal->getFileRecord(oldFileName, &oldRecord)) {
+                qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << oldFileName;
+                done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(oldFileNameString));
+                return;
+            }
+            if (!propagator()->_journal->deleteFileRecord(oldFileName)) {
+                qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << oldFileName;
+                done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(oldFileNameString));
+                return;
+            }
+
+            auto newItem = SyncFileItem::fromSyncJournalFileRecord(oldRecord);
+            newItem->_file = newFileNameString;
+            const auto result = propagator()->updateMetadata(*newItem);
+            if (!result) {
+                done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+                return;
+            }
+        });
+        if (!dbQueryResult) {
+            done(SyncFileItem::FatalError, tr("Failed to propagate directory rename in hierarchy"));
+            return;
+        }
         propagator()->_renamedDirectories.insert(oldFile, _item->_renameTarget);
         if (!PropagateRemoteMove::adjustSelectiveSync(propagator()->_journal, oldFile, _item->_renameTarget)) {
             done(SyncFileItem::FatalError, tr("Failed to rename file"));
index c1afb1959fbf60e7adbeedd03350a0f6a84793d5..3fa37ac03ce0c07b4b54584c3dd6fb534f902f9c 100644 (file)
@@ -86,10 +86,7 @@ class PropagateLocalRename : public PropagateItemJob
 {
     Q_OBJECT
 public:
-    PropagateLocalRename(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
-        : PropagateItemJob(propagator, item)
-    {
-    }
+    PropagateLocalRename(OwncloudPropagator *propagator, const SyncFileItemPtr &item);
     void start() override;
     JobParallelism parallelism() override { return _item->isDirectory() ? WaitForFinished : FullParallelism; }
 };