mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Fixed syncing of tags
This commit is contained in:
parent
1ecf5b626e
commit
c5e71a668c
13019
CliClient/tests/logs/log.txt
Normal file
13019
CliClient/tests/logs/log.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -486,7 +486,7 @@ describe('Synchronizer', function() {
|
|||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
let remoteNoteIds = await Tag.tagNoteIds(tag.id);
|
let remoteNoteIds = await Tag.tagNoteIds(tag.id);
|
||||||
expect(remoteNoteIds.length).toBe(2);
|
expect(remoteNoteIds.length).toBe(2);
|
||||||
Tag.removeNote(tag.id, n1.id);
|
await Tag.removeNote(tag.id, n1.id);
|
||||||
remoteNoteIds = await Tag.tagNoteIds(tag.id);
|
remoteNoteIds = await Tag.tagNoteIds(tag.id);
|
||||||
expect(remoteNoteIds.length).toBe(1);
|
expect(remoteNoteIds.length).toBe(1);
|
||||||
await synchronizer().start();
|
await synchronizer().start();
|
||||||
|
@ -18,7 +18,7 @@ let fileApi_ = null;
|
|||||||
let currentClient_ = 1;
|
let currentClient_ = 1;
|
||||||
|
|
||||||
const logger = new Logger();
|
const logger = new Logger();
|
||||||
logger.addTarget('file', { path: __dirname + '/data/log-test.txt' });
|
logger.addTarget('file', { path: __dirname + '/../tests/logs/log.txt' });
|
||||||
//logger.addTarget('console');
|
//logger.addTarget('console');
|
||||||
logger.setLevel(Logger.LEVEL_DEBUG);
|
logger.setLevel(Logger.LEVEL_DEBUG);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"CliClient/app/lib",
|
"CliClient/app/lib",
|
||||||
"CliClient/tests/src",
|
"CliClient/tests/src",
|
||||||
"CliClient/tests/fuzzing",
|
"CliClient/tests/fuzzing",
|
||||||
|
"CliClient/tests/fuzzing -*",
|
||||||
"ReactNativeClient/node_modules",
|
"ReactNativeClient/node_modules",
|
||||||
"ReactNativeClient/android/app/build",
|
"ReactNativeClient/android/app/build",
|
||||||
"ReactNativeClient/android/build",
|
"ReactNativeClient/android/build",
|
||||||
|
@ -65,9 +65,12 @@ CREATE TABLE tags (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE note_tags (
|
CREATE TABLE note_tags (
|
||||||
id INTEGER PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
note_id TEXT NOT NULL,
|
note_id TEXT NOT NULL,
|
||||||
tag_id TEXT NOT NULL
|
tag_id TEXT NOT NULL,
|
||||||
|
created_time INT NOT NULL,
|
||||||
|
updated_time INT NOT NULL,
|
||||||
|
sync_time INT NOT NULL DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE resources (
|
CREATE TABLE resources (
|
||||||
@ -135,15 +138,6 @@ class Database {
|
|||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDebugMode(v) {
|
|
||||||
// //this.driver_.setDebugMode(v);
|
|
||||||
// this.debugMode_ = v;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// debugMode() {
|
|
||||||
// return this.debugMode_;
|
|
||||||
// }
|
|
||||||
|
|
||||||
initialized() {
|
initialized() {
|
||||||
return this.initialized_;
|
return this.initialized_;
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,13 @@ class BaseItem extends BaseModel {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static trackDeleted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to dynamically load the classes like this to avoid circular dependencies
|
// Need to dynamically load the classes like this to avoid circular dependencies
|
||||||
static getClass(name) {
|
static getClass(name) {
|
||||||
if (!this.classes_) this.classes_ = {};
|
if (!this.classes_) this.classes_ = {};
|
||||||
if (this.classes_[name]) return this.classes_[name];
|
if (this.classes_[name]) return this.classes_[name];
|
||||||
this.classes_[name] = require('lib/models/' + name.toLowerCase() + '.js')[name];
|
let filename = name.toLowerCase();
|
||||||
|
if (name == 'NoteTag') filename = 'note-tag';
|
||||||
|
this.classes_[name] = require('lib/models/' + filename + '.js')[name];
|
||||||
return this.classes_[name];
|
return this.classes_[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +31,15 @@ class BaseItem extends BaseModel {
|
|||||||
if (!('type_' in item)) throw new Error('Item does not have a type_ property');
|
if (!('type_' in item)) throw new Error('Item does not have a type_ property');
|
||||||
return this.itemClass(item.type_);
|
return this.itemClass(item.type_);
|
||||||
} else {
|
} else {
|
||||||
if (Number(item) === BaseModel.TYPE_NOTE) return this.getClass('Note');
|
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
|
||||||
if (Number(item) === BaseModel.TYPE_FOLDER) return this.getClass('Folder');
|
let d = BaseItem.syncItemDefinitions_[i];
|
||||||
if (Number(item) === BaseModel.TYPE_RESOURCE) return this.getClass('Resource');
|
if (Number(item) == d.type) return this.getClass(d.className);
|
||||||
if (Number(item) === BaseModel.TYPE_TAG) return this.getClass('Tag');
|
}
|
||||||
|
// if (Number(item) === BaseModel.TYPE_NOTE) return this.getClass('Note');
|
||||||
|
// if (Number(item) === BaseModel.TYPE_FOLDER) return this.getClass('Folder');
|
||||||
|
// if (Number(item) === BaseModel.TYPE_RESOURCE) return this.getClass('Resource');
|
||||||
|
// if (Number(item) === BaseModel.TYPE_TAG) return this.getClass('Tag');
|
||||||
|
// if (Number(item) === BaseModel.TYPE_NOTE_TAG) return this.getClass('NoteTag');
|
||||||
throw new Error('Unknown type: ' + item);
|
throw new Error('Unknown type: ' + item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +50,8 @@ class BaseItem extends BaseModel {
|
|||||||
let notes = await this.getClass('Note').modelSelectAll('SELECT id FROM notes WHERE is_conflict = 0 AND sync_time > 0');
|
let notes = await this.getClass('Note').modelSelectAll('SELECT id FROM notes WHERE is_conflict = 0 AND sync_time > 0');
|
||||||
let resources = await this.getClass('Resource').modelSelectAll('SELECT id FROM resources WHERE sync_time > 0');
|
let resources = await this.getClass('Resource').modelSelectAll('SELECT id FROM resources WHERE sync_time > 0');
|
||||||
let tags = await this.getClass('Tag').modelSelectAll('SELECT id FROM tags WHERE sync_time > 0');
|
let tags = await this.getClass('Tag').modelSelectAll('SELECT id FROM tags WHERE sync_time > 0');
|
||||||
return folders.concat(notes).concat(resources).concat(tags);
|
let noteTags = await this.getClass('NoteTag').modelSelectAll('SELECT id FROM note_tags WHERE sync_time > 0');
|
||||||
|
return folders.concat(notes).concat(resources).concat(tags).concat(noteTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static pathToId(path) {
|
static pathToId(path) {
|
||||||
@ -60,7 +64,7 @@ class BaseItem extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async loadItemById(id) {
|
static async loadItemById(id) {
|
||||||
let classes = ['Note', 'Folder', 'Resource', 'Tag'];
|
let classes = this.syncItemClassNames();
|
||||||
for (let i = 0; i < classes.length; i++) {
|
for (let i = 0; i < classes.length; i++) {
|
||||||
let item = await this.getClass(classes[i]).load(id);
|
let item = await this.getClass(classes[i]).load(id);
|
||||||
if (item) return item;
|
if (item) return item;
|
||||||
@ -84,7 +88,7 @@ class BaseItem extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async delete(id, options = null) {
|
static async delete(id, options = null) {
|
||||||
let trackDeleted = this.trackDeleted();
|
let trackDeleted = true;
|
||||||
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
||||||
|
|
||||||
await super.delete(id, options);
|
await super.delete(id, options);
|
||||||
@ -215,9 +219,30 @@ class BaseItem extends BaseModel {
|
|||||||
if (items.length) return { hasMore: true, items: items };
|
if (items.length) return { hasMore: true, items: items };
|
||||||
|
|
||||||
items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||||
|
if (items.length) return { hasMore: true, items: items };
|
||||||
|
|
||||||
|
items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||||
return { hasMore: items.length >= limit, items: items };
|
return { hasMore: items.length >= limit, items: items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static syncItemClassNames() {
|
||||||
|
return BaseItem.syncItemDefinitions_.map((def) => {
|
||||||
|
return def.className;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also update:
|
||||||
|
// - itemsThatNeedSync()
|
||||||
|
// - syncedItems()
|
||||||
|
|
||||||
|
BaseItem.syncItemDefinitions_ = [
|
||||||
|
{ type: BaseModel.TYPE_NOTE, className: 'Note' },
|
||||||
|
{ type: BaseModel.TYPE_FOLDER, className: 'Folder' },
|
||||||
|
{ type: BaseModel.TYPE_RESOURCE, className: 'Resource' },
|
||||||
|
{ type: BaseModel.TYPE_TAG, className: 'Tag' },
|
||||||
|
{ type: BaseModel.TYPE_NOTE_TAG, className: 'NoteTag' },
|
||||||
|
];
|
||||||
|
|
||||||
export { BaseItem };
|
export { BaseItem };
|
@ -24,10 +24,6 @@ class Folder extends BaseItem {
|
|||||||
static modelType() {
|
static modelType() {
|
||||||
return BaseModel.TYPE_FOLDER;
|
return BaseModel.TYPE_FOLDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
static trackDeleted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static newFolder() {
|
static newFolder() {
|
||||||
return {
|
return {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Database } from 'lib/database.js';
|
import { Database } from 'lib/database.js';
|
||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
|
import lodash from 'lodash';
|
||||||
|
|
||||||
|
class NoteTag extends BaseItem {
|
||||||
class Tag extends BaseItem {
|
|
||||||
|
|
||||||
static tableName() {
|
static tableName() {
|
||||||
return 'note_tags';
|
return 'note_tags';
|
||||||
@ -12,92 +13,12 @@ class Tag extends BaseItem {
|
|||||||
return BaseModel.TYPE_NOTE_TAG;
|
return BaseModel.TYPE_NOTE_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static async serialize(item, type = null, shownKeys = null) {
|
static async serialize(item, type = null, shownKeys = null) {
|
||||||
// let fieldNames = this.fieldNames();
|
let fieldNames = this.fieldNames();
|
||||||
// fieldNames.push('type_');
|
fieldNames.push('type_');
|
||||||
// fieldNames.push(async () => {
|
lodash.pull(fieldNames, 'sync_time');
|
||||||
// let noteIds = await this.tagNoteIds(item.id);
|
return super.serialize(item, 'note_tag', fieldNames);
|
||||||
// return {
|
}
|
||||||
// key: 'notes_',
|
|
||||||
// value: noteIds.join(','),
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// lodash.pull(fieldNames, 'sync_time');
|
|
||||||
// return super.serialize(item, 'tag', fieldNames);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async tagNoteIds(tagId) {
|
|
||||||
// let rows = await this.db().selectAll('SELECT note_id FROM note_tags WHERE tag_id = ?', [tagId]);
|
|
||||||
// let output = [];
|
|
||||||
// for (let i = 0; i < rows.length; i++) {
|
|
||||||
// output.push(rows[i].note_id);
|
|
||||||
// }
|
|
||||||
// return output;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async notes(tagId) {
|
|
||||||
// let noteIds = await this.tagNoteIds(tagId);
|
|
||||||
// if (!noteIds.length) return [];
|
|
||||||
|
|
||||||
// let noteIdsSql = noteIds.join('","');
|
|
||||||
// noteIdsSql = '"' + noteIdsSql + '"';
|
|
||||||
// let options = {
|
|
||||||
// conditions: ['id IN (' + noteIdsSql + ')'],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return Note.search(options);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async addNote(tagId, noteId) {
|
|
||||||
// let hasIt = await this.hasNote(tagId, noteId);
|
|
||||||
// if (hasIt) return;
|
|
||||||
|
|
||||||
// let query = Database.insertQuery('note_tags', {
|
|
||||||
// tag_id: tagId,
|
|
||||||
// note_id: noteId,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// await this.db().exec(query);
|
|
||||||
// await this.save({ id: tagId, updated_time: time.unixMs() });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async removeNote(tagId, noteId) {
|
|
||||||
// await this.db().exec('DELETE FROM note_tags WHERE tag_id = ? AND note_id = ?', [tagId, noteId]);
|
|
||||||
// await this.save({ id: tagId, updated_time: time.unixMs() });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Note: updated_time must not change here since this is only called from
|
|
||||||
// // save(), which already handles how the updated_time property is set.
|
|
||||||
// static async setAssociatedNotes_(tagId, noteIds) {
|
|
||||||
// let queries = [{
|
|
||||||
// sql: 'DELETE FROM note_tags WHERE tag_id = ?',
|
|
||||||
// params: [tagId],
|
|
||||||
// }];
|
|
||||||
|
|
||||||
// for (let i = 0; i < noteIds.length; i++) {
|
|
||||||
// queries.push(Database.insertQuery('note_tags', { tag_id: tagId, note_id: noteIds[i] }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return this.db().transactionExecBatch(queries);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async hasNote(tagId, noteId) {
|
|
||||||
// let r = await this.db().selectOne('SELECT note_id FROM note_tags WHERE tag_id = ? AND note_id = ? LIMIT 1', [tagId, noteId]);
|
|
||||||
// return !!r;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static async save(o, options = null) {
|
|
||||||
// let result = await super.save(o, options);
|
|
||||||
|
|
||||||
// if (options && options.applyMetadataChanges === true) {
|
|
||||||
// if (o.notes_) {
|
|
||||||
// let noteIds = o.notes_.split(',');
|
|
||||||
// await this.setAssociatedNotes_(o.id, noteIds);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +24,6 @@ class Note extends BaseItem {
|
|||||||
return BaseModel.TYPE_NOTE;
|
return BaseModel.TYPE_NOTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static trackDeleted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static new(parentId = '') {
|
static new(parentId = '') {
|
||||||
let output = super.new();
|
let output = super.new();
|
||||||
output.parent_id = parentId;
|
output.parent_id = parentId;
|
||||||
@ -76,28 +72,6 @@ class Note extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.search(options);
|
return this.search(options);
|
||||||
|
|
||||||
// let sql = 'SELECT ' + this.previewFieldsSql() + ' FROM notes WHERE is_conflict = 0 AND parent_id = ?';
|
|
||||||
// let params = [parentId];
|
|
||||||
// if (options.itemTypes && options.itemTypes.length) {
|
|
||||||
// if (options.itemTypes.indexOf('note') >= 0 && options.itemTypes.indexOf('todo') >= 0) {
|
|
||||||
// // Fetch everything
|
|
||||||
// } else if (options.itemTypes.indexOf('note') >= 0) {
|
|
||||||
// sql += ' AND is_todo = 0';
|
|
||||||
// } else if (options.itemTypes.indexOf('todo') >= 0) {
|
|
||||||
// sql += ' AND is_todo = 1';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (options.titlePattern) {
|
|
||||||
// let pattern = options.titlePattern.replace(/\*/g, '%');
|
|
||||||
// sql += ' AND title LIKE ?';
|
|
||||||
// params.push(pattern);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let query = this.applySqlOptions(options, sql, params);
|
|
||||||
|
|
||||||
// return this.modelSelectAll(query.sql, query.params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static preview(noteId) {
|
static preview(noteId) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Database } from 'lib/database.js';
|
import { Database } from 'lib/database.js';
|
||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
|
import { NoteTag } from 'lib/models/note-tag.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
@ -18,13 +19,6 @@ class Tag extends BaseItem {
|
|||||||
static async serialize(item, type = null, shownKeys = null) {
|
static async serialize(item, type = null, shownKeys = null) {
|
||||||
let fieldNames = this.fieldNames();
|
let fieldNames = this.fieldNames();
|
||||||
fieldNames.push('type_');
|
fieldNames.push('type_');
|
||||||
fieldNames.push(async () => {
|
|
||||||
let noteIds = await this.tagNoteIds(item.id);
|
|
||||||
return {
|
|
||||||
key: 'notes_',
|
|
||||||
value: noteIds.join(','),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
lodash.pull(fieldNames, 'sync_time');
|
lodash.pull(fieldNames, 'sync_time');
|
||||||
return super.serialize(item, 'tag', fieldNames);
|
return super.serialize(item, 'tag', fieldNames);
|
||||||
}
|
}
|
||||||
@ -55,33 +49,17 @@ class Tag extends BaseItem {
|
|||||||
let hasIt = await this.hasNote(tagId, noteId);
|
let hasIt = await this.hasNote(tagId, noteId);
|
||||||
if (hasIt) return;
|
if (hasIt) return;
|
||||||
|
|
||||||
let query = Database.insertQuery('note_tags', {
|
return NoteTag.save({
|
||||||
tag_id: tagId,
|
tag_id: tagId,
|
||||||
note_id: noteId,
|
note_id: noteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.db().exec(query);
|
|
||||||
await this.save({ id: tagId, updated_time: time.unixMs() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeNote(tagId, noteId) {
|
static async removeNote(tagId, noteId) {
|
||||||
await this.db().exec('DELETE FROM note_tags WHERE tag_id = ? AND note_id = ?', [tagId, noteId]);
|
let noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ? and note_id = ?', [tagId, noteId]);
|
||||||
await this.save({ id: tagId, updated_time: time.unixMs() });
|
for (let i = 0; i < noteTags.length; i++) {
|
||||||
}
|
await NoteTag.delete(noteTags[i].id);
|
||||||
|
|
||||||
// Note: updated_time must not change here since this is only called from
|
|
||||||
// save(), which already handles how the updated_time property is set.
|
|
||||||
static async setAssociatedNotes_(tagId, noteIds) {
|
|
||||||
let queries = [{
|
|
||||||
sql: 'DELETE FROM note_tags WHERE tag_id = ?',
|
|
||||||
params: [tagId],
|
|
||||||
}];
|
|
||||||
|
|
||||||
for (let i = 0; i < noteIds.length; i++) {
|
|
||||||
queries.push(Database.insertQuery('note_tags', { tag_id: tagId, note_id: noteIds[i] }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db().transactionExecBatch(queries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async hasNote(tagId, noteId) {
|
static async hasNote(tagId, noteId) {
|
||||||
@ -89,19 +67,6 @@ class Tag extends BaseItem {
|
|||||||
return !!r;
|
return !!r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async save(o, options = null) {
|
|
||||||
let result = await super.save(o, options);
|
|
||||||
|
|
||||||
if (options && options.applyMetadataChanges === true) {
|
|
||||||
if (o.notes_) {
|
|
||||||
let noteIds = o.notes_.split(',');
|
|
||||||
await this.setAssociatedNotes_(o.id, noteIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tag };
|
export { Tag };
|
Loading…
Reference in New Issue
Block a user