From ac4718ac920dd42e4f047ca9232d5328d1403217 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 25 Jun 2017 13:49:46 +0100 Subject: [PATCH] Handle globbing pattern for rm and ls --- CliClient/app/main.js | 65 ++++++++++++++++++++++++------------------- lib/base-model.js | 1 + lib/database.js | 16 ++++++++++- lib/logger.js | 25 +++++++++++++++-- lib/models/note.js | 16 +++++++++-- 5 files changed, 89 insertions(+), 34 deletions(-) diff --git a/CliClient/app/main.js b/CliClient/app/main.js index c11a130fc..8f40a63e1 100644 --- a/CliClient/app/main.js +++ b/CliClient/app/main.js @@ -75,6 +75,13 @@ async function main() { // return; + // let testglob = await Note.glob('title', 'La *', { + // fields: ['title', 'updated_time'], + // }); + // console.info(testglob); + + + @@ -340,26 +347,37 @@ async function main() { }); commands.push({ - usage: 'rm ', + usage: 'rm ', description: 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../` to delete a notebook.', action: async function(args, end) { - let title = args['item-title']; + let pattern = args['pattern']; let itemType = null; - if (title.substr(0, 3) == '../') { - itemType = BaseModel.MODEL_TYPE_FOLDER; - title = title.substr(3); - } else { - itemType = BaseModel.MODEL_TYPE_NOTE; - } + if (pattern.indexOf('*') < 0) { // Handle it as a simple title + if (pattern.substr(0, 3) == '../') { + itemType = BaseModel.MODEL_TYPE_FOLDER; + pattern = pattern.substr(3); + } else { + itemType = BaseModel.MODEL_TYPE_NOTE; + } - let item = await BaseItem.loadItemByField(itemType, 'title', title); - if (!item) return cmdError(this, _('No item with title "%s" found.', title), end); - await BaseItem.deleteItem(itemType, item.id); + let item = await BaseItem.loadItemByField(itemType, 'title', pattern); + if (!item) return cmdError(this, _('No item with title "%s" found.', pattern), end); + await BaseItem.deleteItem(itemType, item.id); - if (currentFolder && currentFolder.id == item.id) { - let f = await Folder.defaultFolder(); - switchCurrentFolder(f); + if (currentFolder && currentFolder.id == item.id) { + let f = await Folder.defaultFolder(); + switchCurrentFolder(f); + } + } else { // Handle it as a glob pattern + let notes = await Note.previews(currentFolder.id, { titlePattern: pattern }); + if (!notes.length) return cmdError(this, _('No note matches this pattern: "%s"', pattern), end); + let ok = await cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length)); + if (ok) { + for (let i = 0; i < notes.length; i++) { + await Note.delete(notes[i].id); + } + } } end(); @@ -368,7 +386,7 @@ async function main() { }); commands.push({ - usage: 'ls [notebook-title]', + usage: 'ls [pattern]', description: 'Displays the notes in [notebook-title]. Use `ls ..` to display the list of notebooks.', options: [ ['-n, --lines ', 'Displays only the first top lines.'], @@ -377,7 +395,7 @@ async function main() { ['-t, --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) { - let folderTitle = args['notebook-title']; + let pattern = args['pattern']; let suffix = ''; let items = []; let options = args.options; @@ -395,22 +413,13 @@ async function main() { 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 (folderTitle == '..') { + if (pattern == '..') { items = await Folder.all(queryOptions); suffix = '/'; } else { - let folder = null; - - if (folderTitle) { - folder = await Folder.loadByField('title', folderTitle); - } else if (currentFolder) { - folder = currentFolder; - } - - if (!folder) return cmdError(this, _('Unknown notebook: "%s"', folderTitle), end); - - items = await Note.previews(folder.id, queryOptions); + items = await Note.previews(currentFolder.id, queryOptions); } for (let i = 0; i < items.length; i++) { diff --git a/lib/base-model.js b/lib/base-model.js index 8d2e91fe0..32d1b9371 100644 --- a/lib/base-model.js +++ b/lib/base-model.js @@ -133,6 +133,7 @@ class BaseModel { if (options.orderByDir) sql += ' ' + options.orderByDir; } if (options.limit) sql += ' LIMIT ' + options.limit; + //if (options.fields && options.fields.length) sql = sql.replace('SELECT *', 'SELECT ' + this.db().escapeFields(options.fields).join(',')); return { sql: sql, params: params }; } diff --git a/lib/database.js b/lib/database.js index 6b6ae6d67..bc35219ae 100644 --- a/lib/database.js +++ b/lib/database.js @@ -167,6 +167,18 @@ class Database { }); } + escapeField(field) { + return '`' + field + '`'; + } + + escapeFields(fields) { + let output = []; + for (let i = 0; i < fields.length; i++) { + output.push(this.escapeField(fields[i])); + } + return output; + } + selectOne(sql, params = null) { this.logQuery(sql, params); return this.driver().selectOne(sql, params).catch((error) => { @@ -285,8 +297,10 @@ class Database { logQuery(sql, params = null) { if (!this.debugMode()) return; + //console.info(sql, params); + this.logger().debug(sql); - if (params !== null) this.logger().debug(JSON.stringify(params)); + if (params !== null && params.length) this.logger().debug(JSON.stringify(params)); } static insertQuery(tableName, data) { diff --git a/lib/logger.js b/lib/logger.js index b00d739f1..5a26d91d0 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -6,6 +6,7 @@ class Logger { constructor() { this.targets_ = []; this.level_ = Logger.LEVEL_ERROR; + this.fileAppendQueue_ = [] } setLevel(level) { @@ -64,14 +65,32 @@ class Logger { } else { serializedObject = object; } - - fs.appendFile(t.path, line + serializedObject + "\n", (error) => { - if (error) throw error; + + this.fileAppendQueue_.push({ + path: t.path, + line: line + serializedObject + "\n", }); + + this.scheduleFileAppendQueueProcessing_(); } } } + scheduleFileAppendQueueProcessing_() { + if (this.fileAppendQueueTID_) return; + + this.fileAppendQueueTID_ = setTimeout(async () => { + this.fileAppendQueueTID_ = null; + + let queue = this.fileAppendQueue_.slice(0); + for (let i = 0; i < queue.length; i++) { + let t = queue[i]; + await fs.appendFile(t.path, t.line); + } + this.fileAppendQueue_.splice(0, queue.length); + }, 10); + } + error(object) { return this.log(Logger.LEVEL_ERROR, object); } warn(object) { return this.log(Logger.LEVEL_WARN, object); } info(object) { return this.log(Logger.LEVEL_INFO, object); } diff --git a/lib/models/note.js b/lib/models/note.js index bee677c26..a7e93cc75 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -39,8 +39,12 @@ class Note extends BaseItem { return output; } + static previewFields() { + return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time']; + } + static previewFieldsSql() { - return '`id`, `title`, `body`, `is_todo`, `todo_completed`, `parent_id`, `updated_time`' + return this.db().escapeFields(this.previewFields()).join(','); } static previews(parentId, options = null) { @@ -49,6 +53,7 @@ class Note extends BaseItem { if (!options.orderByDir) options.orderByDir = 'DESC'; 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 @@ -58,7 +63,14 @@ class Note extends BaseItem { sql += ' AND is_todo = 1'; } } - let query = this.applySqlOptions(options, sql, [parentId]); + + 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); }