1- import { Folder , ItemLocation , TItem , TItemLocation , TOppositeLocation } from '../Tree'
2- import Diff , { CreateAction , MoveAction , PlanStage1 , PlanStage3 , ReorderAction } from '../Diff'
1+ import { Folder , ItemLocation , ItemType , TItem , TItemLocation , TOppositeLocation } from '../Tree'
2+ import Diff , { CreateAction , MoveAction , PlanStage1 } from '../Diff'
33import Scanner , { ScanResult } from '../Scanner'
44import * as Parallel from 'async-parallel'
55import DefaultSyncProcess , { ISerializedSyncProcess } from './Default'
6- import Mappings , { MappingSnapshot } from '../Mappings'
6+ import Mappings from '../Mappings'
77import Logger from '../Logger'
88
99const ACTION_CONCURRENCY = 12
@@ -49,27 +49,34 @@ export default class MergeSyncProcess extends DefaultSyncProcess {
4949 return { localScanResult, serverScanResult}
5050 }
5151
52+ // This is a copy of DefaultSyncProcess#reconcileDiffs without anything involving REMOVEs
5253 async reconcileDiffs < L1 extends TItemLocation , L2 extends TItemLocation , L3 extends TItemLocation > (
5354 sourceScanResult :ScanResult < L1 , L2 > ,
5455 targetScanResult :ScanResult < TOppositeLocation < L1 > , L3 > ,
5556 targetLocation : TOppositeLocation < L1 >
5657 ) : Promise < PlanStage1 < L1 , L2 > > {
58+ Logger . log ( 'Reconciling diffs to prepare a plan for ' + targetLocation )
5759 const mappingsSnapshot = this . mappings . getSnapshot ( )
5860
5961 const targetCreations = targetScanResult . CREATE . getActions ( )
62+ const targetRemovals = targetScanResult . REMOVE . getActions ( )
6063 const targetMoves = targetScanResult . MOVE . getActions ( )
64+ const targetUpdates = targetScanResult . UPDATE . getActions ( )
65+ const targetReorders = targetScanResult . REORDER . getActions ( )
6166
67+ const sourceRemovals = sourceScanResult . REMOVE . getActions ( )
6268 const sourceMoves = sourceScanResult . MOVE . getActions ( )
63- const sourceUpdates = sourceScanResult . UPDATE . getActions ( )
6469
65- const targetTree = this . getTargetTree ( targetLocation )
66- const sourceTree = this . getTargetTree ( targetLocation === ItemLocation . LOCAL ? ItemLocation . SERVER : ItemLocation . LOCAL ) as Folder < L1 >
70+ const targetTree : Folder < TOppositeLocation < L1 > > = ( targetLocation === ItemLocation . LOCAL ? this . localTreeRoot : this . serverTreeRoot ) as Folder < TOppositeLocation < L1 > >
71+ const sourceTree : Folder < L1 > = ( targetLocation === ItemLocation . LOCAL ? this . serverTreeRoot : this . localTreeRoot ) as Folder < L1 >
6772
6873 const allCreateAndMoveActions = ( sourceScanResult . CREATE . getActions ( ) as Array < CreateAction < L1 , L2 > | MoveAction < L1 , L2 > | CreateAction < TOppositeLocation < L1 > , L3 > | MoveAction < TOppositeLocation < L1 > , L3 > > )
6974 . concat ( sourceScanResult . MOVE . getActions ( ) )
7075 . concat ( targetScanResult . CREATE . getActions ( ) )
7176 . concat ( targetScanResult . MOVE . getActions ( ) )
7277
78+ const avoidTargetReorders = { }
79+
7380 // Prepare target plan
7481 const targetPlan : PlanStage1 < L1 , L2 > = {
7582 CREATE : new Diff ( ) ,
@@ -79,16 +86,18 @@ export default class MergeSyncProcess extends DefaultSyncProcess {
7986 REORDER : new Diff ( ) ,
8087 }
8188
89+ const findChainCacheForCreations = { }
8290 await Parallel . each ( sourceScanResult . CREATE . getActions ( ) , async ( action ) => {
83- const concurrentCreation = targetCreations . find ( a =>
84- a . payload . parentId === Mappings . mapParentId ( mappingsSnapshot , action . payload , a . payload . location ) &&
85- action . payload . canMergeWith ( a . payload ) )
91+ const concurrentCreation = targetCreations . find ( a => (
92+ action . payload . parentId === Mappings . mapParentId ( mappingsSnapshot , a . payload , action . payload . location ) &&
93+ action . payload . canMergeWith ( a . payload )
94+ ) )
8695 if ( concurrentCreation ) {
87- // created on both the server and locally , try to reconcile
96+ // created on both the target and sourcely , try to reconcile
8897 const newMappings = [ ]
8998 const subScanner = new Scanner (
90- concurrentCreation . payload , // server tree
91- action . payload , // local tree
99+ concurrentCreation . payload , // target tree
100+ action . payload , // source tree
92101 ( oldItem , newItem ) => {
93102 if ( oldItem . type === newItem . type && oldItem . canMergeWith ( newItem ) ) {
94103 // if two items can be merged, we'll add mappings here directly
@@ -98,7 +107,6 @@ export default class MergeSyncProcess extends DefaultSyncProcess {
98107 return false
99108 } ,
100109 this . hashSettings ,
101- false ,
102110 false
103111 )
104112 await subScanner . run ( )
@@ -110,76 +118,118 @@ export default class MergeSyncProcess extends DefaultSyncProcess {
110118 return
111119 }
112120
121+ const concurrentRemoval = targetScanResult . REMOVE . getActions ( ) . find ( targetRemoval =>
122+ // target removal removed this creation's target (via some chain)
123+ Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , targetRemoval , findChainCacheForCreations )
124+ )
125+ if ( concurrentRemoval ) {
126+ avoidTargetReorders [ action . payload . parentId ] = true
127+ // Already deleted on target, do nothing.
128+ return
129+ }
130+
113131 targetPlan . CREATE . commit ( action )
114132 } , ACTION_CONCURRENCY )
115133
116134 await Parallel . each ( sourceScanResult . MOVE . getActions ( ) , async ( action ) => {
117- if ( targetLocation === ItemLocation . LOCAL ) {
118- const concurrentMove = sourceMoves . find ( a =>
119- ( action . payload . type === a . payload . type && Mappings . mappable ( mappingsSnapshot , action . payload , a . payload ) ) ||
120- ( action . payload . type === 'bookmark' && action . payload . canMergeWith ( a . payload ) )
121- )
135+ if ( targetLocation === this . masterLocation ) {
136+ const concurrentMove = targetMoves . find ( a =>
137+ action . payload . type === a . payload . type && Mappings . mappable ( mappingsSnapshot , action . payload , a . payload ) )
122138 if ( concurrentMove ) {
123- // Moved both on server and locally, local has precedence: do nothing locally
139+ // Moved both on target and sourcely, master has precedence: do nothing on master
124140 return
125141 }
126142 }
143+
144+ let findChainCache1 = { } , findChainCache2 = { }
127145 // Find concurrent moves that form a hierarchy reversal together with this one
128146 const concurrentHierarchyReversals = targetMoves . filter ( targetMove => {
129- return Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , targetMove ) &&
130- Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , targetTree , targetMove . payload , action )
147+ return Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , targetMove , findChainCache1 ) &&
148+ Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , targetTree , targetMove . payload , action , findChainCache2 )
131149 } )
132150 if ( concurrentHierarchyReversals . length ) {
133- if ( targetLocation === ItemLocation . SERVER ) {
151+ if ( targetLocation !== this . masterLocation ) {
152+ targetPlan . MOVE . commit ( action )
153+
154+ findChainCache1 = { }
155+ findChainCache2 = { }
134156 concurrentHierarchyReversals . forEach ( a => {
135- // moved locally but moved in reverse hierarchical order on server
136- const payload = a . oldItem . copyWithLocation ( false , action . payload . location ) // we don't map here as we want this to look like a local action
157+ // moved sourcely but moved in reverse hierarchical order on target
158+ const payload = a . oldItem . copyWithLocation ( false , action . payload . location )
137159 const oldItem = a . payload . copyWithLocation ( false , action . oldItem . location )
138- oldItem . id = Mappings . mapId ( mappingsSnapshot , oldItem , action . payload . location )
139- oldItem . parentId = Mappings . mapParentId ( mappingsSnapshot , oldItem , action . payload . location )
160+ oldItem . id = Mappings . mapId ( mappingsSnapshot , a . payload , action . oldItem . location )
161+ oldItem . parentId = Mappings . mapParentId ( mappingsSnapshot , a . payload , action . oldItem . location )
140162
141163 if (
164+ // Don't create duplicates!
142165 targetPlan . MOVE . getActions ( ) . find ( move => String ( move . payload . id ) === String ( payload . id ) ) ||
143- sourceMoves . find ( move => String ( move . payload . id ) === String ( payload . id ) )
166+ sourceMoves . find ( move => String ( move . payload . id ) === String ( payload . id ) ) ||
167+ // Don't move back into removed territory
168+ targetRemovals . find ( remove => Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , remove , findChainCache1 ) ) ||
169+ sourceRemovals . find ( remove => Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , targetTree , action . payload , remove , findChainCache2 ) )
144170 ) {
145- // Don't create duplicates!
146171 return
147172 }
148173
149- // revert server move
174+ // revert target move
150175 targetPlan . MOVE . commit ( { ...a , payload, oldItem } )
176+ DefaultSyncProcess . removeItemFromReorders ( mappingsSnapshot , sourceScanResult . REORDER , payload )
177+ DefaultSyncProcess . removeItemFromReorders ( mappingsSnapshot , sourceScanResult . REORDER , oldItem )
151178 } )
152- targetPlan . MOVE . commit ( action )
179+ } else {
180+ DefaultSyncProcess . removeItemFromReorders ( mappingsSnapshot , sourceScanResult . REORDER , action . oldItem )
181+ DefaultSyncProcess . removeItemFromReorders ( mappingsSnapshot , sourceScanResult . REORDER , action . payload )
153182 }
154-
155- // if target === LOCAL: Moved locally and in reverse hierarchical order on server. local has precedence: do nothing locally
156183 return
157184 }
158185
159186 targetPlan . MOVE . commit ( action )
160- } , ACTION_CONCURRENCY )
187+ } , 1 )
161188
162189 await Parallel . each ( sourceScanResult . UPDATE . getActions ( ) , async ( action ) => {
163- const concurrentUpdate = sourceUpdates . find ( a =>
190+ const concurrentUpdate = targetUpdates . find ( a =>
164191 action . payload . type === a . payload . type && Mappings . mappable ( mappingsSnapshot , action . payload , a . payload ) )
165- if ( concurrentUpdate && targetLocation === ItemLocation . LOCAL ) {
166- // Updated both on server and locally, local has precedence: do nothing locally
192+ if ( concurrentUpdate && targetLocation === this . masterLocation ) {
193+ // Updated both on target and sourcely, source has precedence: do nothing sourcely
194+ return
195+ }
196+ const concurrentRemoval = targetRemovals . find ( a =>
197+ a . payload . findItem ( action . payload . type , Mappings . mapId ( mappingsSnapshot , action . payload , a . payload . location ) ) ||
198+ a . payload . findItem ( ItemType . FOLDER , Mappings . mapParentId ( mappingsSnapshot , action . payload , a . payload . location ) ) )
199+ if ( concurrentRemoval ) {
200+ // Already deleted on target, do nothing.
167201 return
168202 }
169203
170204 targetPlan . UPDATE . commit ( action )
171- } , ACTION_CONCURRENCY )
205+ } )
172206
173- return targetPlan
174- }
207+ await Parallel . each ( sourceScanResult . REORDER . getActions ( ) , async ( action ) => {
208+ if ( avoidTargetReorders [ action . payload . id ] ) {
209+ return
210+ }
211+
212+ if ( targetLocation !== this . masterLocation ) {
213+ const concurrentReorder = targetReorders . find ( a =>
214+ action . payload . type === a . payload . type && Mappings . mappable ( mappingsSnapshot , action . payload , a . payload ) )
215+ if ( concurrentReorder ) {
216+ return
217+ }
218+ }
175219
176- reconcileReorderings < L1 extends TItemLocation , L2 extends TItemLocation > (
177- targetReorders : Diff < L2 , TItemLocation , ReorderAction < L2 , TItemLocation > > ,
178- targetOrSourceDonePlan : PlanStage3 < TItemLocation , TItemLocation , TItemLocation > ,
179- targetLocation : L1 ,
180- mappingSnapshot : MappingSnapshot
181- ) : Diff < L2 , TItemLocation , ReorderAction < L2 , TItemLocation > > {
182- return super . reconcileReorderings ( targetReorders , targetOrSourceDonePlan , targetLocation , mappingSnapshot )
220+ const findChainCache = { }
221+ const concurrentRemoval = targetRemovals . find ( targetRemoval =>
222+ Diff . findChain ( mappingsSnapshot , allCreateAndMoveActions , sourceTree , action . payload , targetRemoval , findChainCache )
223+ )
224+ if ( concurrentRemoval ) {
225+ // Already deleted on target, do nothing.
226+ return
227+ }
228+
229+ targetPlan . REORDER . commit ( action )
230+ } )
231+
232+ return targetPlan
183233 }
184234
185235 async loadChildren ( serverTreeRoot : Folder < typeof ItemLocation . SERVER > ) :Promise < void > {
0 commit comments