Skip to content

Commit 72ca9b4

Browse files
authored
Merge pull request #2253 from floccusaddon/fix/2230/crank-up-children-similarity
refactor(Scanner): Move mapping creation from mergeable to diffItem
2 parents 55bb6ff + 0974df8 commit 72ca9b4

7 files changed

Lines changed: 400 additions & 73 deletions

File tree

src/lib/Diff.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,19 @@ export default class Diff<
335335
action.payload,
336336
targetLocation
337337
)
338+
if (action.type !== ActionType.CREATE && typeof action.payload.id !== 'undefined' && typeof newAction.payload.id === 'undefined') {
339+
Logger.log(
340+
'payload.location = ' +
341+
action.payload.location +
342+
' | targetLocation = ' +
343+
targetLocation
344+
)
345+
const diff = new Diff()
346+
diff.commit(action)
347+
Logger.log('Failed to map id of action ' + diff.inspect())
348+
Logger.log(JSON.stringify(mappingsSnapshot, null, '\t'))
349+
throw new MappingFailureError(String(action.payload.id))
350+
}
338351
}
339352

340353
if (
@@ -397,6 +410,10 @@ export default class Diff<
397410
})
398411
}
399412

413+
if (action.type !== ActionType.REORDER) {
414+
Logger.log('Mapped action', action, newAction)
415+
}
416+
400417
newDiff.commit(newAction)
401418
})
402419
return newDiff

src/lib/Mappings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export default class Mappings {
4848
}
4949

5050
async addFolder({ localId, remoteId }: { localId?:string|number, remoteId?:string|number }):Promise<void> {
51+
Mappings.remove(this.folders, { localId, remoteId })
5152
Mappings.add(this.folders, { localId, remoteId })
5253
}
5354

@@ -56,6 +57,7 @@ export default class Mappings {
5657
}
5758

5859
async addBookmark({ localId, remoteId }: { localId?:string|number, remoteId?:string|number }):Promise<void> {
60+
Mappings.remove(this.bookmarks, { localId, remoteId })
5961
Mappings.add(this.bookmarks, { localId, remoteId })
6062
}
6163

src/lib/Scanner.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Bookmark, Folder, ItemLocation, ItemType, TItem, TItemLocation } from '
44
import Logger from './Logger'
55
import { IHashSettings } from './interfaces/Resource'
66
import { yieldToEventLoop } from './yieldToEventLoop'
7+
import Mappings from './Mappings'
78

89
export interface ScanResult<L1 extends TItemLocation, L2 extends TItemLocation> {
910
CREATE: Diff<L1, L2, CreateAction<L1, L2>>
@@ -14,23 +15,36 @@ export interface ScanResult<L1 extends TItemLocation, L2 extends TItemLocation>
1415
}
1516

1617
export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation> {
18+
private mappings: Mappings
1719
private oldTree: TItem<L1>
1820
private newTree: TItem<L2>
1921
private mergeable: (i1: TItem<TItemLocation>, i2: TItem<TItemLocation>) => boolean
2022
private hashSettings: IHashSettings
2123
private checkHashes: boolean
2224
private hasCache: boolean
25+
private createMappings: boolean
2326
private iterations = 0
2427

2528
private result: ScanResult<L2, L1>
2629

27-
constructor(oldTree:TItem<L1>, newTree:TItem<L2>, mergeable:(i1:TItem<TItemLocation>, i2:TItem<TItemLocation>)=>boolean, hashSettings: IHashSettings, checkHashes = true, hasCache = true) {
30+
constructor(
31+
mappings: Mappings,
32+
oldTree:TItem<L1>,
33+
newTree:TItem<L2>,
34+
mergeable:(i1:TItem<TItemLocation>, i2:TItem<TItemLocation>)=>boolean,
35+
hashSettings: IHashSettings,
36+
checkHashes = true,
37+
hasCache = true,
38+
createMappings = true,
39+
) {
40+
this.mappings = mappings
2841
this.oldTree = oldTree
2942
this.newTree = newTree
3043
this.mergeable = mergeable
3144
this.hashSettings = hashSettings
3245
this.checkHashes = typeof checkHashes === 'undefined' ? true : checkHashes
3346
this.hasCache = hasCache
47+
this.createMappings = createMappings
3448
this.result = {
3549
CREATE: new Diff(),
3650
UPDATE: new Diff(),
@@ -53,8 +67,14 @@ export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation>
5367

5468
async diffItem(oldItem:TItem<L1>, newItem:TItem<L2>):Promise<void> {
5569
if (oldItem.type === 'folder' && newItem.type === 'folder') {
70+
if (this.createMappings) {
71+
await this.addMapping(oldItem, newItem)
72+
}
5673
return this.diffFolder(oldItem, newItem)
5774
} else if (oldItem.type === 'bookmark' && newItem.type === 'bookmark') {
75+
if (this.createMappings) {
76+
await this.addMapping(oldItem, newItem)
77+
}
5878
return this.diffBookmark(oldItem, newItem)
5979
} else {
6080
throw new Error('Mismatched diff items: ' + oldItem.type + ', ' + newItem.type)
@@ -182,7 +202,7 @@ export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation>
182202
this.mergeable(removedItem, createdItem) &&
183203
(removedItem.type !== 'folder' ||
184204
(!this.hasCache &&
185-
removedItem.childrenSimilarity(createdItem) > 0.8))
205+
removedItem.childrenSimilarity(createdItem) >= 0.8))
186206
) {
187207
this.result.CREATE.retract(createAction)
188208
this.result.REMOVE.retract(removeAction)
@@ -243,6 +263,9 @@ export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation>
243263
reconciled = true
244264
if (oldItem.type === ItemType.FOLDER) {
245265
await this.diffItem(oldItem, createdItem)
266+
} else if (this.createMappings) {
267+
// Usually we let diffItem handle mapping creation but here we need to do it ourselves
268+
await this.addMapping(oldItem, createdItem)
246269
}
247270
} else {
248271
const newItem = createdItem.findItemFilter(
@@ -275,6 +298,9 @@ export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation>
275298
reconciled = true
276299
if (removedItem.type === ItemType.FOLDER) {
277300
await this.diffItem(removedItem, newItem)
301+
} else if (this.createMappings) {
302+
// Usually we let diffItem handle mapping creation but here we need to do it ourselves
303+
await this.addMapping(removedItem, newItem)
278304
}
279305
}
280306
}
@@ -292,6 +318,30 @@ export default class Scanner<L1 extends TItemLocation, L2 extends TItemLocation>
292318
})
293319
}
294320

321+
async addMapping(oldItem: TItem<L1>, newItem: TItem<L2>): Promise<void> {
322+
let localId, remoteId
323+
if (
324+
newItem.location === ItemLocation.SERVER &&
325+
oldItem.location === ItemLocation.LOCAL
326+
) {
327+
localId = oldItem.id
328+
remoteId = newItem.id
329+
} else if (
330+
oldItem.location === ItemLocation.SERVER &&
331+
newItem.location === ItemLocation.LOCAL
332+
) {
333+
localId = newItem.id
334+
remoteId = oldItem.id
335+
} else {
336+
return
337+
}
338+
if (newItem.type === 'folder') {
339+
await this.mappings.addFolder({ localId, remoteId })
340+
} else {
341+
await this.mappings.addBookmark({ localId, remoteId })
342+
}
343+
}
344+
295345
async addReorders(): Promise<void> {
296346
Logger.log('Scanner: Generate reorders')
297347
const targets = {}

src/lib/strategies/Default.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -626,12 +626,11 @@ export default class SyncProcess {
626626
async getDiffs():Promise<{localScanResult:ScanResult<typeof ItemLocation.LOCAL, TItemLocation>, serverScanResult:ScanResult<typeof ItemLocation.SERVER, TItemLocation>}> {
627627
const mappingsSnapshot = this.mappings.getSnapshot()
628628

629-
const newMappings = []
630-
631629
const isUsingTabs = await this.localTree.isUsingBrowserTabs?.()
632630

633631
// Since we have the cache available, Diff cache and both trees..
634632
const localScanner = new Scanner(
633+
this.mappings,
635634
this.cacheTreeRoot,
636635
this.localTreeRoot,
637636
(oldItem, newItem) => {
@@ -656,8 +655,11 @@ export default class SyncProcess {
656655
return false
657656
},
658657
this.hashSettings,
658+
true,
659+
false,
659660
)
660661
const serverScanner = new Scanner(
662+
this.mappings,
661663
this.cacheTreeRoot,
662664
this.serverTreeRoot,
663665
(oldItem, newItem) => {
@@ -671,30 +673,28 @@ export default class SyncProcess {
671673

672674
// The two are mappable, no-brainer...
673675
if (Mappings.mappable(mappingsSnapshot, oldItem, newItem)) {
674-
newMappings.push([oldItem, newItem])
675676
return true
676677
}
677678

678679
// We also allow canMergeWith here for bookmarks, because e.g. for NextcloudFolders the id of moved bookmarks changes (because their id is "<bookmarkID>;<folderId>")
679680
if (oldItem.type === 'bookmark' && newItem.type === 'bookmark' && oldItem.canMergeWith(newItem)) {
680-
newMappings.push([oldItem, newItem])
681681
return true
682682
}
683683

684684
// We also allow canMergeWith here for folders, if we're dealing with tabs, because Window IDs are not stable
685685
if (isUsingTabs && oldItem.type === 'folder' && newItem.type === 'folder' && oldItem.canMergeWith(newItem)) {
686-
newMappings.push([oldItem, newItem])
687686
return true
688687
}
689688

690689
return false
691690
},
692691
this.hashSettings,
692+
true,
693+
true,
693694
)
694695
Logger.log('Calculating diffs for local and server trees relative to cache tree')
695696
const localScanResult = await localScanner.run()
696697
const serverScanResult = await serverScanner.run()
697-
await Parallel.map(newMappings, ([localItem, serverItem]) => this.addMapping(this.server, localItem, serverItem.id), 1)
698698
return {localScanResult, serverScanResult}
699699
}
700700

@@ -765,26 +765,32 @@ export default class SyncProcess {
765765
))
766766
if (concurrentCreation) {
767767
// created on both the target and sourcely, try to reconcile
768-
const newMappings = []
769768
const subScanner = new Scanner(
769+
this.mappings,
770770
concurrentCreation.payload, // target tree
771771
action.payload, // source tree
772772
(oldItem, newItem) => {
773-
if (oldItem.type === newItem.type && oldItem.canMergeWith(newItem)) {
774-
// if two items can be merged, we'll add mappings here directly
775-
newMappings.push([oldItem, newItem.id])
773+
if (
774+
oldItem.type === newItem.type &&
775+
oldItem.canMergeWith(newItem)
776+
) {
776777
return true
777778
}
778779
return false
779780
},
780781
this.hashSettings,
781-
false
782+
false,
783+
true,
784+
true,
782785
)
783786
const scanResult = await subScanner.run()
784-
newMappings.push([concurrentCreation.payload, action.payload.id])
785-
await Parallel.each(newMappings, async([oldItem, newId]) => {
786-
await this.addMapping(action.payload.location === ItemLocation.LOCAL ? this.localTree : this.server, oldItem, newId)
787-
},1)
787+
await this.addMapping(
788+
action.payload.location === ItemLocation.LOCAL
789+
? this.localTree
790+
: this.server,
791+
concurrentCreation.payload,
792+
action.payload.id
793+
)
788794

789795
// SubScanner may reveal residual CREATE/REMOVE actions that we add to the plan here
790796
// We do not act on REMOVEs, only on CREATEs as we merge the two sides
@@ -1098,25 +1104,25 @@ export default class SyncProcess {
10981104
// Try bulk import with sub folders
10991105
const imported = await resource.bulkImportFolder(id, action.oldItem.copyWithLocation(false, action.payload.location)) as Folder<typeof targetLocation>
11001106
await done()
1101-
const newMappings = []
11021107
const subScanner = new Scanner(
1108+
this.mappings,
11031109
action.oldItem,
11041110
imported,
11051111
(oldItem, newItem) => {
1106-
if (oldItem.type === newItem.type && oldItem.canMergeWith(newItem)) {
1107-
// if two items can be merged, we'll add mappings here directly
1108-
newMappings.push([oldItem, newItem.id])
1112+
if (
1113+
oldItem.type === newItem.type &&
1114+
oldItem.canMergeWith(newItem)
1115+
) {
11091116
return true
11101117
}
11111118
return false
11121119
},
11131120
this.hashSettings,
11141121
false,
1122+
true,
1123+
true,
11151124
)
11161125
await subScanner.run()
1117-
await Parallel.each(newMappings, async([oldItem, newId]: [TItem<TItemLocation>, string|number]) => {
1118-
await this.addMapping(resource, oldItem, newId)
1119-
}, 10)
11201126

11211127
if ('orderFolder' in resource) {
11221128
const mappingsSnapshot = this.mappings.getSnapshot()
@@ -1157,25 +1163,26 @@ export default class SyncProcess {
11571163
Logger.log('Attempting chunked bulk import')
11581164
tempItem.children = bookmarks.splice(0, 70)
11591165
const imported = await resource.bulkImportFolder(action.payload.id, tempItem)
1160-
const newMappings = []
11611166
const subScanner = new Scanner(
1167+
this.mappings,
11621168
tempItem,
11631169
imported,
11641170
(oldItem, newItem) => {
1165-
if (oldItem.type === newItem.type && oldItem.canMergeWith(newItem)) {
1171+
if (
1172+
oldItem.type === newItem.type &&
1173+
oldItem.canMergeWith(newItem)
1174+
) {
11661175
// if two items can be merged, we'll add mappings here directly
1167-
newMappings.push([oldItem, newItem.id])
11681176
return true
11691177
}
11701178
return false
11711179
},
11721180
this.hashSettings,
11731181
false,
1182+
true,
1183+
true,
11741184
)
11751185
await subScanner.run()
1176-
await Parallel.each(newMappings, async([oldItem, newId]: [TItem<TItemLocation>, string|number]) => {
1177-
await this.addMapping(resource, oldItem, newId)
1178-
}, 10)
11791186
}
11801187

11811188
// create sub plan for the folders

0 commit comments

Comments
 (0)