You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +02:00
Improved enex import
This commit is contained in:
@@ -13,25 +13,6 @@ const Promise = require('promise');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const stringToStream = require('string-to-stream')
|
const stringToStream = require('string-to-stream')
|
||||||
|
|
||||||
let existingTimestamps = [];
|
|
||||||
|
|
||||||
function uniqueCreatedTimestamp(timestamp) {
|
|
||||||
if (existingTimestamps.indexOf(timestamp) < 0) {
|
|
||||||
existingTimestamps.push(timestamp);
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i <= 999; i++) {
|
|
||||||
let t = timestamp + i;
|
|
||||||
if (existingTimestamps.indexOf(t) < 0) {
|
|
||||||
existingTimestamps.push(t);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateToTimestamp(s, zeroIfInvalid = false) {
|
function dateToTimestamp(s, zeroIfInvalid = false) {
|
||||||
let m = moment(s, 'YYYYMMDDTHHmmssZ');
|
let m = moment(s, 'YYYYMMDDTHHmmssZ');
|
||||||
if (!m.isValid()) {
|
if (!m.isValid()) {
|
||||||
@@ -69,7 +50,7 @@ function createNoteId(note) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fuzzyMatch(note) {
|
async function fuzzyMatch(note) {
|
||||||
let notes = await Note.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0 AND created_time = ?', note.created_time);
|
let notes = await Note.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0 AND created_time = ?', [note.created_time]);
|
||||||
if (!notes.length) return null;
|
if (!notes.length) return null;
|
||||||
if (notes.length === 1) return notes[0];
|
if (notes.length === 1) return notes[0];
|
||||||
|
|
||||||
@@ -92,6 +73,7 @@ async function saveNoteToStorage(note, fuzzyMatching = false) {
|
|||||||
let result = {
|
let result = {
|
||||||
noteCreated: false,
|
noteCreated: false,
|
||||||
noteUpdated: false,
|
noteUpdated: false,
|
||||||
|
noteSkipped: false,
|
||||||
resourcesCreated: 0,
|
resourcesCreated: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,14 +85,16 @@ async function saveNoteToStorage(note, fuzzyMatching = false) {
|
|||||||
|
|
||||||
// TODO: also save resources
|
// TODO: also save resources
|
||||||
|
|
||||||
if (!Object.getOwnPropertyNames(diff).length) return;
|
if (!Object.getOwnPropertyNames(diff).length) {
|
||||||
|
result.noteSkipped = true;
|
||||||
|
// TODO: also save resources
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
diff.id = existingNote.id;
|
diff.id = existingNote.id;
|
||||||
diff.type_ = existingNote.type_;
|
diff.type_ = existingNote.type_;
|
||||||
return Note.save(diff, { autoTimestamp: false }).then(() => {
|
await Note.save(diff, { autoTimestamp: false })
|
||||||
result.noteUpdated = true;
|
result.noteUpdated = true;
|
||||||
return result;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < note.resources.length; i++) {
|
for (let i = 0; i < note.resources.length; i++) {
|
||||||
let resource = note.resources[i];
|
let resource = note.resources[i];
|
||||||
@@ -128,14 +112,14 @@ async function saveNoteToStorage(note, fuzzyMatching = false) {
|
|||||||
result.resourcesCreated++;
|
result.resourcesCreated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Note.save(note, {
|
await Note.save(note, {
|
||||||
isNew: true,
|
isNew: true,
|
||||||
autoTimestamp: false,
|
autoTimestamp: false,
|
||||||
}).then(() => {
|
|
||||||
result.noteCreated = true;
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
result.noteCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importEnex(parentFolderId, filePath, importOptions = null) {
|
function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||||
@@ -144,11 +128,35 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
|||||||
if (!('onProgress' in importOptions)) importOptions.onProgress = function(state) {};
|
if (!('onProgress' in importOptions)) importOptions.onProgress = function(state) {};
|
||||||
if (!('onError' in importOptions)) importOptions.onError = function(error) {};
|
if (!('onError' in importOptions)) importOptions.onError = function(error) {};
|
||||||
|
|
||||||
|
// Some notes were created with the exact same timestamp, for example when they were
|
||||||
|
// batch imported. In order to make fuzzy matching easier, this function ensures
|
||||||
|
// that each timestamp is unique.
|
||||||
|
let existingTimestamps = [];
|
||||||
|
function uniqueCreatedTimestamp(timestamp) {
|
||||||
|
return timestamp;
|
||||||
|
|
||||||
|
if (existingTimestamps.indexOf(timestamp) < 0) {
|
||||||
|
existingTimestamps.push(timestamp);
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= 999; i++) {
|
||||||
|
let t = timestamp + i;
|
||||||
|
if (existingTimestamps.indexOf(t) < 0) {
|
||||||
|
existingTimestamps.push(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let progressState = {
|
let progressState = {
|
||||||
loaded: 0,
|
loaded: 0,
|
||||||
created: 0,
|
created: 0,
|
||||||
updated: 0,
|
updated: 0,
|
||||||
|
skipped: 0,
|
||||||
resourcesCreated: 0,
|
resourcesCreated: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -182,7 +190,8 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function processNotes() {
|
async function processNotes() {
|
||||||
if (processingNotes) return;
|
if (processingNotes) return false;
|
||||||
|
|
||||||
processingNotes = true;
|
processingNotes = true;
|
||||||
stream.pause();
|
stream.pause();
|
||||||
|
|
||||||
@@ -204,6 +213,8 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
|||||||
progressState.updated++;
|
progressState.updated++;
|
||||||
} else if (result.noteCreated) {
|
} else if (result.noteCreated) {
|
||||||
progressState.created++;
|
progressState.created++;
|
||||||
|
} else if (result.noteSkipped) {
|
||||||
|
progressState.skipped++;
|
||||||
}
|
}
|
||||||
progressState.resourcesCreated += result.resourcesCreated;
|
progressState.resourcesCreated += result.resourcesCreated;
|
||||||
importOptions.onProgress(progressState);
|
importOptions.onProgress(progressState);
|
||||||
@@ -214,6 +225,7 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
|||||||
return promiseChain(chain).then(() => {
|
return promiseChain(chain).then(() => {
|
||||||
stream.resume();
|
stream.resume();
|
||||||
processingNotes = false;
|
processingNotes = false;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,12 +366,12 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
|||||||
saxStream.on('end', function() {
|
saxStream.on('end', function() {
|
||||||
// Wait till there is no more notes to process.
|
// Wait till there is no more notes to process.
|
||||||
let iid = setInterval(() => {
|
let iid = setInterval(() => {
|
||||||
if (notes.length) {
|
processNotes().then((allDone) => {
|
||||||
processNotes();
|
if (allDone) {
|
||||||
} else {
|
clearTimeout(iid);
|
||||||
clearInterval(iid);
|
resolve();
|
||||||
resolve();
|
}
|
||||||
}
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -63,10 +63,11 @@ commands.push({
|
|||||||
aliases: ['mkdir'],
|
aliases: ['mkdir'],
|
||||||
description: 'Creates a new notebook',
|
description: 'Creates a new notebook',
|
||||||
action: function(args, end) {
|
action: function(args, end) {
|
||||||
Folder.save({ title: args['notebook'] }).catch((error) => {
|
Folder.save({ title: args['notebook'] }).then((folder) => {
|
||||||
this.log(error);
|
|
||||||
}).then((folder) => {
|
|
||||||
switchCurrentFolder(folder);
|
switchCurrentFolder(folder);
|
||||||
|
}).catch((error) => {
|
||||||
|
this.log(error);
|
||||||
|
}).then(() => {
|
||||||
end();
|
end();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -187,34 +188,38 @@ commands.push({
|
|||||||
usage: 'rm <pattern>',
|
usage: 'rm <pattern>',
|
||||||
description: 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../<notebook>` to delete a notebook.',
|
description: 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../<notebook>` to delete a notebook.',
|
||||||
action: async function(args, end) {
|
action: async function(args, end) {
|
||||||
let pattern = args['pattern'];
|
try {
|
||||||
let itemType = null;
|
let pattern = args['pattern'];
|
||||||
|
let itemType = null;
|
||||||
|
|
||||||
if (pattern.indexOf('*') < 0) { // Handle it as a simple title
|
if (pattern.indexOf('*') < 0) { // Handle it as a simple title
|
||||||
if (pattern.substr(0, 3) == '../') {
|
if (pattern.substr(0, 3) == '../') {
|
||||||
itemType = BaseModel.MODEL_TYPE_FOLDER;
|
itemType = BaseModel.MODEL_TYPE_FOLDER;
|
||||||
pattern = pattern.substr(3);
|
pattern = pattern.substr(3);
|
||||||
} else {
|
} else {
|
||||||
itemType = BaseModel.MODEL_TYPE_NOTE;
|
itemType = BaseModel.MODEL_TYPE_NOTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = await BaseItem.loadItemByField(itemType, 'title', pattern);
|
let item = await BaseItem.loadItemByField(itemType, 'title', pattern);
|
||||||
if (!item) return cmdError(this, _('No item with title "%s" found.', pattern), end);
|
if (!item) throw new Error(_('No item with title "%s" found.', pattern));
|
||||||
await BaseItem.deleteItem(itemType, item.id);
|
await BaseItem.deleteItem(itemType, item.id);
|
||||||
|
|
||||||
if (currentFolder && currentFolder.id == item.id) {
|
if (currentFolder && currentFolder.id == item.id) {
|
||||||
let f = await Folder.defaultFolder();
|
let f = await Folder.defaultFolder();
|
||||||
switchCurrentFolder(f);
|
switchCurrentFolder(f);
|
||||||
}
|
}
|
||||||
} else { // Handle it as a glob pattern
|
} else { // Handle it as a glob pattern
|
||||||
let notes = await Note.previews(currentFolder.id, { titlePattern: pattern });
|
let notes = await Note.previews(currentFolder.id, { titlePattern: pattern });
|
||||||
if (!notes.length) return cmdError(this, _('No note matches this pattern: "%s"', pattern), end);
|
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
|
||||||
let ok = await cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
|
let ok = await cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
|
||||||
if (ok) {
|
if (ok) {
|
||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
await Note.delete(notes[i].id);
|
await Note.delete(notes[i].id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
end();
|
end();
|
||||||
@@ -226,15 +231,19 @@ commands.push({
|
|||||||
usage: 'mv <pattern> <notebook>',
|
usage: 'mv <pattern> <notebook>',
|
||||||
description: 'Moves the notes matching <pattern> to <notebook>.',
|
description: 'Moves the notes matching <pattern> to <notebook>.',
|
||||||
action: async function(args, end) {
|
action: async function(args, end) {
|
||||||
let pattern = args['pattern'];
|
try {
|
||||||
|
let pattern = args['pattern'];
|
||||||
|
|
||||||
let folder = await Folder.loadByField('title', args['notebook']);
|
let folder = await Folder.loadByField('title', args['notebook']);
|
||||||
if (!folder) return cmdError(this, _('No folder with title "%s"', args['notebook']), end);
|
if (!folder) throw new Error(_('No folder with title "%s"', args['notebook']));
|
||||||
let notes = await Note.previews(currentFolder.id, { titlePattern: pattern });
|
let notes = await Note.previews(currentFolder.id, { titlePattern: pattern });
|
||||||
if (!notes.length) return cmdError(this, _('No note matches this pattern: "%s"', pattern), end);
|
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
await Note.save({ id: notes[i].id, parent_id: folder.id });
|
await Note.save({ id: notes[i].id, parent_id: folder.id });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
end();
|
end();
|
||||||
@@ -252,41 +261,45 @@ commands.push({
|
|||||||
['-t, --type <type>', 'Displays only the items of the specific type(s). Can be `n` for notes, `t` for todos, or `nt` for notes and todos (eg. `-tt` would display only the todos, while `-ttd` would display notes and todos.'],
|
['-t, --type <type>', 'Displays only the items of the specific type(s). Can be `n` for notes, `t` for todos, or `nt` for notes and todos (eg. `-tt` would display only the todos, while `-ttd` would display notes and todos.'],
|
||||||
],
|
],
|
||||||
action: async function(args, end) {
|
action: async function(args, end) {
|
||||||
let pattern = args['pattern'];
|
try {
|
||||||
let suffix = '';
|
let pattern = args['pattern'];
|
||||||
let items = [];
|
let suffix = '';
|
||||||
let options = args.options;
|
let items = [];
|
||||||
|
let options = args.options;
|
||||||
|
|
||||||
let queryOptions = {};
|
let queryOptions = {};
|
||||||
if (options.lines) queryOptions.limit = options.lines;
|
if (options.lines) queryOptions.limit = options.lines;
|
||||||
if (options.sort) {
|
if (options.sort) {
|
||||||
queryOptions.orderBy = options.sort;
|
queryOptions.orderBy = options.sort;
|
||||||
queryOptions.orderByDir = 'ASC';
|
queryOptions.orderByDir = 'ASC';
|
||||||
}
|
|
||||||
if (options.reverse === true) queryOptions.orderByDir = queryOptions.orderByDir == 'ASC' ? 'DESC' : 'ASC';
|
|
||||||
queryOptions.caseInsensitive = true;
|
|
||||||
if (options.type) {
|
|
||||||
queryOptions.itemTypes = [];
|
|
||||||
if (options.type.indexOf('n') >= 0) queryOptions.itemTypes.push('note');
|
|
||||||
if (options.type.indexOf('t') >= 0) queryOptions.itemTypes.push('todo');
|
|
||||||
}
|
|
||||||
if (pattern) queryOptions.titlePattern = pattern;
|
|
||||||
|
|
||||||
if (pattern == '..') {
|
|
||||||
items = await Folder.all(queryOptions);
|
|
||||||
suffix = '/';
|
|
||||||
} else {
|
|
||||||
items = await Note.previews(currentFolder.id, queryOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
let item = items[i];
|
|
||||||
let line = '';
|
|
||||||
if (!!item.is_todo) {
|
|
||||||
line += sprintf('[%s] ', !!item.todo_completed ? 'X' : ' ');
|
|
||||||
}
|
}
|
||||||
line += item.title + suffix;
|
if (options.reverse === true) queryOptions.orderByDir = queryOptions.orderByDir == 'ASC' ? 'DESC' : 'ASC';
|
||||||
this.log(line);
|
queryOptions.caseInsensitive = true;
|
||||||
|
if (options.type) {
|
||||||
|
queryOptions.itemTypes = [];
|
||||||
|
if (options.type.indexOf('n') >= 0) queryOptions.itemTypes.push('note');
|
||||||
|
if (options.type.indexOf('t') >= 0) queryOptions.itemTypes.push('todo');
|
||||||
|
}
|
||||||
|
if (pattern) queryOptions.titlePattern = pattern;
|
||||||
|
|
||||||
|
if (pattern == '..') {
|
||||||
|
items = await Folder.all(queryOptions);
|
||||||
|
suffix = '/';
|
||||||
|
} else {
|
||||||
|
items = await Note.previews(currentFolder.id, queryOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
let line = '';
|
||||||
|
if (!!item.is_todo) {
|
||||||
|
line += sprintf('[%s] ', !!item.todo_completed ? 'X' : ' ');
|
||||||
|
}
|
||||||
|
line += item.title + suffix;
|
||||||
|
this.log(line);
|
||||||
|
}
|
||||||
|
} catch (Error) {
|
||||||
|
this.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
end();
|
end();
|
||||||
@@ -315,59 +328,64 @@ commands.push({
|
|||||||
['--fuzzy-matching', 'For debugging purposes. Do not use.'],
|
['--fuzzy-matching', 'For debugging purposes. Do not use.'],
|
||||||
],
|
],
|
||||||
action: async function(args, end) {
|
action: async function(args, end) {
|
||||||
let filePath = args.file;
|
try {
|
||||||
let folder = null;
|
let filePath = args.file;
|
||||||
let folderTitle = args['notebook'];
|
let folder = null;
|
||||||
|
let folderTitle = args['notebook'];
|
||||||
|
|
||||||
if (folderTitle) {
|
if (folderTitle) {
|
||||||
folder = await Folder.loadByField('title', folderTitle);
|
folder = await Folder.loadByField('title', folderTitle);
|
||||||
if (!folder) return cmdError(this, _('Folder does not exists: "%s"', folderTitle), end);
|
if (!folder) return cmdError(this, _('Folder does not exists: "%s"', folderTitle), end);
|
||||||
} else {
|
} else {
|
||||||
folderTitle = filename(filePath);
|
folderTitle = filename(filePath);
|
||||||
folderTitle = _('Imported - %s', folderTitle);
|
folderTitle = _('Imported - %s', folderTitle);
|
||||||
let inc = 0;
|
let inc = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
let t = folderTitle + (inc ? ' (' + inc + ')' : '');
|
let t = folderTitle + (inc ? ' (' + inc + ')' : '');
|
||||||
let f = await Folder.loadByField('title', t);
|
let f = await Folder.loadByField('title', t);
|
||||||
if (!f) {
|
if (!f) {
|
||||||
folderTitle = t;
|
folderTitle = t;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
inc++;
|
||||||
}
|
}
|
||||||
inc++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ok = await cmdPromptConfirm(this, _('File "%s" will be imported into notebook "%s". Continue?', basename(filePath), folderTitle))
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let redrawnCalled = false;
|
||||||
|
let options = {
|
||||||
|
fuzzyMatching: args.options['fuzzy-matching'] === true,
|
||||||
|
onProgress: (progressState) => {
|
||||||
|
let line = [];
|
||||||
|
line.push(_('Found: %d.', progressState.loaded));
|
||||||
|
line.push(_('Created: %d.', progressState.created));
|
||||||
|
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||||
|
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
||||||
|
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
||||||
|
redrawnCalled = true;
|
||||||
|
vorpal.ui.redraw(line.join(' '));
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
let s = error.trace ? error.trace : error.toString();
|
||||||
|
this.log(s);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
folder = !folder ? await Folder.save({ title: folderTitle }) : folder;
|
||||||
|
this.log(_('Importing notes...'));
|
||||||
|
await importEnex(folder.id, filePath, options);
|
||||||
|
if (redrawnCalled) vorpal.ui.redraw.done();
|
||||||
|
this.log(_('Done.'));
|
||||||
|
} catch (error) {
|
||||||
|
this.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ok = await cmdPromptConfirm(this, _('File "%s" will be imported into notebook "%s". Continue?', basename(filePath), folderTitle))
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let redrawnCalled = false;
|
|
||||||
let options = {
|
|
||||||
fuzzyMatching: args.options['fuzzy-matching'] === true,
|
|
||||||
onProgress: (progressState) => {
|
|
||||||
let line = [];
|
|
||||||
line.push(_('Found: %d.', progressState.loaded));
|
|
||||||
line.push(_('Created: %d.', progressState.created));
|
|
||||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
|
||||||
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
|
||||||
redrawnCalled = true;
|
|
||||||
vorpal.ui.redraw(line.join(' '));
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
let s = error.trace ? error.trace : error.toString();
|
|
||||||
this.log(s);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
folder = !folder ? await Folder.save({ title: folderTitle }) : folder;
|
|
||||||
this.log(_('Importing notes...'));
|
|
||||||
await importEnex(folder.id, filePath, options);
|
|
||||||
if (redrawnCalled) vorpal.ui.redraw.done();
|
|
||||||
this.log(_('Done.'));
|
|
||||||
|
|
||||||
end();
|
end();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "joplin-cli",
|
"name": "joplin-cli",
|
||||||
"version": "0.8.9",
|
"version": "0.8.11",
|
||||||
"bin": {
|
"bin": {
|
||||||
"joplin": "./main.sh"
|
"joplin": "./main.sh"
|
||||||
},
|
},
|
||||||
|
@@ -271,7 +271,17 @@ class BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static filter(model) {
|
static filter(model) {
|
||||||
return model;
|
if (!model) return model;
|
||||||
|
|
||||||
|
let output = Object.assign({}, model);
|
||||||
|
for (let n in output) {
|
||||||
|
if (!output.hasOwnProperty(n)) continue;
|
||||||
|
// The SQLite database doesn't have booleans so cast everything to int
|
||||||
|
if (output[n] === true) output[n] = 1;
|
||||||
|
if (output[n] === false) output[n] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static delete(id, options = null) {
|
static delete(id, options = null) {
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { uuid } from 'lib/uuid.js';
|
import { uuid } from 'lib/uuid.js';
|
||||||
import { promiseChain } from 'lib/promise-utils.js';
|
import { promiseChain } from 'lib/promise-utils.js';
|
||||||
import { Logger } from 'lib/logger.js'
|
import { Logger } from 'lib/logger.js'
|
||||||
|
import { time } from 'lib/time-utils.js'
|
||||||
import { _ } from 'lib/locale.js'
|
import { _ } from 'lib/locale.js'
|
||||||
|
import { sprintf } from 'sprintf-js';
|
||||||
|
|
||||||
const structureSql = `
|
const structureSql = `
|
||||||
CREATE TABLE folders (
|
CREATE TABLE folders (
|
||||||
@@ -193,11 +195,29 @@ class Database {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(sql, params = null) {
|
async exec(sql, params = null) {
|
||||||
this.logQuery(sql, params);
|
let result = null;
|
||||||
return this.driver().exec(sql, params).catch((error) => {
|
let waitTime = 50;
|
||||||
throw this.sqliteErrorToJsError(error, sql, params);
|
let totalWaitTime = 0;
|
||||||
});
|
while (true) {
|
||||||
|
try {
|
||||||
|
this.logQuery(sql, params);
|
||||||
|
let result = await this.driver().exec(sql, params);
|
||||||
|
return result;; // No exception was thrown
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
if (error && error.code == 'SQLITE_IOERR') {
|
||||||
|
if (totalWaitTime >= 20000) throw error;
|
||||||
|
this.logger().warn(sprintf('SQLITE_IOERR: will retry in %s milliseconds', waitTime));
|
||||||
|
this.logger().warn('Error was: ' + error.toString());
|
||||||
|
await time.msleep(waitTime);
|
||||||
|
totalWaitTime += waitTime;
|
||||||
|
waitTime *= 1.5;
|
||||||
|
} else {
|
||||||
|
throw this.sqliteErrorToJsError(error, sql, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionExecBatch(queries) {
|
transactionExecBatch(queries) {
|
||||||
|
@@ -105,7 +105,7 @@ class Note extends BaseItem {
|
|||||||
static filter(note) {
|
static filter(note) {
|
||||||
if (!note) return note;
|
if (!note) return note;
|
||||||
|
|
||||||
let output = Object.assign({}, note);
|
let output = super.filter(note);
|
||||||
if ('longitude' in output) output.longitude = Number(!output.longitude ? 0 : output.longitude).toFixed(8);
|
if ('longitude' in output) output.longitude = Number(!output.longitude ? 0 : output.longitude).toFixed(8);
|
||||||
if ('latitude' in output) output.latitude = Number(!output.latitude ? 0 : output.latitude).toFixed(8);
|
if ('latitude' in output) output.latitude = Number(!output.latitude ? 0 : output.latitude).toFixed(8);
|
||||||
if ('altitude' in output) output.altitude = Number(!output.altitude ? 0 : output.altitude).toFixed(4);
|
if ('altitude' in output) output.altitude = Number(!output.altitude ? 0 : output.altitude).toFixed(4);
|
||||||
|
@@ -224,7 +224,8 @@ class OneDriveApi {
|
|||||||
|
|
||||||
enableServerDestroy(server);
|
enableServerDestroy(server);
|
||||||
|
|
||||||
console.info('Please open this URL in your browser to authentify the application: ' + authCodeUrl);
|
console.info('Please open this URL in your browser to authentify the application:');
|
||||||
|
console.info(authCodeUrl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,18 @@ let time = {
|
|||||||
return moment.unix(ms / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z';
|
return moment.unix(ms / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
msleep(ms) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sleep(seconds) {
|
||||||
|
return this.msleep(seconds * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { time };
|
export { time };
|
Reference in New Issue
Block a user