mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
All: Added fail-safe to prevent data from being wiped out when the sync target is empty
This commit is contained in:
parent
3e5a9cdb97
commit
348efdd7b6
@ -1480,4 +1480,20 @@ describe('Synchronizer', function() {
|
|||||||
expect((await decryptionWorker().decryptionDisabledItems()).length).toBe(0);
|
expect((await decryptionWorker().decryptionDisabledItems()).length).toBe(0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should not wipe out user data when syncing with an empty target', asyncTest(async () => {
|
||||||
|
await Note.save({ title: 'ma note' });
|
||||||
|
await Note.save({ title: 'mon autre note' });
|
||||||
|
await Note.save({ title: 'ma troisième note' });
|
||||||
|
|
||||||
|
Setting.setValue('sync.wipeOutFailSafe', true);
|
||||||
|
await synchronizer().start();
|
||||||
|
await fileApi().clearRoot(); // oops
|
||||||
|
await synchronizer().start();
|
||||||
|
expect((await Note.all()).length).toBe(3); // but since the fail-safe if on, the notes have not been deleted
|
||||||
|
|
||||||
|
Setting.setValue('sync.wipeOutFailSafe', false); // Now switch it off
|
||||||
|
await synchronizer().start();
|
||||||
|
expect((await Note.all()).length).toBe(0); // Since the fail-safe was off, the data has been cleared
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -135,7 +135,9 @@ async function switchClient(id) {
|
|||||||
|
|
||||||
Setting.setConstant('resourceDir', resourceDir(id));
|
Setting.setConstant('resourceDir', resourceDir(id));
|
||||||
|
|
||||||
return Setting.load();
|
await Setting.load();
|
||||||
|
|
||||||
|
Setting.setValue('sync.wipeOutFailSafe', false); // To keep things simple, always disable fail-safe unless explicitely set in the test itself
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearDatabase(id = null) {
|
async function clearDatabase(id = null) {
|
||||||
|
@ -247,6 +247,13 @@ async function basicDelta(path, getDirStatFn, options) {
|
|||||||
});
|
});
|
||||||
newContext.statIdsCache = newContext.statsCache.filter(item => BaseItem.isSystemPath(item.path)).map(item => BaseItem.pathToId(item.path));
|
newContext.statIdsCache = newContext.statsCache.filter(item => BaseItem.isSystemPath(item.path)).map(item => BaseItem.pathToId(item.path));
|
||||||
newContext.statIdsCache.sort(); // Items must be sorted to use binary search below
|
newContext.statIdsCache.sort(); // Items must be sorted to use binary search below
|
||||||
|
|
||||||
|
// At this point statIdsCache contains the list of all the item IDs on the sync target.
|
||||||
|
// If it's empty, it's most likely a configuration error or bug. For example, if the
|
||||||
|
// user moves their Nextcloud directory, or if a network drive gets disconnected and
|
||||||
|
// returns an empty dir instead of an error. In that case, we don't wipe out the user
|
||||||
|
// data, unless they have switched off the fail-safe.
|
||||||
|
if (options.wipeOutFailSafe && !newContext.statIdsCache.length) throw new JoplinError('Fail-safe: The delta operation was interrupted because the sync target appears to be empty. To override this behaviour disable the "Wipe out fail-safe" option in the settings.', 'failSafe');
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = [];
|
let output = [];
|
||||||
|
@ -425,6 +425,8 @@ class Setting extends BaseModel {
|
|||||||
label: () => _('Ignore TLS certificate errors'),
|
label: () => _('Ignore TLS certificate errors'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'sync.wipeOutFailSafe': { value: true, type: Setting.TYPE_BOOL, public: true, section: 'sync', label: () => _('Fail-safe: Do not wipe out local data when sync target is empty (often the result of a misconfiguration or bug)') },
|
||||||
|
|
||||||
'api.token': { value: null, type: Setting.TYPE_STRING, public: false },
|
'api.token': { value: null, type: Setting.TYPE_STRING, public: false },
|
||||||
'api.port': { value: null, type: Setting.TYPE_INT, public: true, appTypes: ['cli'], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
|
'api.port': { value: null, type: Setting.TYPE_INT, public: true, appTypes: ['cli'], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
||||||
const MasterKey = require('lib/models/MasterKey.js');
|
const MasterKey = require('lib/models/MasterKey.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
@ -508,6 +509,8 @@ class Synchronizer {
|
|||||||
allItemIdsHandler: async () => {
|
allItemIdsHandler: async () => {
|
||||||
return BaseItem.syncedItemIds(syncTargetId);
|
return BaseItem.syncedItemIds(syncTargetId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
wipeOutFailSafe: Setting.value('sync.wipeOutFailSafe'),
|
||||||
});
|
});
|
||||||
|
|
||||||
let remotes = listResult.items;
|
let remotes = listResult.items;
|
||||||
@ -695,7 +698,7 @@ class Synchronizer {
|
|||||||
}
|
}
|
||||||
} // DELTA STEP
|
} // DELTA STEP
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && ['cannotEncryptEncrypted', 'noActiveMasterKey', 'processingPathTwice'].indexOf(error.code) >= 0) {
|
if (error && ['cannotEncryptEncrypted', 'noActiveMasterKey', 'processingPathTwice', 'failSafe'].indexOf(error.code) >= 0) {
|
||||||
// Only log an info statement for this since this is a common condition that is reported
|
// Only log an info statement for this since this is a common condition that is reported
|
||||||
// in the application, and needs to be resolved by the user.
|
// in the application, and needs to be resolved by the user.
|
||||||
// Or it's a temporary issue that will be resolved on next sync.
|
// Or it's a temporary issue that will be resolved on next sync.
|
||||||
|
Loading…
Reference in New Issue
Block a user