1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
This commit is contained in:
Laurent Cozic 2017-07-03 20:50:45 +01:00
parent b36905cb3c
commit c28323bfab
12 changed files with 63 additions and 90 deletions

View File

@ -9,4 +9,5 @@ tests/fuzzing/client0
tests/fuzzing/client1
tests/fuzzing/client2
tests/fuzzing/sync
tests/fuzzing.*
tests/fuzzing.*
tests/fuzzing -*

View File

@ -202,10 +202,10 @@ commands.push({
if (pattern.indexOf('*') < 0) { // Handle it as a simple title
if (pattern.substr(0, 3) == '../') {
itemType = BaseModel.MODEL_TYPE_FOLDER;
itemType = BaseModel.TYPE_FOLDER;
pattern = pattern.substr(3);
} else {
itemType = BaseModel.MODEL_TYPE_NOTE;
itemType = BaseModel.TYPE_NOTE;
}
let item = await BaseItem.loadItemByField(itemType, 'title', pattern);
@ -272,9 +272,9 @@ commands.push({
action: async function(args, end) {
try {
let tag = null;
if (args.tag) tag = await loadItem(BaseModel.MODEL_TYPE_TAG, args.tag);
if (args.tag) tag = await loadItem(BaseModel.TYPE_TAG, args.tag);
let note = null;
if (args.note) note = await loadItem(BaseModel.MODEL_TYPE_NOTE, args.note);
if (args.note) note = await loadItem(BaseModel.TYPE_NOTE, args.note);
if (args.command == 'remove' && !tag) throw new Error(_('Tag does not exist: "%s"', args.tag));

View File

@ -25,7 +25,7 @@ describe('BaseItem', function() {
await Folder.delete(folder1.id);
let items = await BaseModel.deletedItems();
let items = await BaseItem.deletedItems();
expect(items.length).toBe(1);
expect(items[0].item_id).toBe(folder1.id);

View File

@ -38,7 +38,7 @@ async function localItemsSameAsRemote(locals, expect) {
expect(remote.updated_time).toBe(dbItem.updated_time);
let remoteContent = await fileApi().get(path);
remoteContent = dbItem.type_ == BaseModel.MODEL_TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
remoteContent = dbItem.type_ == BaseModel.TYPE_NOTE ? await Note.unserialize(remoteContent) : await Folder.unserialize(remoteContent);
expect(remoteContent.title).toBe(dbItem.title);
}
} catch (error) {
@ -238,7 +238,7 @@ describe('Synchronizer', function() {
expect(files.length).toBe(1);
expect(files[0].path).toBe(Folder.systemPath(folder1));
let deletedItems = await BaseModel.deletedItems();
let deletedItems = await BaseItem.deletedItems();
expect(deletedItems.length).toBe(0);
done();
@ -267,7 +267,7 @@ describe('Synchronizer', function() {
expect(items.length).toBe(1);
let deletedItems = await BaseModel.deletedItems();
let deletedItems = await BaseItem.deletedItems();
expect(deletedItems.length).toBe(0);

View File

@ -16,7 +16,7 @@ class BaseModel {
return output;
} else {
model = Object.assign({}, model);
model.type_ = this.itemType();
model.type_ = this.modelType();
return model;
}
}
@ -33,14 +33,10 @@ class BaseModel {
return false;
}
static itemType() {
static modelType() {
throw new Error('Must be overriden');
}
static trackDeleted() {
return false;
}
static byId(items, id) {
for (let i = 0; i < items.length; i++) {
if (items[i].id == id) return items[i];
@ -69,13 +65,6 @@ class BaseModel {
return this.db().tableFields(this.tableName());
}
static identifyItemType(item) {
if (!item) throw new Error('Cannot identify undefined item');
if ('body' in item || ('parent_id' in item && !!item.parent_id)) return BaseModel.MODEL_TYPE_NOTE;
if ('sync_time' in item) return BaseModel.MODEL_TYPE_FOLDER;
throw new Error('Cannot identify item: ' + JSON.stringify(item));
}
static new() {
let fields = this.fields();
let output = {};
@ -86,26 +75,14 @@ class BaseModel {
return output;
}
static fromApiResult(apiResult) {
let fieldNames = this.fieldNames();
let output = {};
for (let i = 0; i < fieldNames.length; i++) {
let f = fieldNames[i];
output[f] = f in apiResult ? apiResult[f] : null;
}
return output;
}
static modOptions(options) {
if (!options) {
options = {};
} else {
options = Object.assign({}, options);
}
if (!('trackDeleted' in options)) options.trackDeleted = null;
if (!('isNew' in options)) options.isNew = 'auto';
if (!('autoTimestamp' in options)) options.autoTimestamp = true;
if (!('transactionNextQueries' in options)) options.transactionNextQueries = [];
return options;
}
@ -179,15 +156,6 @@ class BaseModel {
return this.modelSelectOne('SELECT * FROM `' + this.tableName() + '` WHERE `title` = ?', [fieldValue]);
}
static applyPatch(model, patch) {
model = Object.assign({}, model);
for (let n in patch) {
if (!patch.hasOwnProperty(n)) continue;
model[n] = patch[n];
}
return model;
}
static diffObjects(oldModel, newModel) {
let output = {};
let type = null;
@ -256,10 +224,6 @@ class BaseModel {
queries.push(saveQuery);
for (let i = 0; i < options.transactionNextQueries.length; i++) {
queries.push(options.transactionNextQueries[i]);
}
return this.db().transactionExecBatch(queries).then(() => {
o = Object.assign({}, o);
o.id = modelId;
@ -280,14 +244,6 @@ class BaseModel {
return !object.id;
}
static deletedItems() {
return this.db().selectAll('SELECT * FROM deleted_items');
}
static remoteDeletedItem(itemId) {
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ?', [itemId]);
}
static filterArray(models) {
let output = [];
for (let i = 0; i < models.length; i++) {
@ -312,16 +268,8 @@ class BaseModel {
static delete(id, options = null) {
options = this.modOptions(options);
if (!id) throw new Error('Cannot delete object without an ID');
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
let trackDeleted = this.trackDeleted();
if (options.trackDeleted !== null) trackDeleted = options.trackDeleted;
if (trackDeleted) {
return this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.itemType(), id, time.unixMs()]);
}
});
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]);
}
static db() {
@ -331,11 +279,11 @@ class BaseModel {
}
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.TYPE_NOTE = 1;
BaseModel.TYPE_FOLDER = 2;
BaseModel.TYPE_SETTING = 3;
BaseModel.TYPE_RESOURCE = 4;
BaseModel.TYPE_TAG = 5;
BaseModel.tableInfo_ = null;
BaseModel.tableKeys_ = null;
BaseModel.db_ = null;

View File

@ -9,6 +9,10 @@ class BaseItem extends BaseModel {
return true;
}
static trackDeleted() {
return false;
}
// Need to dynamically load the classes like this to avoid circular dependencies
static getClass(name) {
if (!this.classes_) this.classes_ = {};
@ -29,10 +33,10 @@ class BaseItem extends BaseModel {
if (!('type_' in item)) throw new Error('Item does not have a type_ property');
return this.itemClass(item.type_);
} else {
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');
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');
throw new Error('Unknown type: ' + item);
}
}
@ -79,6 +83,25 @@ class BaseItem extends BaseModel {
return ItemClass.delete(id);
}
static async delete(id, options = null) {
let trackDeleted = this.trackDeleted();
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
await super.delete(id, options);
if (trackDeleted) {
await this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.modelType(), id, time.unixMs()]);
}
}
static deletedItems() {
return this.db().selectAll('SELECT * FROM deleted_items');
}
static remoteDeletedItem(itemId) {
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ?', [itemId]);
}
static serialize_format(propName, propValue) {
if (['created_time', 'updated_time'].indexOf(propName) >= 0) {
if (!propValue) return '';
@ -145,6 +168,7 @@ class BaseItem extends BaseModel {
if (line == '') {
state = 'readingBody';
continue;
}
let p = line.indexOf(':');
@ -165,7 +189,7 @@ class BaseItem extends BaseModel {
if (!output.type_) throw new Error('Missing required property: type_: ' + content);
output.type_ = Number(output.type_);
if (output.type_ == BaseModel.MODEL_TYPE_NOTE) output.body = body.join("\n");
if (output.type_ == BaseModel.TYPE_NOTE) output.body = body.join("\n");
for (let n in output) {
if (!output.hasOwnProperty(n)) continue;

View File

@ -21,8 +21,8 @@ class Folder extends BaseItem {
return super.serialize(folder, 'folder', fieldNames);
}
static itemType() {
return BaseModel.MODEL_TYPE_FOLDER;
static modelType() {
return BaseModel.TYPE_FOLDER;
}
static trackDeleted() {

View File

@ -20,8 +20,8 @@ class Note extends BaseItem {
return super.serialize(note, 'note', fieldNames);
}
static itemType() {
return BaseModel.MODEL_TYPE_NOTE;
static modelType() {
return BaseModel.TYPE_NOTE;
}
static trackDeleted() {

View File

@ -11,8 +11,8 @@ class Resource extends BaseItem {
return 'resources';
}
static itemType() {
return BaseModel.MODEL_TYPE_RESOURCE;
static modelType() {
return BaseModel.TYPE_RESOURCE;
}
static async serialize(item, type = null, shownKeys = null) {

View File

@ -7,8 +7,8 @@ class Setting extends BaseModel {
return 'settings';
}
static itemType() {
return BaseModel.MODEL_TYPE_SETTING;
static modelType() {
return BaseModel.TYPE_SETTING;
}
static defaultSetting(key) {

View File

@ -11,8 +11,8 @@ class Tag extends BaseItem {
return 'tags';
}
static itemType() {
return BaseModel.MODEL_TYPE_TAG;
static modelType() {
return BaseModel.TYPE_TAG;
}
static async serialize(item, type = null, shownKeys = null) {

View File

@ -156,7 +156,7 @@ class Synchronizer {
reason = 'remote does not exist, and local is new and has never been synced';
} else {
// Note or item was modified after having been deleted remotely
action = local.type_ == BaseModel.MODEL_TYPE_NOTE ? 'noteConflict' : 'itemConflict';
action = local.type_ == BaseModel.TYPE_NOTE ? 'noteConflict' : 'itemConflict';
reason = 'remote has been deleted, but local has changes';
}
} else {
@ -164,7 +164,7 @@ class Synchronizer {
// Since, in this loop, we are only dealing with notes that require sync, if the
// remote has been modified after the sync time, it means both notes have been
// modified and so there's a conflict.
action = local.type_ == BaseModel.MODEL_TYPE_NOTE ? 'noteConflict' : 'itemConflict';
action = local.type_ == BaseModel.TYPE_NOTE ? 'noteConflict' : 'itemConflict';
reason = 'both remote and local have changes';
} else {
action = 'updateRemote';
@ -175,7 +175,7 @@ class Synchronizer {
this.logSyncOperation(action, local, remote, reason);
if (local.type_ == BaseModel.MODEL_TYPE_RESOURCE && (action == 'createRemote' || (action == 'itemConflict' && remote))) {
if (local.type_ == BaseModel.TYPE_RESOURCE && (action == 'createRemote' || (action == 'itemConflict' && remote))) {
let remoteContentPath = this.resourceDirName_ + '/' + local.id;
let resourceContent = await Resource.content(local);
await this.api().put(remoteContentPath, resourceContent);
@ -244,7 +244,7 @@ class Synchronizer {
// Delete the remote items that have been deleted locally.
// ------------------------------------------------------------------------
let deletedItems = await BaseModel.deletedItems();
let deletedItems = await BaseItem.deletedItems();
report.remotesToDelete = deletedItems.length;
options.onProgress(report);
for (let i = 0; i < deletedItems.length; i++) {
@ -253,7 +253,7 @@ class Synchronizer {
this.logSyncOperation('deleteRemote', null, { id: item.item_id }, 'local has been deleted');
await this.api().delete(path);
if (this.randomFailure(options, 2)) return;
await BaseModel.remoteDeletedItem(item.item_id);
await BaseItem.remoteDeletedItem(item.item_id);
report['deleteRemote']++;
options.onProgress(report);
@ -315,7 +315,7 @@ class Synchronizer {
};
if (action == 'createLocal') options.isNew = true;
if (newContent.type_ == BaseModel.MODEL_TYPE_RESOURCE && action == 'createLocal') {
if (newContent.type_ == BaseModel.TYPE_RESOURCE && action == 'createLocal') {
let localResourceContentPath = Resource.fullPath(newContent);
let remoteResourceContentPath = this.resourceDirName_ + '/' + newContent.id;
let remoteResourceContent = await this.api().get(remoteResourceContentPath, { encoding: 'binary' });