Skip to content

Commit 26d5a49

Browse files
committed
feat(failsafe): be more precise about where bookmarks are added/deleted
fixes #2116 Signed-off-by: Marcel Klehr <[email protected]>
1 parent 5149670 commit 26d5a49

File tree

6 files changed

+91
-32
lines changed

6 files changed

+91
-32
lines changed

_locales/en/messages.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"message": "E028: Couldn't authenticate with the server."
8585
},
8686
"Error029": {
87-
"message": "E029: Failsafe: The current sync run would delete {0}% of your links. Refusing to execute. Disable this failsafe in the account settings if you want to proceed anyway."
87+
"message": "E029: Failsafe: The current sync run would delete {0}% of your links on the server. Refusing to execute. Disable this failsafe in the account settings if you want to proceed anyway."
8888
},
8989
"Error030": {
9090
"message": "E030: Failed to decrypt bookmarks file. The passphrase may be wrong or the file may be corrupted."
@@ -126,7 +126,7 @@
126126
"message": "E042: Remote bookmarks file size could not be retrieved. It is impossible to verify that the bookmarks file was downloaded in full. If this error persists please contact the server administrator."
127127
},
128128
"Error043": {
129-
"message": "E043: Failsafe: The current sync run would increase your bookmarks count by {0}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway."
129+
"message": "E043: Failsafe: The current sync run would increase your bookmarks count on the server by {0}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway."
130130
},
131131
"Error044": {
132132
"message": "E044: Git push operation failed: {0}"
@@ -140,6 +140,12 @@
140140
"Error047": {
141141
"message": "E047: Failed to parse XBEL file. The XBEL data seems to be corrupted or incomplete. You can try removing the file on the server to let floccus recreate it. Make sure to take a backup first."
142142
},
143+
"Error049": {
144+
"message": "E049: Failsafe: The current sync run would increase your local bookmarks count in this profile by {0}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway."
145+
},
146+
"Error050": {
147+
"message": "E050: Failsafe: The current sync run would delete {0}% of your local links in this profile. Refusing to execute. Disable this failsafe in the account settings if you want to proceed anyway."
148+
},
143149
"LabelWebdavurl": {
144150
"message": "WebDAV URL"
145151
},

src/errors/Error.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,14 @@ export class InterruptedSyncError extends FloccusError {
240240

241241
// code 28 is unused
242242

243-
export class DeletionFailsafeError extends FloccusError {
243+
export class ServersideDeletionFailsafeError extends FloccusError {
244244
public percent: number
245245

246246
constructor(percent:number) {
247-
super(`E029: Failsafe: The current sync run would delete ${percent}% of your links. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
247+
super(`E029: Failsafe: The current sync run would delete ${percent}% of your links on the server. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
248248
this.code = 29
249249
this.percent = percent
250-
Object.setPrototypeOf(this, DeletionFailsafeError.prototype)
250+
Object.setPrototypeOf(this, ServersideDeletionFailsafeError.prototype)
251251
}
252252
}
253253

@@ -359,14 +359,14 @@ export class FileSizeUnknown extends FloccusError {
359359
}
360360
}
361361

362-
export class AdditionFailsafeError extends FloccusError {
362+
export class ServersideAdditionFailsafeError extends FloccusError {
363363
public percent: number
364364

365365
constructor(percent:number) {
366-
super(`E043: Failsafe: The current sync run would increase your bookmarks count by ${percent}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
366+
super(`E043: Failsafe: The current sync run would increase your bookmarks count on the server by ${percent}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
367367
this.code = 43
368368
this.percent = percent
369-
Object.setPrototypeOf(this, AdditionFailsafeError.prototype)
369+
Object.setPrototypeOf(this, ServersideAdditionFailsafeError.prototype)
370370
}
371371
}
372372

@@ -422,4 +422,26 @@ export class MappingFailureError extends FloccusError {
422422
this.code = 48
423423
Object.setPrototypeOf(this, MappingFailureError.prototype)
424424
}
425+
}
426+
427+
export class ClientsideAdditionFailsafeError extends FloccusError {
428+
public percent: number
429+
430+
constructor(percent:number) {
431+
super(`E049: Failsafe: The current sync run would increase your local link count in this profile by ${percent}%. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
432+
this.code = 49
433+
this.percent = percent
434+
Object.setPrototypeOf(this, ClientsideAdditionFailsafeError.prototype)
435+
}
436+
}
437+
438+
export class ClientsideDeletionFailsafeError extends FloccusError {
439+
public percent: number
440+
441+
constructor(percent:number) {
442+
super(`E050: Failsafe: The current sync run would delete ${percent}% of your local links in this profile. Refusing to execute. Disable this failsafe in the profile settings if you want to proceed anyway.`)
443+
this.code = 50
444+
this.percent = percent
445+
Object.setPrototypeOf(this, ClientsideDeletionFailsafeError.prototype)
446+
}
425447
}

src/lib/browser/BrowserAccount.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import browser from '../browser-api'
44
import AdapterFactory from '../AdapterFactory'
55
import Account from '../Account'
66
import {
7-
AdditionFailsafeError,
7+
ClientsideAdditionFailsafeError, ClientsideDeletionFailsafeError,
88
CreateBookmarkError,
9-
DeletionFailsafeError, FloccusError, GitPushError,
9+
FloccusError, GitPushError,
1010
HttpError,
1111
InconsistentBookmarksExistenceError, InvalidUrlError, LockFileError,
1212
MissingItemOrderError,
13-
ParseResponseError, UnexpectedFolderPathError,
13+
ParseResponseError, ServersideAdditionFailsafeError, ServersideDeletionFailsafeError, UnexpectedFolderPathError,
1414
UnknownFolderItemOrderError, UpdateBookmarkError
1515
} from '../../errors/Error'
1616
import {i18n} from '../native/I18n'
@@ -108,10 +108,16 @@ export default class BrowserAccount extends Account {
108108
if (er instanceof LockFileError) {
109109
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.status, er.lockFile])
110110
}
111-
if (er instanceof DeletionFailsafeError) {
111+
if (er instanceof ServersideDeletionFailsafeError) {
112112
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
113113
}
114-
if (er instanceof AdditionFailsafeError) {
114+
if (er instanceof ServersideAdditionFailsafeError) {
115+
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
116+
}
117+
if (er instanceof ClientsideDeletionFailsafeError) {
118+
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
119+
}
120+
if (er instanceof ClientsideAdditionFailsafeError) {
115121
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
116122
}
117123
if (er instanceof CreateBookmarkError) {

src/lib/native/NativeAccount.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import AdapterFactory from '../AdapterFactory'
44
import Account from '../Account'
55
import { IAccountData } from '../interfaces/AccountStorage'
66
import {
7-
AdditionFailsafeError,
7+
ClientsideAdditionFailsafeError, ClientsideDeletionFailsafeError,
88
CreateBookmarkError,
9-
DeletionFailsafeError, FloccusError, GitPushError,
9+
FloccusError, GitPushError,
1010
HttpError,
1111
InconsistentBookmarksExistenceError, InvalidUrlError, LockFileError,
1212
MissingItemOrderError,
13-
ParseResponseError, UnexpectedFolderPathError,
13+
ParseResponseError, ServersideAdditionFailsafeError, ServersideDeletionFailsafeError, UnexpectedFolderPathError,
1414
UnknownFolderItemOrderError, UpdateBookmarkError
1515
} from '../../errors/Error'
1616
import Logger from '../Logger'
@@ -85,10 +85,16 @@ export default class NativeAccount extends Account {
8585
if (er instanceof LockFileError) {
8686
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.status, er.lockFile])
8787
}
88-
if (er instanceof DeletionFailsafeError) {
88+
if (er instanceof ServersideDeletionFailsafeError) {
8989
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
9090
}
91-
if (er instanceof AdditionFailsafeError) {
91+
if (er instanceof ServersideAdditionFailsafeError) {
92+
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
93+
}
94+
if (er instanceof ClientsideDeletionFailsafeError) {
95+
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
96+
}
97+
if (er instanceof ClientsideAdditionFailsafeError) {
9298
return i18n.getMessage('Error' + String(er.code).padStart(3, '0'), [er.percent])
9399
}
94100
if (er instanceof CreateBookmarkError) {

src/lib/strategies/Default.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import type { ThrottledFunction } from '@jcoreio/async-throttle'
2525
import Mappings, { MappingSnapshot } from '../Mappings'
2626
import TResource, { IHashSettings, OrderFolderResource, TLocalTree } from '../interfaces/Resource'
2727
import { TAdapter } from '../interfaces/Adapter'
28-
import { AdditionFailsafeError, CancelledSyncError, DeletionFailsafeError } from '../../errors/Error'
28+
import {
29+
CancelledSyncError, ClientsideAdditionFailsafeError,
30+
ClientsideDeletionFailsafeError, ServersideAdditionFailsafeError,
31+
ServersideDeletionFailsafeError
32+
} from '../../errors/Error'
2933

3034
import NextcloudBookmarksAdapter from '../adapters/NextcloudBookmarks'
3135
import CachingAdapter from '../adapters/Caching'
@@ -288,10 +292,10 @@ export default class SyncProcess {
288292

289293
Logger.log({localPlan: this.localPlanStage2, serverPlan: this.serverPlanStage2})
290294

291-
this.applyDeletionFailsafe(this.localTreeRoot, this.localPlanStage2.REMOVE)
292-
this.applyAdditionFailsafe(this.localTreeRoot, this.localPlanStage2.CREATE)
293-
this.applyDeletionFailsafe(this.serverTreeRoot, this.serverPlanStage2.REMOVE)
294-
this.applyAdditionFailsafe(this.serverTreeRoot, this.serverPlanStage2.CREATE)
295+
this.applyDeletionFailsafe(ItemLocation.LOCAL, this.localTreeRoot, this.localPlanStage2.REMOVE)
296+
this.applyAdditionFailsafe(ItemLocation.LOCAL, this.localTreeRoot, this.localPlanStage2.CREATE)
297+
this.applyDeletionFailsafe(ItemLocation.SERVER, this.serverTreeRoot, this.serverPlanStage2.REMOVE)
298+
this.applyAdditionFailsafe(ItemLocation.SERVER, this.serverTreeRoot, this.serverPlanStage2.CREATE)
295299

296300
if (!this.localDonePlan) {
297301
this.localDonePlan = {
@@ -471,7 +475,7 @@ export default class SyncProcess {
471475
this.serverTreeRoot.createIndex()
472476
}
473477

474-
protected applyDeletionFailsafe(tree: Folder<TItemLocation>, removals: Diff<TItemLocation, TItemLocation, RemoveAction<TItemLocation, TItemLocation>>) {
478+
protected applyDeletionFailsafe(direction: TItemLocation, tree: Folder<TItemLocation>, removals: Diff<TItemLocation, TItemLocation, RemoveAction<TItemLocation, TItemLocation>>) {
475479
const countTotal = tree.count()
476480
const countDeleted = removals.getActions().reduce((count, action) => count + action.payload.count(), 0)
477481

@@ -480,12 +484,17 @@ export default class SyncProcess {
480484
if ((countTotal > 5 && countDeleted / countTotal > 0.2) || countDeleted > 1000) {
481485
const failsafe = this.server.getData().failsafe
482486
if (failsafe !== false || typeof failsafe === 'undefined') {
483-
throw new DeletionFailsafeError(Math.ceil((countDeleted / countTotal) * 100))
487+
const percentage = Math.ceil((countDeleted / countTotal) * 100)
488+
if (direction === ItemLocation.LOCAL) {
489+
throw new ClientsideDeletionFailsafeError(percentage)
490+
} else {
491+
throw new ServersideDeletionFailsafeError(percentage)
492+
}
484493
}
485494
}
486495
}
487496

488-
protected applyAdditionFailsafe(tree: Folder<TItemLocation>, creations: Diff<TItemLocation, TItemLocation, CreateAction<TItemLocation, TItemLocation>>) {
497+
protected applyAdditionFailsafe(direction: TItemLocation, tree: Folder<TItemLocation>, creations: Diff<TItemLocation, TItemLocation, CreateAction<TItemLocation, TItemLocation>>) {
489498
const countTotal = tree.count()
490499
const countAdded = creations.getActions().reduce((count, action) => count + action.payload.count(), 0)
491500

@@ -494,7 +503,12 @@ export default class SyncProcess {
494503
if (countTotal > 5 && ((countAdded >= 20 && countAdded / countTotal > 0.2) || countAdded > 1000)) {
495504
const failsafe = this.server.getData().failsafe
496505
if (failsafe !== false || typeof failsafe === 'undefined') {
497-
throw new AdditionFailsafeError(Math.ceil((countAdded / countTotal) * 100))
506+
const percentage = Math.ceil((countAdded / countTotal) * 100)
507+
if (direction === ItemLocation.LOCAL) {
508+
throw new ClientsideAdditionFailsafeError(percentage)
509+
} else {
510+
throw new ServersideAdditionFailsafeError(percentage)
511+
}
498512
}
499513
}
500514
}

src/test/test.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import DefunctCrypto from '../lib/DefunctCrypto'
1111
import Controller from '../lib/Controller'
1212
import FakeAdapter from '../lib/adapters/Fake'
1313
import BrowserTree from '../lib/browser/BrowserTree'
14-
import { AdditionFailsafeError, DeletionFailsafeError } from '../errors/Error'
14+
import {
15+
AdditionFailsafeError,
16+
ClientsideAdditionFailsafeError,
17+
ClientsideDeletionFailsafeError,
18+
DeletionFailsafeError, ServersideAdditionFailsafeError, ServersideDeletionFailsafeError
19+
} from '../errors/Error'
1520

1621
chai.use(chaiAsPromised)
1722
const expect = chai.expect
@@ -2238,7 +2243,7 @@ describe('Floccus', function() {
22382243

22392244
await account.sync()
22402245
expect(account.getData().error).to.be.ok // should have errored
2241-
expect(account.getData().error).to.include((new DeletionFailsafeError).code)
2246+
expect(account.getData().error).to.include((new ClientsideDeletionFailsafeError()).code)
22422247
})
22432248
it('should error when adding too much local data (failsafe)', async function() {
22442249
if (ACCOUNT_DATA.noCache) {
@@ -2303,7 +2308,7 @@ describe('Floccus', function() {
23032308

23042309
await account.sync()
23052310
expect(account.getData().error).to.be.ok // should have errored
2306-
expect(account.getData().error).to.include((new AdditionFailsafeError).code)
2311+
expect(account.getData().error).to.include((new ClientsideAdditionFailsafeError()).code)
23072312
})
23082313
it('should error when deleting too much remote data (failsafe)', async function() {
23092314
if (ACCOUNT_DATA.noCache) {
@@ -2345,7 +2350,7 @@ describe('Floccus', function() {
23452350

23462351
await account.sync()
23472352
expect(account.getData().error).to.be.ok // should have errored
2348-
expect(account.getData().error).to.include((new DeletionFailsafeError).code)
2353+
expect(account.getData().error).to.include((new ServersideDeletionFailsafeError()).code)
23492354
})
23502355
it('should error when adding too much remote data (failsafe)', async function() {
23512356
if (ACCOUNT_DATA.noCache) {
@@ -2409,7 +2414,7 @@ describe('Floccus', function() {
24092414

24102415
await account.sync()
24112416
expect(account.getData().error).to.be.ok // should have errored
2412-
expect(account.getData().error).to.include((new AdditionFailsafeError).code)
2417+
expect(account.getData().error).to.include((new ServersideAdditionFailsafeError()).code)
24132418
})
24142419
it('should leave alone unaccepted bookmarks entirely', async function() {
24152420
if (!~ACCOUNT_DATA.type.indexOf('nextcloud')) {

0 commit comments

Comments
 (0)