mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-26 18:58:21 +02:00
sync tags
This commit is contained in:
parent
f04c1c58f1
commit
a11acb0fa8
File diff suppressed because one or more lines are too long
@ -76,7 +76,7 @@ async function saveNoteResources(note) {
|
||||
}
|
||||
|
||||
async function saveNoteTags(note) {
|
||||
let noteTagged = 0;
|
||||
let notesTagged = 0;
|
||||
for (let i = 0; i < note.tags.length; i++) {
|
||||
let tagTitle = note.tags[i];
|
||||
|
||||
@ -85,9 +85,9 @@ async function saveNoteTags(note) {
|
||||
|
||||
await Tag.addNote(tag.id, note.id);
|
||||
|
||||
noteTagged++;
|
||||
notesTagged++;
|
||||
}
|
||||
return noteTagged;
|
||||
return notesTagged;
|
||||
}
|
||||
|
||||
async function saveNoteToStorage(note, fuzzyMatching = false) {
|
||||
@ -100,14 +100,14 @@ async function saveNoteToStorage(note, fuzzyMatching = false) {
|
||||
noteUpdated: false,
|
||||
noteSkipped: false,
|
||||
resourcesCreated: 0,
|
||||
noteTagged: 0,
|
||||
notesTagged: 0,
|
||||
};
|
||||
|
||||
let resourcesCreated = await saveNoteResources(note);
|
||||
result.resourcesCreated += resourcesCreated;
|
||||
|
||||
let noteTagged = await saveNoteTags(note);
|
||||
result.noteTagged += noteTagged;
|
||||
let notesTagged = await saveNoteTags(note);
|
||||
result.notesTagged += notesTagged;
|
||||
|
||||
if (existingNote) {
|
||||
let diff = BaseModel.diffObjects(existingNote, note);
|
||||
@ -148,7 +148,7 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
resourcesCreated: 0,
|
||||
noteTagged: 0,
|
||||
notesTagged: 0,
|
||||
};
|
||||
|
||||
let stream = fs.createReadStream(filePath);
|
||||
@ -213,7 +213,7 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||
progressState.skipped++;
|
||||
}
|
||||
progressState.resourcesCreated += result.resourcesCreated;
|
||||
progressState.noteTagged += result.noteTagged;
|
||||
progressState.notesTagged += result.notesTagged;
|
||||
importOptions.onProgress(progressState);
|
||||
});
|
||||
});
|
||||
|
@ -391,8 +391,8 @@ commands.push({
|
||||
if (report.remotesToDelete) line.push(_('Remote items to delete: %d/%d.', report.deleteRemote, report.remotesToDelete));
|
||||
if (report.localsToUdpate) line.push(_('Items to download: %d/%d.', report.createLocal + report.updateLocal, report.localsToUdpate));
|
||||
if (report.localsToDelete) line.push(_('Local items to delete: %d/%d.', report.deleteLocal, report.localsToDelete));
|
||||
//redrawnCalled = true;
|
||||
//vorpal.ui.redraw(line.join(' '));
|
||||
// redrawnCalled = true;
|
||||
// vorpal.ui.redraw(line.join(' '));
|
||||
},
|
||||
onMessage: (msg) => {
|
||||
if (redrawnCalled) vorpal.ui.redraw.done();
|
||||
|
@ -45,7 +45,6 @@ function clearDatabase(id = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
|
||||
let queries = [
|
||||
'DELETE FROM changes',
|
||||
'DELETE FROM notes',
|
||||
'DELETE FROM folders',
|
||||
'DELETE FROM resources',
|
||||
|
@ -37,10 +37,6 @@ class BaseModel {
|
||||
throw new Error('Must be overriden');
|
||||
}
|
||||
|
||||
static trackChanges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return false;
|
||||
}
|
||||
@ -106,7 +102,6 @@ class BaseModel {
|
||||
} else {
|
||||
options = Object.assign({}, options);
|
||||
}
|
||||
if (!('trackChanges' in options)) options.trackChanges = true;
|
||||
if (!('trackDeleted' in options)) options.trackDeleted = null;
|
||||
if (!('isNew' in options)) options.isNew = 'auto';
|
||||
if (!('autoTimestamp' in options)) options.autoTimestamp = true;
|
||||
@ -321,6 +316,7 @@ BaseModel.MODEL_TYPE_NOTE = 1;
|
||||
BaseModel.MODEL_TYPE_FOLDER = 2;
|
||||
BaseModel.MODEL_TYPE_SETTING = 3;
|
||||
BaseModel.MODEL_TYPE_RESOURCE = 4;
|
||||
BaseModel.MODEL_TYPE_TAG = 5;
|
||||
BaseModel.tableInfo_ = null;
|
||||
BaseModel.tableKeys_ = null;
|
||||
BaseModel.db_ = null;
|
||||
|
@ -60,7 +60,8 @@ CREATE TABLE tags (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL DEFAULT "",
|
||||
created_time INT NOT NULL,
|
||||
updated_time INT NOT NULL
|
||||
updated_time INT NOT NULL,
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE note_tags (
|
||||
@ -79,14 +80,6 @@ CREATE TABLE resources (
|
||||
sync_time INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE changes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
\`type\` INT,
|
||||
item_id TEXT,
|
||||
item_type INT,
|
||||
item_field TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE settings (
|
||||
\`key\` TEXT PRIMARY KEY,
|
||||
\`value\` TEXT,
|
||||
|
@ -32,6 +32,7 @@ class BaseItem extends BaseModel {
|
||||
if (Number(item) === BaseModel.MODEL_TYPE_NOTE) return this.getClass('Note');
|
||||
if (Number(item) === BaseModel.MODEL_TYPE_FOLDER) return this.getClass('Folder');
|
||||
if (Number(item) === BaseModel.MODEL_TYPE_RESOURCE) return this.getClass('Resource');
|
||||
if (Number(item) === BaseModel.MODEL_TYPE_TAG) return this.getClass('Tag');
|
||||
throw new Error('Unknown type: ' + item);
|
||||
}
|
||||
}
|
||||
@ -40,7 +41,9 @@ class BaseItem extends BaseModel {
|
||||
static async syncedItems() {
|
||||
let folders = await this.getClass('Folder').modelSelectAll('SELECT id FROM folders WHERE sync_time > 0');
|
||||
let notes = await this.getClass('Note').modelSelectAll('SELECT id FROM notes WHERE is_conflict = 0 AND sync_time > 0');
|
||||
return folders.concat(notes);
|
||||
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');
|
||||
return folders.concat(notes).concat(resources).concat(tags);
|
||||
}
|
||||
|
||||
static pathToId(path) {
|
||||
@ -52,11 +55,13 @@ class BaseItem extends BaseModel {
|
||||
return this.loadItemById(this.pathToId(path));
|
||||
}
|
||||
|
||||
static loadItemById(id) {
|
||||
return this.getClass('Note').load(id).then((item) => {
|
||||
static async loadItemById(id) {
|
||||
let classes = ['Note', 'Folder', 'Resource', 'Tag'];
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
let item = await this.getClass(classes[i]).load(id);
|
||||
if (item) return item;
|
||||
return this.getClass('Folder').load(id);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static loadItemByField(itemType, field, value) {
|
||||
@ -86,7 +91,7 @@ class BaseItem extends BaseModel {
|
||||
}
|
||||
|
||||
static unserialize_format(type, propName, propValue) {
|
||||
if (propName == 'type_') return propValue;
|
||||
if (propName[propName.length - 1] == '_') return propValue; // Private property
|
||||
|
||||
let ItemClass = this.itemClass(type);
|
||||
|
||||
@ -110,9 +115,17 @@ class BaseItem extends BaseModel {
|
||||
output.push(type == 'note' ? item.body : '');
|
||||
output.push('');
|
||||
for (let i = 0; i < shownKeys.length; i++) {
|
||||
let v = item[shownKeys[i]];
|
||||
v = this.serialize_format(shownKeys[i], v);
|
||||
output.push(shownKeys[i] + ': ' + v);
|
||||
let key = shownKeys[i];
|
||||
let value = null;
|
||||
if (typeof key === 'function') {
|
||||
let r = await key();
|
||||
key = r.key;
|
||||
value = r.value;
|
||||
} else {
|
||||
value = this.serialize_format(key, item[key]);
|
||||
}
|
||||
|
||||
output.push(key + ': ' + value);
|
||||
}
|
||||
|
||||
return output.join("\n");
|
||||
@ -170,6 +183,9 @@ class BaseItem extends BaseModel {
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit);
|
||||
if (items.length) return { hasMore: true, items: items };
|
||||
|
||||
items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
|
||||
return { hasMore: items.length >= limit, items: items };
|
||||
}
|
||||
|
||||
|
@ -1,101 +0,0 @@
|
||||
import { BaseModel } from 'lib/base-model.js';
|
||||
import { Log } from 'lib/log.js';
|
||||
|
||||
class Change extends BaseModel {
|
||||
|
||||
static tableName() {
|
||||
return 'changes';
|
||||
}
|
||||
|
||||
static newChange() {
|
||||
return {
|
||||
id: null,
|
||||
type: null,
|
||||
item_id: null,
|
||||
item_type: null,
|
||||
item_field: null,
|
||||
};
|
||||
}
|
||||
|
||||
static all() {
|
||||
return this.db().selectAll('SELECT * FROM changes');
|
||||
}
|
||||
|
||||
static deleteMultiple(ids) {
|
||||
if (ids.length == 0) return Promise.resolve();
|
||||
|
||||
let queries = [];
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
queries.push(['DELETE FROM changes WHERE id = ?', [ids[i]]]);
|
||||
}
|
||||
|
||||
return this.db().transactionExecBatch(queries);
|
||||
}
|
||||
|
||||
static mergeChanges(changes) {
|
||||
let createdItems = [];
|
||||
let deletedItems = [];
|
||||
let itemChanges = {};
|
||||
|
||||
for (let i = 0; i < changes.length; i++) {
|
||||
let change = changes[i];
|
||||
let mergedChange = null;
|
||||
|
||||
if (itemChanges[change.item_id]) {
|
||||
mergedChange = itemChanges[change.item_id];
|
||||
} else {
|
||||
mergedChange = {
|
||||
item_id: change.item_id,
|
||||
item_type: change.item_type,
|
||||
fields: [],
|
||||
ids: [],
|
||||
type: change.type,
|
||||
}
|
||||
}
|
||||
|
||||
if (change.type == this.TYPE_CREATE) {
|
||||
createdItems.push(change.item_id);
|
||||
} else if (change.type == this.TYPE_DELETE) {
|
||||
deletedItems.push(change.item_id);
|
||||
} else if (change.type == this.TYPE_UPDATE) {
|
||||
if (mergedChange.fields.indexOf(change.item_field) < 0) {
|
||||
mergedChange.fields.push(change.item_field);
|
||||
}
|
||||
}
|
||||
|
||||
mergedChange.ids.push(change.id);
|
||||
|
||||
itemChanges[change.item_id] = mergedChange;
|
||||
}
|
||||
|
||||
let output = [];
|
||||
|
||||
for (let itemId in itemChanges) {
|
||||
if (!itemChanges.hasOwnProperty(itemId)) continue;
|
||||
let change = itemChanges[itemId];
|
||||
|
||||
if (createdItems.indexOf(itemId) >= 0 && deletedItems.indexOf(itemId) >= 0) {
|
||||
// Item both created then deleted - skip
|
||||
change.type = this.TYPE_NOOP;
|
||||
} else if (deletedItems.indexOf(itemId) >= 0) {
|
||||
// Item was deleted at some point - just return one 'delete' event
|
||||
change.type = this.TYPE_DELETE;
|
||||
} else if (createdItems.indexOf(itemId) >= 0) {
|
||||
// Item was created then updated - just return one 'create' event with the latest changes
|
||||
change.type = this.TYPE_CREATE;
|
||||
}
|
||||
|
||||
output.push(change);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Change.TYPE_NOOP = 0;
|
||||
Change.TYPE_CREATE = 1;
|
||||
Change.TYPE_UPDATE = 2;
|
||||
Change.TYPE_DELETE = 3;
|
||||
|
||||
export { Change };
|
@ -25,10 +25,6 @@ class Folder extends BaseItem {
|
||||
return BaseModel.MODEL_TYPE_FOLDER;
|
||||
}
|
||||
|
||||
static trackChanges() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return true;
|
||||
}
|
||||
|
@ -24,10 +24,6 @@ class Note extends BaseItem {
|
||||
return BaseModel.MODEL_TYPE_NOTE;
|
||||
}
|
||||
|
||||
static trackChanges() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static trackDeleted() {
|
||||
return true;
|
||||
}
|
||||
|
@ -16,17 +16,31 @@ class Tag extends BaseItem {
|
||||
static async serialize(item, type = null, shownKeys = null) {
|
||||
let fieldNames = this.fieldNames();
|
||||
fieldNames.push('type_');
|
||||
fieldNames.push(() => {
|
||||
|
||||
fieldNames.push(async () => {
|
||||
let noteIds = await this.tagNoteIds(item.id);
|
||||
console.info('NOTE IDS', noteIds);
|
||||
return {
|
||||
key: 'notes_',
|
||||
value: noteIds.join(','),
|
||||
};
|
||||
});
|
||||
lodash.pull(fieldNames, 'sync_time');
|
||||
return super.serialize(item, 'tag', fieldNames);
|
||||
}
|
||||
|
||||
static tagNoteIds(tagId) {
|
||||
return this.db().selectAll('SELECT note_id FROM note_tags WHERE tag_id = ?', [tagId]);
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO: in order for a sync to happen, the updated_time property should somehow be changed
|
||||
// whenever an tag is applied or removed from an item. Either the updated_time property
|
||||
// is changed here or by the caller?
|
||||
|
||||
static async addNote(tagId, noteId) {
|
||||
let hasIt = await this.hasNote(tagId, noteId);
|
||||
if (hasIt) return;
|
||||
@ -35,7 +49,30 @@ class Tag extends BaseItem {
|
||||
tag_id: tagId,
|
||||
note_id: noteId,
|
||||
});
|
||||
return this.db().exec(query);
|
||||
|
||||
await this.db().exec(query);
|
||||
//await this.save({ id: tagId, updated_time: time.unixMs() }); //type_: BaseModel.MODEL_TYPE_TAG
|
||||
}
|
||||
|
||||
static async addNotes(tagId, noteIds) {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await this.addNote(tagId, noteIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: updated_time must not change since this is only called from
|
||||
// the synchronizer, which manages and sets the correct updated_time
|
||||
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) {
|
||||
|
@ -331,6 +331,11 @@ class Synchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
if (newContent.type_ == BaseModel.MODEL_TYPE_TAG) {
|
||||
let noteIds = newContent.notes_.split(',');
|
||||
await ItemClass.setAssociatedNotes(newContent.id, noteIds);
|
||||
}
|
||||
|
||||
this.logSyncOperation(action, local, content, reason);
|
||||
} else {
|
||||
this.logSyncOperation(action, local, remote, reason);
|
||||
|
Loading…
x
Reference in New Issue
Block a user