You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +02:00
handle deleted sync items
This commit is contained in:
@@ -5,5 +5,7 @@ rm -f "$CLIENT_DIR/tests-build/src"
|
||||
mkdir -p "$CLIENT_DIR/tests-build/data"
|
||||
ln -s "$CLIENT_DIR/build/src" "$CLIENT_DIR/tests-build"
|
||||
|
||||
npm run build && NODE_PATH="$CLIENT_DIR/tests-build/" npm test tests-build/synchronizer.js
|
||||
npm run build && NODE_PATH="$CLIENT_DIR/tests-build/" npm test tests-build/base-model.js
|
||||
|
||||
#npm run build && NODE_PATH="$CLIENT_DIR/tests-build/" npm test tests-build/synchronizer.js
|
||||
#npm run build && NODE_PATH="$CLIENT_DIR/tests-build/" npm test tests-build/services/note-folder-service.js
|
40
CliClient/tests/base-model.js
Normal file
40
CliClient/tests/base-model.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { time } from 'src/time-utils.js';
|
||||
import { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient } from 'test-utils.js';
|
||||
import { createFoldersAndNotes } from 'test-data.js';
|
||||
import { Folder } from 'src/models/folder.js';
|
||||
import { Note } from 'src/models/note.js';
|
||||
import { Setting } from 'src/models/setting.js';
|
||||
import { BaseItem } from 'src/models/base-item.js';
|
||||
import { BaseModel } from 'src/base-model.js';
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.error('Unhandled promise rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('BaseItem', function() {
|
||||
|
||||
beforeEach( async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create a deleted_items record', async (done) => {
|
||||
let folder = await Folder.save({ title: 'folder1' });
|
||||
|
||||
await Folder.delete(folder.id);
|
||||
|
||||
let items = await BaseModel.deletedItems();
|
||||
|
||||
expect(items.length).toBe(1);
|
||||
expect(items[0].item_id).toBe(folder.id);
|
||||
expect(items[0].item_type).toBe(folder.type_);
|
||||
|
||||
let folders = await Folder.all();
|
||||
|
||||
expect(folders.length).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
@@ -9,7 +9,6 @@ import { BaseModel } from 'src/base-model.js';
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
// application specific logging, throwing an error, or other logic here
|
||||
});
|
||||
|
||||
async function localItemsSameAsRemote(locals, expect) {
|
||||
@@ -22,11 +21,6 @@ async function localItemsSameAsRemote(locals, expect) {
|
||||
let path = BaseItem.systemPath(dbItem);
|
||||
let remote = await fileApi().stat(path);
|
||||
|
||||
// console.info('=======================');
|
||||
// console.info(remote);
|
||||
// console.info(dbItem);
|
||||
// console.info('=======================');
|
||||
|
||||
expect(!!remote).toBe(true);
|
||||
expect(remote.updated_time).toBe(dbItem.updated_time);
|
||||
|
||||
@@ -218,4 +212,81 @@ describe('Synchronizer', function() {
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// it('should delete local items', async (done) => {
|
||||
// let folder1 = await Folder.save({ title: "folder1" });
|
||||
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
|
||||
// await synchronizer().start();
|
||||
|
||||
// switchClient(2);
|
||||
|
||||
// await synchronizer().start();
|
||||
|
||||
// await sleep(0.1);
|
||||
|
||||
// await Note.delete(note1.id);
|
||||
|
||||
// await synchronizer().start();
|
||||
|
||||
// switchClient(1);
|
||||
|
||||
// let files = await fileApi().list();
|
||||
// console.info(files);
|
||||
|
||||
// // await synchronizer().start();
|
||||
|
||||
// // note1 = await Note.load(note1.id);
|
||||
|
||||
// // expect(!note1).toBe(true);
|
||||
|
||||
// done();
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// it('should delete remote items', async (done) => {
|
||||
// let folder1 = await Folder.save({ title: "folder1" });
|
||||
// let note1 = await Note.save({ title: "un", parent_id: folder1.id });
|
||||
// await synchronizer().start();
|
||||
|
||||
// switchClient(2);
|
||||
|
||||
// await synchronizer().start();
|
||||
|
||||
// await sleep(0.1);
|
||||
|
||||
// await Note.delete(note1.id);
|
||||
|
||||
// await synchronizer().start();
|
||||
|
||||
// switchClient(1);
|
||||
|
||||
// let files = await fileApi().list();
|
||||
// console.info(files);
|
||||
|
||||
// await synchronizer().start();
|
||||
|
||||
// note1 = await Note.load(note1.id);
|
||||
|
||||
// expect(!note1).toBe(true);
|
||||
|
||||
// done();
|
||||
// });
|
||||
|
||||
});
|
@@ -37,6 +37,10 @@ class BaseModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static byId(items, id) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].id == id) return items[i];
|
||||
@@ -239,6 +243,10 @@ class BaseModel {
|
||||
});
|
||||
}
|
||||
|
||||
static deletedItems() {
|
||||
return this.db().selectAll('SELECT * FROM deleted_items');
|
||||
}
|
||||
|
||||
static delete(id, options = null) {
|
||||
options = this.modOptions(options);
|
||||
|
||||
@@ -248,6 +256,10 @@ class BaseModel {
|
||||
}
|
||||
|
||||
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
|
||||
if (this.trackDeleted()) {
|
||||
return this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.itemType(), id, time.unixMs()]);
|
||||
}
|
||||
|
||||
// if (options.trackChanges && this.trackChanges()) {
|
||||
// const { Change } = require('src/models/change.js');
|
||||
|
||||
|
@@ -36,6 +36,13 @@ CREATE TABLE notes (
|
||||
\`order\` INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE deleted_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_type INT NOT NULL,
|
||||
item_id TEXT NOT NULL,
|
||||
deleted_time INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tags (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
|
@@ -26,6 +26,10 @@ class Folder extends BaseItem {
|
||||
return true;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static newFolder() {
|
||||
return {
|
||||
id: null,
|
||||
@@ -33,8 +37,18 @@ class Folder extends BaseItem {
|
||||
}
|
||||
}
|
||||
|
||||
static noteIds(id) {
|
||||
return this.db().selectAll('SELECT id FROM notes WHERE parent_id = ?', [id]).then((rows) => {
|
||||
static syncedNoteIds() {
|
||||
return this.db().selectAll('SELECT id FROM notes WHERE sync_time > 0').then((rows) => {
|
||||
let output = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
output.push(rows[i].id);
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
static noteIds(parentId) {
|
||||
return this.db().selectAll('SELECT id FROM notes WHERE parent_id = ?', [parentId]).then((rows) => {
|
||||
let output = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
@@ -46,6 +60,8 @@ class Folder extends BaseItem {
|
||||
|
||||
static delete(folderId, options = null) {
|
||||
return this.load(folderId).then((folder) => {
|
||||
if (!folder) throw new Error('Trying to delete non-existing folder: ' + folderId);
|
||||
|
||||
if (!!folder.is_default) {
|
||||
throw new Error(_('Cannot delete the default list'));
|
||||
}
|
||||
@@ -72,7 +88,6 @@ class Folder extends BaseItem {
|
||||
|
||||
static loadNoteByField(folderId, field, value) {
|
||||
return this.modelSelectAll('SELECT * FROM notes WHERE `parent_id` = ? AND `' + field + '` = ?', [folderId, value]);
|
||||
//return this.db().selectOne('SELECT * FROM notes WHERE `parent_id` = ? AND `' + field + '` = ?', [folderId, value]);
|
||||
}
|
||||
|
||||
static async all(includeNotes = false) {
|
||||
|
@@ -24,6 +24,10 @@ class Note extends BaseItem {
|
||||
return true;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static new(parentId = '') {
|
||||
let output = super.new();
|
||||
output.parent_id = parentId;
|
||||
|
@@ -1,5 +1,10 @@
|
||||
// A service that handle notes and folders in a uniform way
|
||||
|
||||
|
||||
// TODO: remote this service
|
||||
// - Move setting of geo-location to GUI side (only for note explicitely created on client
|
||||
// - Don't do diffing - make caller explicitely set model properties that need to be saved
|
||||
|
||||
import { BaseService } from 'src/base-service.js';
|
||||
import { BaseModel } from 'src/base-model.js';
|
||||
import { BaseItem } from 'src/models/base-item.js';
|
||||
|
@@ -48,7 +48,13 @@ class Synchronizer {
|
||||
let updateSyncTimeOnly = true;
|
||||
|
||||
if (!remote) {
|
||||
action = 'createRemote';
|
||||
if (!local.sync_time) {
|
||||
action = 'createRemote';
|
||||
} else {
|
||||
// Note or folder was modified after having been deleted remotely
|
||||
action = local.type_ == BaseModel.MODEL_TYPE_NOTE ? 'noteConflict' : 'folderConflict';
|
||||
// TODO: handle conflict
|
||||
}
|
||||
} else {
|
||||
if (remote.updated_time > local.sync_time) {
|
||||
// Since, in this loop, we are only dealing with notes that require sync, if the
|
||||
@@ -107,10 +113,12 @@ class Synchronizer {
|
||||
// At this point all the local items that have changed have been pushed to remote
|
||||
// or handled as conflicts, so no conflict is possible after this.
|
||||
|
||||
let remoteIds = [];
|
||||
let remotes = await this.api().list();
|
||||
for (let i = 0; i < remotes.length; i++) {
|
||||
let remote = remotes[i];
|
||||
let path = remote.path;
|
||||
remoteIds.push(BaseItem.pathToId(path));
|
||||
if (donePaths.indexOf(path) > 0) continue;
|
||||
|
||||
let action = null;
|
||||
@@ -143,6 +151,18 @@ class Synchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Search, among the local IDs, those that don't exist remotely, which
|
||||
// means the item has been deleted.
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// let noteIds = Folder.syncedNoteIds();
|
||||
// for (let i = 0; i < noteIds.length; i++) {
|
||||
// if (remoteIds.indexOf(noteIds[i]) < 0) {
|
||||
// console.info('Sync action (3): Delete ' + noteIds[i]);
|
||||
// await Note.delete(noteIds[i]);
|
||||
// }
|
||||
// }
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
Reference in New Issue
Block a user