Skip to content

Commit 4d553d5

Browse files
committed
Sync: Fix deletions mixed with moves
1 parent f95f028 commit 4d553d5

File tree

3 files changed

+54
-47
lines changed

3 files changed

+54
-47
lines changed

src/lib/Diff.ts

+16
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ export default class Diff {
125125
)
126126
}
127127

128+
static findChain(mappingsSnapshot: MappingSnapshot, actions: Action[], currentItem: TItem, targetAction: Action): boolean {
129+
if (
130+
targetAction.payload.findItem(ItemType.FOLDER,
131+
Mappings.mapParentId(mappingsSnapshot, currentItem, targetAction.payload.location))
132+
) {
133+
return true
134+
}
135+
const newCurrentAction = actions.find(targetAction =>
136+
targetAction.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, currentItem, targetAction.payload.location))
137+
)
138+
if (newCurrentAction) {
139+
return Diff.findChain(mappingsSnapshot, actions, newCurrentAction.payload, targetAction)
140+
}
141+
return false
142+
}
143+
128144
static sortMoves(actions: Action[], tree: Folder) :Action[][] {
129145
const bookmarks = actions.filter(a => a.payload.type === ItemType.BOOKMARK)
130146
const folderMoves = actions.filter(a => a.payload.type === ItemType.FOLDER)

src/lib/strategies/Default.ts

+37-46
Original file line numberDiff line numberDiff line change
@@ -208,41 +208,32 @@ export default class SyncProcess {
208208
const sourceCreations = sourceDiff.getActions(ActionType.CREATE).map(a => a as CreateAction)
209209
const sourceRemovals = sourceDiff.getActions(ActionType.REMOVE).map(a => a as RemoveAction)
210210

211+
const allCreateAndMoveActions = targetDiff.getActions()
212+
.filter(a => a.type === ActionType.CREATE || a.type === ActionType.MOVE)
213+
.map(a => a as CreateAction|MoveAction)
214+
.concat(
215+
sourceDiff.getActions()
216+
.filter(a => a.type === ActionType.CREATE || a.type === ActionType.MOVE)
217+
.map(a => a as CreateAction|MoveAction)
218+
)
219+
211220
const avoidTargetReorders = {}
212221

213222
// Prepare target plan
214223
const targetPlan = new Diff() // to be mapped
215224
await Parallel.each(sourceDiff.getActions(), async(action:Action) => {
216225
if (action.type === ActionType.REMOVE) {
217-
const concurrentRemoval = targetRemovals.find(a =>
218-
(action.payload.type === a.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, a.payload)) ||
219-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, a.payload.location)))
226+
const concurrentRemoval = targetRemovals.find(targetRemoval =>
227+
(action.payload.type === targetRemoval.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, targetRemoval.payload)) ||
228+
Diff.findChain(mappingsSnapshot, allCreateAndMoveActions, action.payload, targetRemoval))
220229
if (concurrentRemoval) {
221230
// Already deleted on target, do nothing.
222231
return
223232
}
224233

225-
const complexConcurrentRemoval = sourceRemovals.find(sourceRemoval => {
226-
let currentAction : RemoveAction|CreateAction|MoveAction =
227-
targetDiff.getActions()
228-
.filter(a => a.type === ActionType.CREATE || a.type === ActionType.MOVE)
229-
.map(a => a as CreateAction|MoveAction)
230-
.find(targetAction => targetAction.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, targetAction.payload.location)))
231-
while (currentAction && !sourceRemoval.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, currentAction.payload, sourceRemoval.payload.location))) {
232-
currentAction = targetDiff.getActions()
233-
.filter(a => a.type === ActionType.CREATE || a.type === ActionType.MOVE)
234-
.map(a => a as CreateAction|MoveAction)
235-
.find(targetAction => targetAction.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, currentAction.payload, targetAction.payload.location)))
236-
}
237-
return Boolean(currentAction)
238-
})
239-
if (complexConcurrentRemoval) {
240-
// already deleted by a different REMOVE from this diff (connected via source MOVE|CREATEs)
241-
return
242-
}
243-
244-
const concurrentMove = targetMoves.find(a =>
245-
action.payload.type === a.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, a.payload))
234+
const concurrentMove = targetMoves.find(targetMove =>
235+
action.payload.type === targetMove.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, targetMove.payload)
236+
)
246237
if (concurrentMove) {
247238
// moved on the target, moves take precedence, do nothing (i.e. leave target version intact)
248239
return
@@ -278,15 +269,9 @@ export default class SyncProcess {
278269
// TODO: subScanner may contain residual CREATE/REMOVE actions that need to be added to mappings
279270
return
280271
}
281-
const concurrentRemoval = targetRemovals.find(a =>
282-
// target removal removed this creation's target
283-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, a.payload.location)) ||
284-
// or: target removal removed a move's target that was the target of this creation
285-
sourceDiff.getActions().filter(a => a.type === ActionType.MOVE || a.type === ActionType.CREATE)
286-
.find(a2 =>
287-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, a2.payload, a.payload.location)) &&
288-
a2.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, a2.payload.location))
289-
)
272+
const concurrentRemoval = targetRemovals.find(targetRemoval =>
273+
// target removal removed this creation's target (via some chain)
274+
Diff.findChain(mappingsSnapshot, allCreateAndMoveActions, action.payload, targetRemoval)
290275
)
291276
if (concurrentRemoval) {
292277
avoidTargetReorders[action.payload.parentId] = true
@@ -295,22 +280,27 @@ export default class SyncProcess {
295280
}
296281
}
297282
if (action.type === ActionType.MOVE) {
298-
const concurrentParentRemoval = targetRemovals.find(a =>
299-
// target-side removal of this move's target
300-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, a.payload.location)) ||
301-
// or: target-side removal of a source creation's target which was the target of this move
302-
sourceDiff.getActions().filter(a => a.type === ActionType.MOVE || a.type === ActionType.CREATE)
303-
.find(a2 =>
304-
a2.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.payload, a2.payload.location)) &&
305-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, a2.payload, a.payload.location)))
283+
// FInd out if there's a removal in the target diff which already deletes this item (via some chain of MOVE|CREATEs)
284+
const complexTargetTargetRemoval = targetRemovals.find(targetRemoval => {
285+
return Diff.findChain(mappingsSnapshot, allCreateAndMoveActions, action.payload, targetRemoval)
286+
})
287+
const concurrentTargetOriginRemoval = targetRemovals.find(targetRemoval =>
288+
(action.payload.type === targetRemoval.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, targetRemoval.payload)) ||
289+
Diff.findChain(mappingsSnapshot, allCreateAndMoveActions, action.oldItem, targetRemoval)
306290
)
307-
if (concurrentParentRemoval) {
291+
const concurrentSourceOriginRemoval = sourceRemovals.find(sourceRemoval => {
292+
return Diff.findChain(mappingsSnapshot, allCreateAndMoveActions, action.oldItem, sourceRemoval)
293+
})
294+
if (complexTargetTargetRemoval) {
295+
// target already deleted by a target REMOVE (connected via source MOVE|CREATEs)
296+
if (!concurrentTargetOriginRemoval && !concurrentSourceOriginRemoval) {
297+
// make sure this item is not already being removed, when it's no longer moved
298+
targetPlan.commit({ ...action, type: ActionType.REMOVE, payload: action.oldItem, oldItem: null })
299+
}
308300
return
309301
}
310-
const concurrentRemoval = targetRemovals.find(a =>
311-
(action.payload.type === a.payload.type && Mappings.mappable(mappingsSnapshot, action.payload, a.payload)) ||
312-
a.payload.findItem(ItemType.FOLDER, Mappings.mapParentId(mappingsSnapshot, action.oldItem, a.payload.location)))
313-
if (concurrentRemoval && !sourceRemovals.find(a => a.payload.findItem(action.payload.type, Mappings.mapId(mappingsSnapshot, action.payload, a.payload.location)))) {
302+
if (concurrentTargetOriginRemoval) {
303+
// if (!concurrentSourceOriginRemoval) {
314304
// moved sourcely but removed on the target, recreate it on the target
315305
const originalCreation = sourceCreations.find(creation => creation.payload.findItem(ItemType.FOLDER, action.payload.parentId))
316306
if (originalCreation && originalCreation.payload.type === ItemType.FOLDER) {
@@ -320,6 +310,7 @@ export default class SyncProcess {
320310
} else {
321311
targetPlan.commit({ ...action, type: ActionType.CREATE, oldItem: null })
322312
}
313+
// }
323314
return
324315
}
325316

src/test/test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3760,7 +3760,7 @@ describe('Floccus', function() {
37603760

37613761
await account2.sync()
37623762
expect(account2.getData().error).to.not.be.ok
3763-
console.log('second round: account1 completed')
3763+
console.log('second round: account2 completed')
37643764

37653765
const serverTreeAfterSecondSync = await getAllBookmarks(account1)
37663766

0 commit comments

Comments
 (0)