Repository: Infomaniak/desktop-kDrive
Pull request #738: Normalize names in sorter
Author: login: ClementKunz
Created: 2025-05-07T12:33:02Z | Merged: 2025-05-09T13:57:39Z

OperationSorterWorker should use normalized names and paths.


Diff:
diff --git a/src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp b/src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
index 08b90fdef5..b44536a518 100644
--- a/src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
+++ b/src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
@@ -44,12 +44,12 @@ void OperationSorterFilter::filterOperations() {
         filterMoveBeforeMoveHierarchyFlipCandidates(op, moveBeforeMoveHierarchyFlipCandidates);
     }
 }
-
+// delete before move, e.g. user deletes an object at path "x" and moves another object "a" to "x".
 void OperationSorterFilter::filterDeleteBeforeMoveCandidates(const SyncOpPtr &op, NameToOpMap &deleteBeforeMoveCandidates) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete) || op->affectedNode()->hasChangeEvent(OperationType::Move)) {
+    if (op->type() == OperationType::Delete || op->type() == OperationType::Move) {
         if (const auto [_, ok] = deleteBeforeMoveCandidates.try_emplace(op->affectedNode()->normalizedName(), op); !ok) {
             const auto &otherOp = deleteBeforeMoveCandidates.at(op->affectedNode()->normalizedName());
-            if (op->targetSide() != otherOp->targetSide()) {
+            if (op->targetSide() != otherOp->targetSide() || op->type() == otherOp->type()) {
                 return;
             }
             (void) _fixDeleteBeforeMoveCandidates.emplace_back(op, otherOp);
@@ -58,21 +58,22 @@ void OperationSorterFilter::filterDeleteBeforeMoveCandidates(const SyncOpPtr &op
 }
 
 void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op, NameToOpMap &moveBeforeCreateCandidates) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Create)) {
-        if (const auto &[_, ok] = moveBeforeCreateCandidates.try_emplace(op->affectedNode()->normalizedName(), op); !ok) {
-            const auto &otherOp = moveBeforeCreateCandidates.at(op->affectedNode()->normalizedName());
-            if (op->targetSide() != otherOp->targetSide() || !otherOp->affectedNode()->hasChangeEvent(OperationType::Move)) {
+    if (op->type() == OperationType::Create) {
+        const SyncName name = op->affectedNode()->normalizedName();
+        if (const auto &[_, ok] = moveBeforeCreateCandidates.try_emplace(name, op); !ok) {
+            const auto &otherOp = moveBeforeCreateCandidates.at(name);
+            if (op->targetSide() != otherOp->targetSide() || otherOp->type() != OperationType::Move) {
                 return;
             }
             (void) _fixMoveBeforeCreateCandidates.emplace_back(op, otherOp);
         }
     }
-    if (op->affectedNode()->hasChangeEvent(OperationType::Move)) {
-        if (const auto &[_, ok] =
-                    moveBeforeCreateCandidates.try_emplace(op->affectedNode()->moveOriginInfos().path().filename().native(), op);
-            !ok) {
-            const auto &otherOp = moveBeforeCreateCandidates.at(op->affectedNode()->normalizedName());
-            if (op->targetSide() != otherOp->targetSide() || !otherOp->affectedNode()->hasChangeEvent(OperationType::Create)) {
+    if (op->type() == OperationType::Move) {
+        const SyncName name = op->affectedNode()->moveOriginInfos().normalizedPath().filename().native();
+        if (const auto &[_, ok] = moveBeforeCreateCandidates.try_emplace(name, op); !ok) {
+            // move before create, e.g. user moves an object "a" to "b" and creates another object at "a".
+            const auto &otherOp = moveBeforeCreateCandidates.at(name);
+            if (op->targetSide() != otherOp->targetSide() || otherOp->type() != OperationType::Create) {
                 return;
             }
             (void) _fixMoveBeforeCreateCandidates.emplace_back(op, otherOp);
@@ -82,34 +83,40 @@ void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &deletedDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveOriginPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto deletedPath = op->affectedNode()->getPath();
-        (void) deletedDirectoryPaths.try_emplace(deletedPath, op);
+
+        SyncPath normalizedDeletedPath;
+        if (const auto deletedPath = op->affectedNode()->getPath();
+            !Utility::normalizedSyncPath(deletedPath, normalizedDeletedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(deletedPath));
+            normalizedDeletedPath = deletedPath;
+        }
+        (void) deletedDirectoryPaths.try_emplace(normalizedDeletedPath, op);
 
         // Check if any of the moved item origin path is inside the deleted folder.
-        for (const auto &[path, moveOp]: moveOriginPaths) {
+        for (const auto &[normalizedMovePath, moveOp]: moveOriginPaths) {
             if (op->targetSide() != moveOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(path, deletedPath)) {
+            if (Utility::isDescendantOrEqual(normalizedMovePath, normalizedDeletedPath)) {
                 (void) _fixMoveBeforeDeleteCandidates.emplace_back(op, moveOp);
             }
         }
         return;
     }
 
-    if (op->affectedNode()->hasChangeEvent(OperationType::Move)) {
-        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().path(), op);
+    if (op->type() == OperationType::Move) {
+        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().normalizedPath(), op);
 
         // Check if any of the deleted path contains the origin path.
-        for (const auto &[path, deleteOp]: deletedDirectoryPaths) {
+        for (const auto &[normalizedDeletePath, deleteOp]: deletedDirectoryPaths) {
             if (op->targetSide() != deleteOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(op->affectedNode()->moveOriginInfos().path(), path)) {
+            if (Utility::isDescendantOrEqual(op->affectedNode()->moveOriginInfos().normalizedPath(), normalizedDeletePath)) {
                 (void) _fixMoveBeforeDeleteCandidates.emplace_back(op, deleteOp);
             }
         }
@@ -118,57 +125,65 @@ void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterCreateBeforeMoveCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &createdDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveDestinationPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Create)) {
+    if (op->type() == OperationType::Create) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto createdPath = op->affectedNode()->getPath();
-        (void) createdDirectoryPaths.try_emplace(createdPath, op);
+
+        SyncPath normalizedCreatedPath;
+        if (const auto createdPath = op->affectedNode()->getPath();
+            !Utility::normalizedSyncPath(createdPath, normalizedCreatedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(createdPath));
+            normalizedCreatedPath = createdPath;
+        }
+        (void) createdDirectoryPaths.try_emplace(normalizedCreatedPath, op);
 
         // Check if any of the moved item destination path is inside the created folder.
-        for (const auto &[path, moveOp]: moveDestinationPaths) {
+        for (const auto &[normalizedMovePath, moveOp]: moveDestinationPaths) {
             if (op->targetSide() != moveOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(path, createdPath)) {
+            if (Utility::isDescendantOrEqual(normalizedMovePath, normalizedCreatedPath)) {
                 (void) _fixCreateBeforeMoveCandidates.emplace_back(op, moveOp);
             }
         }
         return;
     }
 
-    if (op->affectedNode()->hasChangeEvent(OperationType::Move)) {
-        const auto destinationPath = op->affectedNode()->getPath();
-        (void) moveDestinationPaths.try_emplace(destinationPath, op);
+    if (op->type() == OperationType::Move) {
+        SyncPath normalizedDestinationPath;
+        if (const auto destinationPath = op->affectedNode()->getPath();
+            !Utility::normalizedSyncPath(destinationPath, normalizedDestinationPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(destinationPath));
+            normalizedDestinationPath = destinationPath;
+        }
+        (void) moveDestinationPaths.try_emplace(normalizedDestinationPath, op);
 
         // Check if any of the created path contains the destination path.
-        for (const auto &[path, createOp]: createdDirectoryPaths) {
+        for (const auto &[normalizedCreatedPath, createOp]: createdDirectoryPaths) {
             if (op->targetSide() != createOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(destinationPath, path)) {
+            if (Utility::isDescendantOrEqual(normalizedDestinationPath, normalizedCreatedPath)) {
                 (void) _fixCreateBeforeMoveCandidates.emplace_back(op, createOp);
             }
         }
     }
 }
 void OperationSorterFilter::filterDeleteBeforeCreateCandidates(const SyncOpPtr &op, NameToOpMap &deleteBeforeCreateCandidates) {
-    /**
-     * @brief delete before create, e.g. user deletes object "x" and then creates a new object at "x".
-     */
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (const auto [_, ok] = deleteBeforeCreateCandidates.try_emplace(op->affectedNode()->normalizedName(), op); !ok) {
             const auto &otherOp = deleteBeforeCreateCandidates.at(op->affectedNode()->normalizedName());
-            if (op->targetSide() != otherOp->targetSide() || !otherOp->affectedNode()->hasChangeEvent(OperationType::Create)) {
+            if (op->targetSide() != otherOp->targetSide() || otherOp->type() != OperationType::Create) {
                 return;
             }
             (void) _fixDeleteBeforeCreateCandidates.emplace_back(op, otherOp);
         }
     }
-    if (op->affectedNode()->hasChangeEvent(OperationType::Create)) {
+    if (op->type() == OperationType::Create) {
         if (const auto [_, ok] = deleteBeforeCreateCandidates.try_emplace(op->affectedNode()->normalizedName(), op); !ok) {
             const auto &otherOp = deleteBeforeCreateCandidates.at(op->affectedNode()->normalizedName());
-            if (op->targetSide() != otherOp->targetSide() || !otherOp->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+            if (op->targetSide() != otherOp->targetSide() || otherOp->type() != OperationType::Delete) {
                 return;
             }
             (void) _fixDeleteBeforeCreateCandidates.emplace_back(op, otherOp);
@@ -178,9 +193,9 @@ void OperationSorterFilter::filterDeleteBeforeCreateCandidates(const SyncOpPtr &
 
 void OperationSorterFilter::filterMoveBeforeMoveOccupiedCandidates(const SyncOpPtr &op, NameToOpMap &moveOriginNames,
                                                                    NameToOpMap &moveDestinationNames) {
-    if (!op->affectedNode()->hasChangeEvent(OperationType::Move)) return;
+    if (op->type() != OperationType::Move) return;
 
-    const SyncName originName = op->affectedNode()->moveOriginInfos().path().filename().native();
+    const SyncName originName = op->affectedNode()->moveOriginInfos().normalizedPath().filename().native();
     const SyncName destinationName = op->affectedNode()->normalizedName();
 
     if (moveOriginNames.contains(destinationName)) {
@@ -205,6 +220,7 @@ void OperationSorterFilter::filterMoveBeforeMoveOccupiedCandidates(const SyncOpP
 }
 
 void OperationSorterFilter::filterEditBeforeMoveCandidates(const SyncOpPtr &op) {
+    // We keep only operations on nodes that have both EDIT and MOVE operations.
     if (op->affectedNode()->hasChangeEvent(OperationType::Edit) && op->affectedNode()->hasChangeEvent(OperationType::Move)) {
         (void) _fixEditBeforeMoveCandidates[op->affectedNode()->id().value()].emplace_back(op);
     }
@@ -212,10 +228,15 @@ void OperationSorterFilter::filterEditBeforeMoveCandidates(const SyncOpPtr &op)
 
 void OperationSorterFilter::filterMoveBeforeMoveHierarchyFlipCandidates(
         const SyncOpPtr &op, std::list<std::pair<SyncOpPtr, SyncPath>> &moveBeforeMoveHierarchyFlipCandidates) {
-    if (!op->affectedNode()->hasChangeEvent(OperationType::Move) || op->nodeType() != NodeType::Directory) return;
+    if (op->type() != OperationType::Move || op->nodeType() != NodeType::Directory) return;
 
-    const auto &originPath = op->affectedNode()->moveOriginInfos().path();
-    const auto &destinationPath = op->affectedNode()->getPath();
+    const auto &originPath = op->affectedNode()->moveOriginInfos().normalizedPath();
+    SyncPath normalizedDestinationPath;
+    if (const auto destinationPath = op->affectedNode()->getPath();
+        !Utility::normalizedSyncPath(destinationPath, normalizedDestinationPath)) {
+        LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(destinationPath));
+        normalizedDestinationPath = destinationPath;
+    }
 
     // Check if any of the moved path contains the destination path.
     for (const auto &[otherOp, otherDestinationPath]: moveBeforeMoveHierarchyFlipCandidates) {
@@ -223,22 +244,22 @@ void OperationSorterFilter::filterMoveBeforeMoveHierarchyFlipCandidates(
             continue;
         }
 
-        const auto &otherOriginPath = otherOp->affectedNode()->moveOriginInfos().path();
+        const auto &otherOriginPath = otherOp->affectedNode()->moveOriginInfos().normalizedPath();
 
-        if (Utility::isStrictDescendant(destinationPath, otherDestinationPath) &&
+        if (Utility::isStrictDescendant(normalizedDestinationPath, otherDestinationPath) &&
             Utility::isStrictDescendant(otherOriginPath, originPath)) {
             // op must be executed after otherOp
             (void) _fixMoveBeforeMoveHierarchyFlipCandidates.emplace_back(op, otherOp);
             continue;
         }
-        if (Utility::isStrictDescendant(otherDestinationPath, destinationPath) &&
+        if (Utility::isStrictDescendant(otherDestinationPath, normalizedDestinationPath) &&
             Utility::isStrictDescendant(originPath, otherOriginPath)) {
             // otherOp must be executed after op
             (void) _fixMoveBeforeMoveHierarchyFlipCandidates.emplace_back(otherOp, op);
         }
     }
 
-    (void) moveBeforeMoveHierarchyFlipCandidates.emplace_back(op, destinationPath);
+    (void) moveBeforeMoveHierarchyFlipCandidates.emplace_back(op, normalizedDestinationPath);
 }
 
 } // namespace KDC
diff --git a/src/libsyncengine/propagation/operation_sorter/operationsorterworker.cpp b/src/libsyncengine/propagation/operation_sorter/operationsorterworker.cpp
index 0478d96d85..6f38cb7930 100644
--- a/src/libsyncengine/propagation/operation_sorter/operationsorterworker.cpp
+++ b/src/libsyncengine/propagation/operation_sorter/operationsorterworker.cpp
@@ -102,9 +102,10 @@ void OperationSorterWorker::fixDeleteBeforeMove() {
 
         const auto deleteNode = deleteOp->affectedNode();
         LOG_IF_FAIL(deleteNode)
-        const auto deleteNodeParentPath = deleteNode->getPath().parent_path();
         NodeId deleteNodeParentId;
-        if (!getIdFromDb(deleteNode->side(), deleteNodeParentPath, deleteNodeParentId)) continue;
+        if (!getIdFromDb(deleteNode->side(), deleteNode->getPath().parent_path(), deleteNodeParentId)) {
+            continue;
+        }
 
         const auto moveNode = moveOp->affectedNode();
         LOG_IF_FAIL(moveNode)
@@ -152,14 +153,7 @@ void OperationSorterWorker::fixMoveBeforeCreate() {
             continue;
         }
 
-        SyncPath normalizedPath;
-        if (!Utility::normalizedSyncPath(moveNode->moveOriginInfos().path(), normalizedPath)) {
-            LOGW_WARN(_logger,
-                      L"Error in Utility::normalizedSyncPath: " << Utility::formatSyncPath(moveNode->moveOriginInfos().path()));
-            normalizedPath = moveNode->moveOriginInfos().path();
-        }
-
-        if (normalizedPath.filename() == createNode->normalizedName()) {
+        if (moveNode->moveOriginInfos().normalizedPath().filename() == createNode->normalizedName()) {
             // move only if createOp is before moveOp
             moveFirstAfterSecond(createOp, moveOp);
         }
@@ -194,9 +188,8 @@ void OperationSorterWorker::fixDeleteBeforeCreate() {
 
         const auto deleteNode = deleteOp->affectedNode();
         LOG_IF_FAIL(deleteNode)
-        const auto deleteNodeParentPath = deleteNode->getPath().parent_path();
         NodeId deleteNodeParentId;
-        if (!getIdFromDb(deleteNode->side(), deleteNodeParentPath, deleteNodeParentId)) {
+        if (!getIdFromDb(deleteNode->side(), deleteNode->getPath().parent_path(), deleteNodeParentId)) {
             continue;
         }
 
@@ -226,18 +219,18 @@ void OperationSorterWorker::fixMoveBeforeMoveOccupied() {
         if (!node->parentNode()) {
             continue;
         }
-        const auto nodePath = node->getPath();
-        const auto nodeParentId = node->parentNode()->id();
 
         const auto otherNode = otherMoveOp->affectedNode();
         LOG_IF_FAIL(otherNode)
-        const auto otherNodeOriginPath = otherNode->moveOriginInfos().path();
+
         NodeId otherNodeOriginParentId;
-        if (!getIdFromDb(otherNode->side(), otherNodeOriginPath.parent_path(), otherNodeOriginParentId)) {
+        if (!getIdFromDb(otherNode->side(), otherNode->moveOriginInfos().path().parent_path(), otherNodeOriginParentId)) {
             continue;
         }
 
-        if (nodeParentId == otherNodeOriginParentId && nodePath.filename() == otherNodeOriginPath.filename()) {
+        const auto nodeParentId = node->parentNode()->id();
+        const auto otherNodeOriginPath = otherNode->moveOriginInfos().normalizedPath();
+        if (nodeParentId == otherNodeOriginParentId && node->normalizedName() == otherNodeOriginPath.filename()) {
             // move only if op is before otherOp
             moveFirstAfterSecond(moveOp, otherMoveOp);
         }
@@ -250,7 +243,7 @@ class SyncOpDepthCmp {
         bool operator()(const std::tuple<SyncOpPtr, SyncOpPtr, int32_t> &a,
                         const std::tuple<SyncOpPtr, SyncOpPtr, int32_t> &b) const {
             if (std::get<2>(a) == std::get<2>(b)) {
-                // If depth are equal, put op to move with lowest ID first
+                // If depths are equal, put op to move with lowest ID first
                 return std::get<0>(a)->id() > std::get<0>(b)->id();
             }
             return std::get<2>(a) < std::get<2>(b);
@@ -369,8 +362,14 @@ std::optional<SyncOperationList> OperationSorterWorker::fixImpossibleFirstMoveOp
     }
 
     // firstOp is an impossible move if dest starts with source + "/".
+    const auto path = node->getPath();
+    SyncPath normalizedPath;
+    if (!Utility::normalizedSyncPath(path, normalizedPath)) {
+        LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(path));
+        normalizedPath = path;
+    }
 
-    if (!Utility::isDescendantOrEqual(node->getPath(), node->moveOriginInfos().path())) {
+    if (!Utility::isDescendantOrEqual(normalizedPath, node->moveOriginInfos().normalizedPath())) {
         return std::nullopt; // firstOp is possible
     }
 
diff --git a/src/libsyncengine/syncpal/operationprocessor.cpp b/src/libsyncengine/syncpal/operationprocessor.cpp
index de8a4e1956..76d4f870d9 100644
--- a/src/libsyncengine/syncpal/operationprocessor.cpp
+++ b/src/libsyncengine/syncpal/operationprocessor.cpp
@@ -23,7 +23,8 @@
 namespace KDC {
 
 OperationProcessor::OperationProcessor(const std::shared_ptr<SyncPal> syncPal, const std::string &name,
-                                       const std::string &shortName) : ISyncWorker(syncPal, name, shortName) {}
+                                       const std::string &shortName) :
+    ISyncWorker(syncPal, name, shortName) {}
 
 bool OperationProcessor::isPseudoConflict(const std::shared_ptr<Node> node, const std::shared_ptr<Node> correspondingNode) {
     if (!node || !node->hasChangeEvent() || !correspondingNode || !correspondingNode->hasChangeEvent()) {
@@ -151,7 +152,7 @@ std::shared_ptr<Node> OperationProcessor::findCorrespondingNodeFromPath(const st
 
     // Construct path with ancestor path / relative path
     SyncPath correspondingPath = correspondingParentNode->getPath() / relativeTraversedPath;
-    std::shared_ptr<Node> correspondingNode = otherTree->getNodeByNormalizedPath(correspondingPath);
+    std::shared_ptr<Node> correspondingNode = otherTree->getNodeByPathNormalized(correspondingPath);
     if (correspondingNode == nullptr) {
         return nullptr;
     }
diff --git a/src/libsyncengine/update_detection/update_detector/node.cpp b/src/libsyncengine/update_detection/update_detector/node.cpp
index 11d850c42d..b185659eba 100644
--- a/src/libsyncengine/update_detection/update_detector/node.cpp
+++ b/src/libsyncengine/update_detection/update_detector/node.cpp
@@ -82,6 +82,7 @@ void Node::setName(const SyncName &name) {
     _name = name;
     _normalizedName.clear();
 }
+
 bool Node::setParentNode(const std::shared_ptr<Node> &parentNode) {
     if (!parentNode) return true;
 
@@ -252,14 +253,45 @@ bool Node::MoveOriginInfos::isValid() const {
     return _isValid;
 }
 
+Node::MoveOriginInfos::MoveOriginInfos(const SyncPath &path, const NodeId &parentNodeId) :
+    _isValid(true),
+    _path(path),
+    _parentNodeId(parentNodeId) {
+    if (!Utility::normalizedSyncPath(_path, _normalizedPath)) {
+        LOGW_WARN(Log::instance()->getLogger(), L"Error in Utility::normalizedSyncPath: " << Utility::formatSyncPath(_path));
+        _normalizedPath = _path;
+    }
+}
+
+Node::MoveOriginInfos &Node::MoveOriginInfos::operator=(const MoveOriginInfos &other) {
+    LOG_IF_FAIL(Log::instance()->getLogger(), other.isValid());
+    _isValid = other.isValid();
+    _path = other.path();
+    _normalizedPath = other.normalizedPath();
+    _parentNodeId = other.parentNodeId();
+    return *this;
+}
+
 const SyncPath &Node::MoveOriginInfos::path() const {
     LOG_IF_FAIL(Log::instance()->getLogger(), isValid());
     return _path;
 }
 
+const SyncPath &Node::MoveOriginInfos::normalizedPath() const {
+    LOG_IF_FAIL(Log::instance()->getLogger(), isValid());
+    return _normalizedPath;
+}
+
 const NodeId &Node::MoveOriginInfos::parentNodeId() const {
     LOG_IF_FAIL(Log::instance()->getLogger(), isValid());
     return _parentNodeId;
 }
 
+void Node::MoveOriginInfos::clear() {
+    _isValid = false;
+    _path = defaultInvalidPath;
+    _normalizedPath = defaultInvalidPath;
+    _parentNodeId = defaultInvalidNodeId;
+}
+
 } // namespace KDC
diff --git a/src/libsyncengine/update_detection/update_detector/node.h b/src/libsyncengine/update_detection/update_detector/node.h
index 601c4ee331..3183e4fd0c 100644
--- a/src/libsyncengine/update_detection/update_detector/node.h
+++ b/src/libsyncengine/update_detection/update_detector/node.h
@@ -29,32 +29,30 @@
 
 namespace KDC {
 
+static const SyncPath defaultInvalidPath = ":\0/:\0";   // Invalid path for increased safety
+static const NodeId defaultInvalidNodeId = "-1";   // Invalid node id for increased safety
+
 class Node {
     public:
         class MoveOriginInfos {
             public:
                 MoveOriginInfos() = default;
                 MoveOriginInfos(const MoveOriginInfos &) = default;
-                MoveOriginInfos(const SyncPath &path, const NodeId &parentNodeId) :
-                    _isValid(true),
-                    _path(path),
-                    _parentNodeId(parentNodeId) {}
-
-                MoveOriginInfos &operator=(const MoveOriginInfos &newMoveOriginInfos) {
-                    LOG_IF_FAIL(Log::instance()->getLogger(), newMoveOriginInfos.isValid());
-                    _isValid = newMoveOriginInfos.isValid();
-                    _path = newMoveOriginInfos.path();
-                    _parentNodeId = newMoveOriginInfos.parentNodeId();
-                    return *this;
-                }
+                MoveOriginInfos(const SyncPath &path, const NodeId &parentNodeId);
+
+                MoveOriginInfos &operator=(const MoveOriginInfos &newMoveOriginInfos);
                 const SyncPath &path() const;
+                const SyncPath &normalizedPath() const;
                 const NodeId &parentNodeId() const;
 
+                void clear();
+
             private:
                 bool isValid() const;
                 bool _isValid = false;
-                SyncPath _path = ":\0/:\0"; // Invalid path for increased safety
-                NodeId _parentNodeId = "-1"; // Invalid node id for increased safety
+                SyncPath _path = defaultInvalidPath;
+                SyncPath _normalizedPath = defaultInvalidPath;
+                NodeId _parentNodeId = defaultInvalidNodeId;
                 friend class Node;
                 friend class TestUpdateTreeWorker;
         };
@@ -120,6 +118,7 @@ class Node {
         inline void setPreviousId(const std::optional<NodeId> &previousNodeId) { _previousId = previousNodeId; }
         bool setParentNode(const std::shared_ptr<Node> &parentNode);
         inline void setMoveOriginInfos(const MoveOriginInfos &moveOriginInfos) { _moveOriginInfos = moveOriginInfos; }
+        inline void clearMoveOriginInfos() { _moveOriginInfos.clear(); }
         inline void setStatus(const NodeStatus &status) { _status = status; }
 
         inline std::unordered_map<NodeId, std::shared_ptr<Node>> &children() { return _childrenById; }
diff --git a/src/libsyncengine/update_detection/update_detector/updatetree.cpp b/src/libsyncengine/update_detection/update_detector/updatetree.cpp
index 41dd30a509..b323abc345 100644
--- a/src/libsyncengine/update_detection/update_detector/updatetree.cpp
+++ b/src/libsyncengine/update_detection/update_detector/updatetree.cpp
@@ -78,7 +78,7 @@ bool UpdateTree::deleteNode(const NodeId &id) {
     return deleteNode(node);
 }
 
-std::shared_ptr<Node> UpdateTree::getNodeByNormalizedPath(const SyncPath &path) {
+std::shared_ptr<Node> UpdateTree::getNodeByPath(const SyncPath &path) {
     if (path.empty()) {
         return _rootNode;
     }
@@ -88,21 +88,9 @@ std::shared_ptr<Node> UpdateTree::getNodeByNormalizedPath(const SyncPath &path)
 
     for (auto nameIt = itemNames.rbegin(); nameIt != itemNames.rend(); ++nameIt) {
         std::shared_ptr<Node> tmpChildNode = nullptr;
-        SyncName normalizedSyncName;
-        if (!Utility::normalizedSyncName(*nameIt, normalizedSyncName)) {
-            LOGW_WARN(Log::instance()->getLogger(),
-                      L"Error in Utility::normalizedSyncName: " << Utility::formatSyncName(*nameIt));
-            return nullptr;
-        }
 
         for (const auto &[_, childNode]: tmpNode->children()) {
-            SyncName normalizedChildSyncName;
-            if (!Utility::normalizedSyncName(childNode->name(), normalizedChildSyncName)) {
-                LOGW_WARN(Log::instance()->getLogger(),
-                          L"Error in Utility::normalizedSyncName: " << Utility::formatSyncName(childNode->name()));
-                return nullptr;
-            }
-            if (normalizedSyncName == normalizedChildSyncName) {
+            if (*nameIt == childNode->name()) {
                 tmpChildNode = childNode;
                 break;
             }
@@ -117,8 +105,7 @@ std::shared_ptr<Node> UpdateTree::getNodeByNormalizedPath(const SyncPath &path)
     return tmpNode;
 }
 
-
-std::shared_ptr<Node> UpdateTree::getNodeByPath(const SyncPath &path) {
+std::shared_ptr<Node> UpdateTree::getNodeByPathNormalized(const SyncPath &path) {
     if (path.empty()) {
         return _rootNode;
     }
@@ -128,9 +115,15 @@ std::shared_ptr<Node> UpdateTree::getNodeByPath(const SyncPath &path) {
 
     for (auto nameIt = itemNames.rbegin(); nameIt != itemNames.rend(); ++nameIt) {
         std::shared_ptr<Node> tmpChildNode = nullptr;
+        SyncName normalizedSyncName;
+        if (!Utility::normalizedSyncName(*nameIt, normalizedSyncName)) {
+            LOGW_WARN(Log::instance()->getLogger(),
+                      L"Error in Utility::normalizedSyncName: " << Utility::formatSyncName(*nameIt));
+            return nullptr;
+        }
 
         for (const auto &[_, childNode]: tmpNode->children()) {
-            if (*nameIt == childNode->name()) {
+            if (normalizedSyncName == childNode->normalizedName()) {
                 tmpChildNode = childNode;
                 break;
             }
@@ -145,7 +138,6 @@ std::shared_ptr<Node> UpdateTree::getNodeByPath(const SyncPath &path) {
     return tmpNode;
 }
 
-
 std::shared_ptr<Node> UpdateTree::getNodeById(const NodeId &nodeId) {
     auto it = _nodes.find(nodeId);
     if (it != _nodes.end()) {
diff --git a/src/libsyncengine/update_detection/update_detector/updatetree.h b/src/libsyncengine/update_detection/update_detector/updatetree.h
index 0a685c8c10..5dc6d3eac5 100644
--- a/src/libsyncengine/update_detection/update_detector/updatetree.h
+++ b/src/libsyncengine/update_detection/update_detector/updatetree.h
@@ -41,7 +41,7 @@ class UpdateTree : public SharedObject {
         inline std::unordered_map<NodeId, std::shared_ptr<Node>> &nodes() { return _nodes; }
         inline std::unordered_map<NodeId, NodeId> &previousIdSet() { return _previousIdSet; }
         std::shared_ptr<Node> getNodeByPath(const SyncPath &path);
-        std::shared_ptr<Node> getNodeByNormalizedPath(const SyncPath &path);
+        std::shared_ptr<Node> getNodeByPathNormalized(const SyncPath &path);
         std::shared_ptr<Node> getNodeById(const NodeId &nodeId);
         bool exists(const NodeId &id);
 
diff --git a/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp b/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp
index 0aee9feb3e..854d82f3b2 100644
--- a/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp
+++ b/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp
@@ -62,12 +62,13 @@ void UpdateTreeWorker::execute() {
     _updateTree->startUpdate();
 
     // Reset nodes working properties
-    for (const auto &[_, nodeItem]: _updateTree->nodes()) {
-        nodeItem->clearChangeEvents();
-        nodeItem->clearConflictAlreadyConsidered();
-        nodeItem->setInconsistencyType(InconsistencyType::None);
-        nodeItem->setPreviousId(std::nullopt);
-        nodeItem->setStatus(NodeStatus::Unprocessed);
+    for (const auto &[_, node]: _updateTree->nodes()) {
+        node->clearChangeEvents();
+        node->clearConflictAlreadyConsidered();
+        node->setInconsistencyType(InconsistencyType::None);
+        node->setPreviousId(std::nullopt);
+        node->setStatus(NodeStatus::Unprocessed);
+        node->clearMoveOriginInfos();
     }
 
     _updateTree->previousIdSet().clear();
diff --git a/test/libsyncengine/propagation/operation_sorter/testoperationsorterworker.cpp b/test/libsyncengine/propagation/operation_sorter/testoperationsorterworker.cpp
index 2da9d17c63..d8bf473865 100644
--- a/test/libsyncengine/propagation/operation_sorter/testoperationsorterworker.cpp
+++ b/test/libsyncengine/propagation/operation_sorter/testoperationsorterworker.cpp
@@ -103,6 +103,11 @@ void TestOperationSorterWorker::testFixDeleteBeforeMove() {
         const auto nodeB = _testSituationGenerator.renameNode(ReplicaSide::Local, "b", Str("A"));
         const auto moveOp = generateSyncOperation(OperationType::Move, nodeB);
 
+        // Edit A (this operation is added to make sure it does not influence or break the test)
+        (void) _testSituationGenerator.editNode(ReplicaSide::Local, nodeB->id().value());
+        const auto editOp = generateSyncOperation(OperationType::Edit, nodeB);
+
+        (void) _syncPal->syncOps()->pushOp(editOp);
         (void) _syncPal->syncOps()->pushOp(moveOp);
         (void) _syncPal->syncOps()->pushOp(deleteOp);
 
@@ -846,12 +851,12 @@ SyncOpPtr TestOperationSorterWorker::generateSyncOperation(const OperationType o
 void TestOperationSorterWorker::generateLotsOfDummySyncOperations(const OperationType opType1,
                                                                   const OperationType opType2 /*= OperationType::None*/,
                                                                   const NodeType nodeType /*= NodeType::File*/) const {
-    const auto dummyNode = _testSituationGenerator.createNode(ReplicaSide::Local, nodeType, "dummy", "", false);
-    dummyNode->setMoveOriginInfos({"/dumm", "1"});
+    const auto dummyNode = _testSituationGenerator.createNode(ReplicaSide::Local, nodeType, "z", "", false);
+    dummyNode->setMoveOriginInfos({"/Y", "1"});
     for (const auto type: {opType1, opType2}) {
         if (type != OperationType::None) {
             // Generate dummy operations
-            for (uint32_t i = 0; i < 10000; i++) {
+            for (uint32_t i = 0; i < 100; i++) {
                 (void) _syncPal->syncOps()->pushOp(generateSyncOperation(type, dummyNode));
             }
         }
diff --git a/test/libsyncengine/update_detection/update_detector/testupdatetreeworker.cpp b/test/libsyncengine/update_detection/update_detector/testupdatetreeworker.cpp
index 0c8b308373..668dc8f22e 100644
--- a/test/libsyncengine/update_detection/update_detector/testupdatetreeworker.cpp
+++ b/test/libsyncengine/update_detection/update_detector/testupdatetreeworker.cpp
@@ -333,11 +333,11 @@ void TestUpdateTreeWorker::testUtilsFunctions() {
     CPPUNIT_ASSERT(newPath == "Dir 4/Dir 4.2/Dir 4.1.1/File 4.1.1.1");
     CPPUNIT_ASSERT(_localUpdateTree->getNodeByPath(newPath)->id() == "id4111");
 
-    CPPUNIT_ASSERT(_localUpdateTree->getNodeByNormalizedPath(newPath) == _localUpdateTree->getNodeByPath(newPath));
+    CPPUNIT_ASSERT(_localUpdateTree->getNodeByPathNormalized(newPath) == _localUpdateTree->getNodeByPath(newPath));
     _localUpdateTree->getNodeByPath("Dir 4/Dir 4.2")->setName(testhelpers::makeNfdSyncName());
 
     CPPUNIT_ASSERT(nullptr == _localUpdateTree->getNodeByPath(SyncPath("Dir 4") / testhelpers::makeNfcSyncName()));
-    CPPUNIT_ASSERT(nullptr != _localUpdateTree->getNodeByNormalizedPath(SyncPath("Dir 4") / testhelpers::makeNfcSyncName()));
+    CPPUNIT_ASSERT(nullptr != _localUpdateTree->getNodeByPathNormalized(SyncPath("Dir 4") / testhelpers::makeNfcSyncName()));
 }
 
 void TestUpdateTreeWorker::testUpdateTmpFileNode() {


Reviews:
user: login: herve-er
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T16:13:12Z
user: login: herve-er
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T07:50:19Z
user: login: ClementKunz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:26:59Z
user: login: ClementKunz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:27:09Z
user: login: ClementKunz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:27:12Z
user: login: herve-er
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:40:41Z

Review comments:
diff_hunk: @@ -62,12 +62,13 @@ void UpdateTreeWorker::execute() {
     _updateTree->startUpdate();
 
     // Reset nodes working properties
-    for (const auto &[_, nodeItem]: _updateTree->nodes()) {
-        nodeItem->clearChangeEvents();
-        nodeItem->clearConflictAlreadyConsidered();
-        nodeItem->setInconsistencyType(InconsistencyType::None);
-        nodeItem->setPreviousId(std::nullopt);
-        nodeItem->setStatus(NodeStatus::Unprocessed);
+    for (const auto &[_, node]: _updateTree->nodes()) {
+        node->clearChangeEvents();
+        node->clearConflictAlreadyConsidered();
+        node->setInconsistencyType(InconsistencyType::None);
+        node->setPreviousId(std::nullopt);
+        node->setStatus(NodeStatus::Unprocessed);
+        node->clearMoveOriginInfos();
path: src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp
user: login: herve-er
body: ❤️ 
created_at: 2025-05-07T16:13:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,34 +83,39 @@ void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &deletedDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveOriginPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto deletedPath = op->affectedNode()->getPath();
-        (void) deletedDirectoryPaths.try_emplace(deletedPath, op);
+
+        SyncPath normalizedDeletedPath;
+        if (const auto deletedPath = op->affectedNode()->getPath(); !Utility::normalizedSyncPath(deletedPath, normalizedDeletedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(deletedPath));
+            normalizedDeletedPath = deletedPath;
+        }
+        (void) deletedDirectoryPaths.try_emplace(normalizedDeletedPath, op);
 
         // Check if any of the moved item origin path is inside the deleted folder.
         for (const auto &[path, moveOp]: moveOriginPaths) {
path: src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
user: login: herve-er
body: Just for it to be clearer, could we rename `path `to `normalizedMovePath`
created_at: 2025-05-08T07:30:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,34 +83,39 @@ void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &deletedDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveOriginPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto deletedPath = op->affectedNode()->getPath();
-        (void) deletedDirectoryPaths.try_emplace(deletedPath, op);
+
+        SyncPath normalizedDeletedPath;
+        if (const auto deletedPath = op->affectedNode()->getPath(); !Utility::normalizedSyncPath(deletedPath, normalizedDeletedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(deletedPath));
+            normalizedDeletedPath = deletedPath;
+        }
+        (void) deletedDirectoryPaths.try_emplace(normalizedDeletedPath, op);
 
         // Check if any of the moved item origin path is inside the deleted folder.
         for (const auto &[path, moveOp]: moveOriginPaths) {
             if (op->targetSide() != moveOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(path, deletedPath)) {
+            if (Utility::isDescendantOrEqual(path, normalizedDeletedPath)) {
                 (void) _fixMoveBeforeDeleteCandidates.emplace_back(op, moveOp);
             }
         }
         return;
     }
 
-    if (op->affectedNode()->hasChangeEvent(OperationType::Move)) {
-        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().path(), op);
+    if (op->type() == OperationType::Move) {
+        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().normalizedPath(), op);
 
         // Check if any of the deleted path contains the origin path.
         for (const auto &[path, deleteOp]: deletedDirectoryPaths) {
path: src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
user: login: herve-er
body: Just for it to be clearer, could we rename `path `to `normalizedDeletePath`

Idem for the other one.
created_at: 2025-05-08T07:31:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,14 +253,46 @@ bool Node::MoveOriginInfos::isValid() const {
     return _isValid;
 }
 
+Node::MoveOriginInfos::MoveOriginInfos(const SyncPath &path, const NodeId &parentNodeId) :
+    _isValid(true),
+    _path(path),
+    _parentNodeId(parentNodeId) {
+    if (!Utility::normalizedSyncPath(_path, _normalizedPath)) {
+        LOGW_WARN(Log::instance()->getLogger(), L"Error in Utility::normalizedSyncPath: " << Utility::formatSyncPath(_path));
+        _normalizedPath = _path;
+    }
+}
+
+Node::MoveOriginInfos &Node::MoveOriginInfos::operator=(const MoveOriginInfos &newMoveOriginInfos) {
+    LOG_IF_FAIL(Log::instance()->getLogger(), newMoveOriginInfos.isValid());
+    _isValid = newMoveOriginInfos.isValid();
+    _path = newMoveOriginInfos.path();
+    _normalizedPath = newMoveOriginInfos.normalizedPath();
+    _parentNodeId = newMoveOriginInfos.parentNodeId();
+    return *this;
+}
path: src/libsyncengine/update_detection/update_detector/node.cpp
user: login: herve-er
body: We usualy name the variable `other` in operators
```suggestion
Node::MoveOriginInfos &Node::MoveOriginInfos::operator=(const MoveOriginInfos &other) {
    LOG_IF_FAIL(Log::instance()->getLogger(), newMoveOriginInfos.isValid());
    _isValid = other.isValid();
    _path = other.path();
    _normalizedPath = other.normalizedPath();
    _parentNodeId = other.parentNodeId();
    return *this;
}
```
created_at: 2025-05-08T07:42:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,14 +253,46 @@ bool Node::MoveOriginInfos::isValid() const {
     return _isValid;
 }
 
+Node::MoveOriginInfos::MoveOriginInfos(const SyncPath &path, const NodeId &parentNodeId) :
+    _isValid(true),
+    _path(path),
+    _parentNodeId(parentNodeId) {
+    if (!Utility::normalizedSyncPath(_path, _normalizedPath)) {
+        LOGW_WARN(Log::instance()->getLogger(), L"Error in Utility::normalizedSyncPath: " << Utility::formatSyncPath(_path));
+        _normalizedPath = _path;
+    }
+}
+
+Node::MoveOriginInfos &Node::MoveOriginInfos::operator=(const MoveOriginInfos &newMoveOriginInfos) {
+    LOG_IF_FAIL(Log::instance()->getLogger(), newMoveOriginInfos.isValid());
+    _isValid = newMoveOriginInfos.isValid();
+    _path = newMoveOriginInfos.path();
+    _normalizedPath = newMoveOriginInfos.normalizedPath();
+    _parentNodeId = newMoveOriginInfos.parentNodeId();
+    return *this;
+}
path: src/libsyncengine/update_detection/update_detector/node.cpp
user: login: ClementKunz
body: Done in 4e3e2d4f84412e270f575a6f8dff72695e0e473f
created_at: 2025-05-09T06:26:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,34 +83,39 @@ void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &deletedDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveOriginPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto deletedPath = op->affectedNode()->getPath();
-        (void) deletedDirectoryPaths.try_emplace(deletedPath, op);
+
+        SyncPath normalizedDeletedPath;
+        if (const auto deletedPath = op->affectedNode()->getPath(); !Utility::normalizedSyncPath(deletedPath, normalizedDeletedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(deletedPath));
+            normalizedDeletedPath = deletedPath;
+        }
+        (void) deletedDirectoryPaths.try_emplace(normalizedDeletedPath, op);
 
         // Check if any of the moved item origin path is inside the deleted folder.
         for (const auto &[path, moveOp]: moveOriginPaths) {
             if (op->targetSide() != moveOp->targetSide()) {
                 continue;
             }
-            if (Utility::isDescendantOrEqual(path, deletedPath)) {
+            if (Utility::isDescendantOrEqual(path, normalizedDeletedPath)) {
                 (void) _fixMoveBeforeDeleteCandidates.emplace_back(op, moveOp);
             }
         }
         return;
     }
 
-    if (op->affectedNode()->hasChangeEvent(OperationType::Move)) {
-        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().path(), op);
+    if (op->type() == OperationType::Move) {
+        (void) moveOriginPaths.try_emplace(op->affectedNode()->moveOriginInfos().normalizedPath(), op);
 
         // Check if any of the deleted path contains the origin path.
         for (const auto &[path, deleteOp]: deletedDirectoryPaths) {
path: src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
user: login: ClementKunz
body: Done in https://github.com/Infomaniak/desktop-kDrive/commit/4e3e2d4f84412e270f575a6f8dff72695e0e473f
created_at: 2025-05-09T06:27:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,34 +83,39 @@ void OperationSorterFilter::filterMoveBeforeCreateCandidates(const SyncOpPtr &op
 
 void OperationSorterFilter::filterMoveBeforeDeleteCandidates(const SyncOpPtr &op, SyncPathToSyncOpMap &deletedDirectoryPaths,
                                                              SyncPathToSyncOpMap &moveOriginPaths) {
-    if (op->affectedNode()->hasChangeEvent(OperationType::Delete)) {
+    if (op->type() == OperationType::Delete) {
         if (op->affectedNode()->type() != NodeType::Directory) {
             return;
         }
-        const auto deletedPath = op->affectedNode()->getPath();
-        (void) deletedDirectoryPaths.try_emplace(deletedPath, op);
+
+        SyncPath normalizedDeletedPath;
+        if (const auto deletedPath = op->affectedNode()->getPath(); !Utility::normalizedSyncPath(deletedPath, normalizedDeletedPath)) {
+            LOGW_WARN(Log::instance()->getLogger(), L"Failed to normalize: " << Utility::formatSyncPath(deletedPath));
+            normalizedDeletedPath = deletedPath;
+        }
+        (void) deletedDirectoryPaths.try_emplace(normalizedDeletedPath, op);
 
         // Check if any of the moved item origin path is inside the deleted folder.
         for (const auto &[path, moveOp]: moveOriginPaths) {
path: src/libsyncengine/propagation/operation_sorter/operationsorterfilter.cpp
user: login: ClementKunz
body: Done in https://github.com/Infomaniak/desktop-kDrive/commit/4e3e2d4f84412e270f575a6f8dff72695e0e473f
created_at: 2025-05-09T06:27:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-09T13:53:43Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Infomaniak_desktop-kDrive&pullRequest=738) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [21 New issues](https://sonarcloud.io/project/issues?id=Infomaniak_desktop-kDrive&pullRequest=738&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [2 Accepted issues](https://sonarcloud.io/project/issues?id=Infomaniak_desktop-kDrive&pullRequest=738&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Infomaniak_desktop-kDrive&pullRequest=738&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Infomaniak_desktop-kDrive&pullRequest=738&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Infomaniak_desktop-kDrive&pullRequest=738&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Infomaniak_desktop-kDrive&pullRequest=738)


author_association: NONE