1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-27 10:32:58 +02:00

Adding service to keep track of note resources associations

This commit is contained in:
Laurent Cozic 2018-03-12 23:40:43 +00:00
parent eef106c99b
commit f595be07d4
9 changed files with 174 additions and 109 deletions

View File

@ -366,7 +366,7 @@ class NoteTextComponent extends React.Component {
webviewReady: true,
});
if (Setting.value('env') === 'dev') this.webview_.openDevTools();
// if (Setting.value('env') === 'dev') this.webview_.openDevTools();
}
webview_ref(element) {

View File

@ -122,15 +122,6 @@ class BaseModel {
return id.substr(0, 5);
}
// static minimalPartialId(id) {
// let length = 2;
// while (true) {
// const partialId = id.substr(0, length);
// const r = await this.db().selectOne('SELECT count(*) as total FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
// if (r['total'] <= 1) return partialId;
// }
// }
static loadByPartialId(partialId) {
return this.modelSelectAll('SELECT * FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']);
}
@ -222,20 +213,6 @@ class BaseModel {
}
if ('type_' in newModel) output.type_ = newModel.type_;
return output;
// let output = {};
// let type = null;
// for (let n in newModel) {
// if (!newModel.hasOwnProperty(n)) continue;
// if (n == 'type_') {
// type = newModel[n];
// continue;
// }
// if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
// output[n] = newModel[n];
// }
// }
// if (type !== null) output.type_ = type;
// return output;
}
static diffObjectsFields(oldModel, newModel) {
@ -505,6 +482,8 @@ BaseModel.typeEnum_ = [
['TYPE_SEARCH', 7],
['TYPE_ALARM', 8],
['TYPE_MASTER_KEY', 9],
['TYPE_ITEM_CHANGE', 10],
['TYPE_NOTE_RESOURCE', 11],
];
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
@ -512,16 +491,6 @@ for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
BaseModel[e[0]] = e[1];
}
// BaseModel.TYPE_NOTE = 1;
// BaseModel.TYPE_FOLDER = 2;
// BaseModel.TYPE_SETTING = 3;
// BaseModel.TYPE_RESOURCE = 4;
// BaseModel.TYPE_TAG = 5;
// BaseModel.TYPE_NOTE_TAG = 6;
// BaseModel.TYPE_SEARCH = 7;
// BaseModel.TYPE_ALARM = 8;
// BaseModel.TYPE_MASTER_KEY = 9;
BaseModel.db_ = null;
BaseModel.dispatch = function(o) {};
BaseModel.saveMutexes_ = {};

View File

@ -123,19 +123,6 @@ class Database {
break;
}
}
// return new Promise((resolve, reject) => {
// let iid = setInterval(() => {
// if (!this.inTransaction_) {
// clearInterval(iid);
// this.transactionExecBatch(queries).then(() => {
// resolve();
// }).catch((error) => {
// reject(error);
// });
// }
// }, 100);
// });
}
this.inTransaction_ = true;
@ -149,56 +136,6 @@ class Database {
}
this.inTransaction_ = false;
// return promiseChain(chain).then(() => {
// this.inTransaction_ = false;
// });
// if (queries.length <= 0) return Promise.resolve();
// if (queries.length == 1) {
// let q = this.wrapQuery(queries[0]);
// return this.exec(q.sql, q.params);
// }
// // There can be only one transaction running at a time so queue
// // any new transaction here.
// if (this.inTransaction_) {
// return new Promise((resolve, reject) => {
// let iid = setInterval(() => {
// if (!this.inTransaction_) {
// clearInterval(iid);
// this.transactionExecBatch(queries).then(() => {
// resolve();
// }).catch((error) => {
// reject(error);
// });
// }
// }, 100);
// });
// }
// this.inTransaction_ = true;
// queries.splice(0, 0, 'BEGIN TRANSACTION');
// queries.push('COMMIT'); // Note: ROLLBACK is currently not supported
// let chain = [];
// for (let i = 0; i < queries.length; i++) {
// let query = this.wrapQuery(queries[i]);
// chain.push(() => {
// return this.exec(query.sql, query.params);
// });
// }
// return promiseChain(chain).then(() => {
// this.inTransaction_ = false;
// });
}
static enumId(type, s) {

View File

@ -202,7 +202,7 @@ class JoplinDatabase extends Database {
// default value and thus might cause problems. In that case, the default value
// must be set in the synchronizer too.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@ -298,6 +298,37 @@ class JoplinDatabase extends Database {
queries.push('ALTER TABLE resources ADD COLUMN encryption_blob_encrypted INT NOT NULL DEFAULT 0');
}
if (targetVersion == 10) {
const itemChangesTable = `
CREATE TABLE item_changes (
id INTEGER PRIMARY KEY,
item_type INT NOT NULL,
item_id TEXT NOT NULL,
type INT NOT NULL,
created_time INT NOT NULL
);
`;
const noteResourcesTable = `
CREATE TABLE note_resources (
id INTEGER PRIMARY KEY,
note_id TEXT NOT NULL,
resource_id TEXT NOT NULL
);
`;
queries.push(this.sqlStringToLines(itemChangesTable)[0]);
queries.push('CREATE INDEX item_changes_item_id ON item_changes (item_id)');
queries.push('CREATE INDEX item_changes_created_time ON item_changes (created_time)');
queries.push('CREATE INDEX item_changes_item_type ON item_changes (item_type)');
queries.push(this.sqlStringToLines(noteResourcesTable)[0]);
queries.push('CREATE INDEX note_resources_note_id ON note_resources (note_id)');
queries.push('CREATE INDEX note_resources_resource_id ON note_resources (resource_id)');
queries.push({ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) SELECT 1, id, 1, ? FROM notes', params: [Date.now()] });
}
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
await this.transactionExecBatch(queries);

View File

@ -0,0 +1,35 @@
const BaseModel = require('lib/BaseModel.js');
const Mutex = require('async-mutex').Mutex;
class ItemChange extends BaseModel {
static tableName() {
return 'item_changes';
}
static modelType() {
return BaseModel.TYPE_ITEM_CHANGE;
}
static async add(itemType, itemId, type) {
const release = await ItemChange.addChangeMutex_.acquire();
try {
await this.db().transactionExecBatch([
{ sql: 'DELETE FROM item_changes WHERE item_id = ?', params: [itemId] },
{ sql: 'INSERT INTO item_changes (item_type, item_id, type, created_time) VALUES (?, ?, ?, ?)', params: [itemType, itemId, type, Date.now()] },
]);
} finally {
release();
}
}
}
ItemChange.addChangeMutex_ = new Mutex();
ItemChange.TYPE_CREATE = 1;
ItemChange.TYPE_UPDATE = 2;
ItemChange.TYPE_DELETE = 3;
module.exports = ItemChange;

View File

@ -2,6 +2,7 @@ const BaseModel = require('lib/BaseModel.js');
const { Log } = require('lib/log.js');
const { sprintf } = require('sprintf-js');
const BaseItem = require('lib/models/BaseItem.js');
const ItemChange = require('lib/models/ItemChange.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const { time } = require('lib/time-utils.js');
@ -398,6 +399,8 @@ class Note extends BaseItem {
const note = await super.save(o, options);
ItemChange.add(BaseModel.TYPE_NOTE, note.id, isNew ? ItemChange.TYPE_CREATE : ItemChange.TYPE_UPDATE);
this.dispatch({
type: 'NOTE_UPDATE_ONE',
note: note,
@ -413,18 +416,22 @@ class Note extends BaseItem {
return note;
}
static async delete(id, options = null) {
let r = await super.delete(id, options);
// Not used?
this.dispatch({
type: 'NOTE_DELETE',
id: id,
});
}
// static async delete(id, options = null) {
// let r = await super.delete(id, options);
// this.dispatch({
// type: 'NOTE_DELETE',
// id: id,
// });
// }
static batchDelete(ids, options = null) {
const result = super.batchDelete(ids, options);
for (let i = 0; i < ids.length; i++) {
ItemChange.add(BaseModel.TYPE_NOTE, ids[i], ItemChange.TYPE_DELETE);
this.dispatch({
type: 'NOTE_DELETE',
id: ids[i],

View File

@ -0,0 +1,32 @@
const BaseModel = require('lib/BaseModel.js');
class NoteResource extends BaseModel {
static tableName() {
return 'note_resources';
}
static modelType() {
return BaseModel.TYPE_NOTE_RESOURCE;
}
static async associate(noteId, resourceIds) {
let queries = [];
queries.push({ sql: 'DELETE FROM note_resources WHERE note_id = ?', params: [noteId] });
for (let i = 0; i < resourceIds.length; i++) {
queries.push({ sql: 'INSERT INTO note_resources (note_id, resource_id) VALUES (?, ?)', params: [noteId, resourceIds[i]] });
}
await this.db().transactionExecBatch(queries);
}
static async remove(noteId) {
let queries = [];
queries.push({ sql: 'DELETE FROM note_resources WHERE note_id = ?', params: [noteId] });
await this.db().transactionExecBatch(queries);
}
}
module.exports = NoteResource;

View File

@ -59,10 +59,10 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
reg.logger().info('Scheduling sync operation...');
// if (Setting.value("env") === "dev" && delay !== 0) {
// reg.logger().info("Schedule sync DISABLED!!!");
// return;
// }
if (Setting.value("env") === "dev" && delay !== 0) {
reg.logger().info("Schedule sync DISABLED!!!");
return;
}
const timeoutCallback = async () => {
reg.scheduleSyncId_ = null;

View File

@ -0,0 +1,54 @@
const ItemChange = require('lib/models/ItemChange');
const NoteResource = require('lib/models/NoteResource');
const Note = require('lib/models/Note');
const BaseModel = require('lib/BaseModel');
class ResourceService {
async indexNoteResources() {
let lastId = 0;
let lastCreatedTime = 0
while (true) {
const changes = await ItemChange.modelSelectAll(`
SELECT id, item_id, type, created_time
FROM item_changes
WHERE item_type = ?
AND id > ?
AND created_time >= ?
ORDER BY id, created_time ASC
LIMIT 10
`, [BaseModel.TYPE_NOTE, lastId, lastCreatedTime]);
if (!changes.length) break;
const noteIds = changes.map(a => a.item_id);
const changesByNoteId = {};
for (let i = 0; i < changes.length; i++) {
changesByNoteId[changes[i].item_id] = changes[i];
}
const notes = await Note.modelSelectAll('SELECT id, title, body FROM notes WHERE id IN ("' + noteIds.join('","') + '")');
for (let i = 0; i < notes.length; i++) {
const note = notes[i];
const change = changesByNoteId[note.id];
if (change.type === ItemChange.TYPE_CREATE || change.type === ItemChange.TYPE_UPDATE) {
const resourceIds = Note.linkedResourceIds(note.body);
await NoteResource.associate(note.id, resourceIds);
} else if (change.type === ItemChange.TYPE_DELETE) {
await NoteResource.remove(note.id);
} else {
throw new Error('Invalid change type: ' + change.type);
}
lastId = change.id;
lastCreatedTime = change.created_time;
}
}
}
}
module.exports = ResourceService;