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

memory driver

This commit is contained in:
Laurent Cozic 2017-06-13 20:12:08 +00:00
parent 8578642307
commit c92b46b651
5 changed files with 441 additions and 321 deletions

View File

@ -2,6 +2,7 @@ require('source-map-support').install();
import { FileApi } from 'src/file-api.js'; import { FileApi } from 'src/file-api.js';
import { FileApiDriverLocal } from 'src/file-api-driver-local.js'; import { FileApiDriverLocal } from 'src/file-api-driver-local.js';
import { FileApiDriverMemory } from 'src/file-api-driver-memory.js';
import { Database } from 'src/database.js'; import { Database } from 'src/database.js';
import { DatabaseDriverNode } from 'src/database-driver-node.js'; import { DatabaseDriverNode } from 'src/database-driver-node.js';
import { BaseModel } from 'src/base-model.js'; import { BaseModel } from 'src/base-model.js';
@ -14,356 +15,359 @@ import { sprintf } from 'sprintf-js';
import { _ } from 'src/locale.js'; import { _ } from 'src/locale.js';
import { NoteFolderService } from 'src/services/note-folder-service.js'; import { NoteFolderService } from 'src/services/note-folder-service.js';
// name: 'f42b0e23f06948ee9dda3fcf1b1c4205/.folder.md',
// createdTime: 1497216952,
// updatedTime: 1497216952,
// createdTimeOrig: 2017-06-11T21:35:52.362Z,
// updatedTimeOrig: 2017-06-11T21:35:52.362Z,
// isDir: false
// Sun, 11 Jun 2017 21:35:52 GMT
// import moment from 'moment';
// let m = moment('2017-06-11T21:35:52.362Z');
// console.info(Math.round(m.toDate().getTime() / 1000));
// //let m = moment(time, 'YYYY-MM-DDTHH:mm:ss.SSSZ');
// // if (!m.isValid()) {
// // throw new Error('Invalid date: ' + time);
// // }
// // return Math.round(m.toDate().getTime() / 1000);
const vorpal = require('vorpal')(); const vorpal = require('vorpal')();
let db = new Database(new DatabaseDriverNode()); let db = new Database(new DatabaseDriverNode());
db.setDebugEnabled(false); db.setDebugEnabled(false);
let fileDriver = new FileApiDriverLocal(); // let fileDriver = new FileApiDriverLocal();
let fileApi = new FileApi('/home/laurent/Temp/TestImport', fileDriver); // let fileApi = new FileApi('/home/laurent/Temp/TestImport', fileDriver);
// let synchronizer = new Synchronizer(db, fileApi);
let fileDriver = new FileApiDriverMemory();
let fileApi = new FileApi('/root', fileDriver);
let synchronizer = new Synchronizer(db, fileApi); let synchronizer = new Synchronizer(db, fileApi);
fileApi.mkdir('test').then(() => {
// import moment from 'moment-timezone'; return fileApi.mkdir('test2');
// console.info(moment.tz.guess());
db.open({ name: '/home/laurent/Temp/test.sqlite3' }).then(() => {
BaseModel.db_ = db;
}).then(() => { }).then(() => {
return Setting.load(); return fileApi.put('test/un', 'abcd1111').then(fileApi.put('test/deux', 'abcd2222'));
}).then(() => { }).then(() => {
let commands = []; return fileApi.list();
let currentFolder = null; }).then((items) => {
//console.info(items);
}).then(() => {
return fileApi.delete('test/un');
}).then(() => {
return fileApi.get('test/deux').then((content) => { console.info(content); });
}).then(() => {
return fileApi.list('test', true);
}).then((items) => {
console.info(items);
}).catch((error) => {
console.error(error);
}).then(() => {
process.exit();
});
function switchCurrentFolder(folder) {
currentFolder = folder;
updatePrompt();
}
function promptString() {
let path = '~';
if (currentFolder) {
path += '/' + currentFolder.title;
}
return 'joplin:' + path + '$ ';
}
function updatePrompt() {
vorpal.delimiter(promptString());
}
// For now, to go around this issue: https://github.com/dthree/vorpal/issues/114
function quotePromptArg(s) {
if (s.indexOf(' ') >= 0) {
return '"' + s + '"';
}
return s;
}
function autocompleteFolders() {
return Folder.all().then((folders) => {
let output = [];
for (let i = 0; i < folders.length; i++) {
output.push(quotePromptArg(folders[i].title));
}
output.push('..');
output.push('.');
return output;
});
}
function autocompleteItems() {
let promise = null;
if (!currentFolder) {
promise = Folder.all();
} else {
promise = Note.previews(currentFolder.id);
}
return promise.then((items) => { // db.open({ name: '/home/laurent/Temp/test.sqlite3' }).then(() => {
let output = []; // BaseModel.db_ = db;
for (let i = 0; i < items.length; i++) { // }).then(() => {
output.push(quotePromptArg(items[i].title)); // return Setting.load();
} // }).then(() => {
return output; // let commands = [];
}); // let currentFolder = null;
}
process.stdin.on('keypress', (_, key) => { // function switchCurrentFolder(folder) {
if (key && key.name === 'return') { // currentFolder = folder;
updatePrompt(); // updatePrompt();
} // }
if (key.name === 'tab') { // function promptString() {
vorpal.ui.imprint(); // let path = '~';
vorpal.log(vorpal.ui.input()); // if (currentFolder) {
} // path += '/' + currentFolder.title;
}); // }
// return 'joplin:' + path + '$ ';
// }
commands.push({ // function updatePrompt() {
usage: 'cd <list-title>', // vorpal.delimiter(promptString());
description: 'Moved to [list-title] - all further operations will happen within this list. Use `cd ..` to go back one level.', // }
action: function (args, end) {
let folderTitle = args['list-title'];
if (folderTitle == '..') { // // For now, to go around this issue: https://github.com/dthree/vorpal/issues/114
switchCurrentFolder(null); // function quotePromptArg(s) {
end(); // if (s.indexOf(' ') >= 0) {
return; // return '"' + s + '"';
} // }
// return s;
// }
if (folderTitle == '.') { // function autocompleteFolders() {
end(); // return Folder.all().then((folders) => {
return; // let output = [];
} // for (let i = 0; i < folders.length; i++) {
// output.push(quotePromptArg(folders[i].title));
// }
// output.push('..');
// output.push('.');
// return output;
// });
// }
Folder.loadByField('title', folderTitle).then((folder) => { // function autocompleteItems() {
switchCurrentFolder(folder); // let promise = null;
end(); // if (!currentFolder) {
}); // promise = Folder.all();
}, // } else {
autocomplete: autocompleteFolders, // promise = Note.previews(currentFolder.id);
}); // }
commands.push({ // return promise.then((items) => {
usage: 'mklist <list-title>', // let output = [];
alias: 'mkdir', // for (let i = 0; i < items.length; i++) {
description: 'Creates a new list', // output.push(quotePromptArg(items[i].title));
action: function (args, end) { // }
NoteFolderService.save('folder', { title: args['list-title'] }).catch((error) => { // return output;
this.log(error); // });
}).then((folder) => { // }
switchCurrentFolder(folder);
end();
});
},
});
commands.push({ // process.stdin.on('keypress', (_, key) => {
usage: 'mknote <note-title>', // if (key && key.name === 'return') {
alias: 'touch', // updatePrompt();
description: 'Creates a new note', // }
action: function (args, end) {
if (!currentFolder) {
this.log('Notes can only be created within a list.');
end();
return;
}
let note = { // if (key.name === 'tab') {
title: args['note-title'], // vorpal.ui.imprint();
parent_id: currentFolder.id, // vorpal.log(vorpal.ui.input());
}; // }
NoteFolderService.save('note', note).catch((error) => { // });
this.log(error);
}).then((note) => {
end();
});
},
});
commands.push({ // commands.push({
usage: 'set <item-title> <prop-name> [prop-value]', // usage: 'cd <list-title>',
description: 'Sets the given <prop-name> of the given item.', // description: 'Moved to [list-title] - all further operations will happen within this list. Use `cd ..` to go back one level.',
action: function (args, end) { // action: function (args, end) {
let promise = null; // let folderTitle = args['list-title'];
let title = args['item-title'];
let propName = args['prop-name'];
let propValue = args['prop-value'];
if (!propValue) propValue = '';
if (!currentFolder) { // if (folderTitle == '..') {
promise = Folder.loadByField('title', title); // switchCurrentFolder(null);
} else { // end();
promise = Folder.loadNoteByField(currentFolder.id, 'title', title); // return;
} // }
promise.then((item) => { // if (folderTitle == '.') {
if (!item) { // end();
this.log(_('No item with title "%s" found.', title)); // return;
end(); // }
return;
}
let newItem = Object.assign({}, item); // Folder.loadByField('title', folderTitle).then((folder) => {
newItem[propName] = propValue; // switchCurrentFolder(folder);
let itemType = currentFolder ? 'note' : 'folder'; // end();
return NoteFolderService.save(itemType, newItem, item); // });
}).catch((error) => { // },
this.log(error); // autocomplete: autocompleteFolders,
}).then(() => { // });
end();
});
},
autocomplete: autocompleteItems,
});
commands.push({ // commands.push({
usage: 'cat <item-title>', // usage: 'mklist <list-title>',
description: 'Displays the given item data.', // alias: 'mkdir',
action: function (args, end) { // description: 'Creates a new list',
let title = args['item-title']; // action: function (args, end) {
// NoteFolderService.save('folder', { title: args['list-title'] }).catch((error) => {
// this.log(error);
// }).then((folder) => {
// switchCurrentFolder(folder);
// end();
// });
// },
// });
let promise = null; // commands.push({
if (!currentFolder) { // usage: 'mknote <note-title>',
promise = Folder.loadByField('title', title); // alias: 'touch',
} else { // description: 'Creates a new note',
promise = Folder.loadNoteByField(currentFolder.id, 'title', title); // action: function (args, end) {
} // if (!currentFolder) {
// this.log('Notes can only be created within a list.');
// end();
// return;
// }
promise.then((item) => { // let note = {
if (!item) { // title: args['note-title'],
this.log(_('No item with title "%s" found.', title)); // parent_id: currentFolder.id,
end(); // };
return; // NoteFolderService.save('note', note).catch((error) => {
} // this.log(error);
// }).then((note) => {
// end();
// });
// },
// });
if (!currentFolder) { // commands.push({
this.log(Folder.toFriendlyString(item)); // usage: 'set <item-title> <prop-name> [prop-value]',
} else { // description: 'Sets the given <prop-name> of the given item.',
this.log(Note.toFriendlyString(item)); // action: function (args, end) {
} // let promise = null;
}).catch((error) => { // let title = args['item-title'];
this.log(error); // let propName = args['prop-name'];
}).then(() => { // let propValue = args['prop-value'];
end(); // if (!propValue) propValue = '';
});
},
autocomplete: autocompleteItems,
});
commands.push({ // if (!currentFolder) {
usage: 'rm <item-title>', // promise = Folder.loadByField('title', title);
description: 'Deletes the given item. For a list, all the notes within that list will be deleted.', // } else {
action: function (args, end) { // promise = Folder.loadNoteByField(currentFolder.id, 'title', title);
let title = args['item-title']; // }
let promise = null; // promise.then((item) => {
let itemType = currentFolder ? 'note' : 'folder'; // if (!item) {
if (itemType == 'folder') { // this.log(_('No item with title "%s" found.', title));
promise = Folder.loadByField('title', title); // end();
} else { // return;
promise = Folder.loadNoteByField(currentFolder.id, 'title', title); // }
}
promise.then((item) => { // let newItem = Object.assign({}, item);
if (!item) { // newItem[propName] = propValue;
this.log(_('No item with title "%s" found.', title)); // let itemType = currentFolder ? 'note' : 'folder';
end(); // return NoteFolderService.save(itemType, newItem, item);
return; // }).catch((error) => {
} // this.log(error);
// }).then(() => {
// end();
// });
// },
// autocomplete: autocompleteItems,
// });
if (itemType == 'folder') { // commands.push({
return Folder.delete(item.id); // usage: 'cat <item-title>',
} else { // description: 'Displays the given item data.',
return Note.delete(item.id); // action: function (args, end) {
} // let title = args['item-title'];
}).catch((error) => {
this.log(error);
}).then(() => {
end();
});
},
autocomplete: autocompleteItems,
});
commands.push({ // let promise = null;
usage: 'ls [list-title]', // if (!currentFolder) {
alias: 'll', // promise = Folder.loadByField('title', title);
description: 'Lists items in [list-title].', // } else {
action: function (args, end) { // promise = Folder.loadNoteByField(currentFolder.id, 'title', title);
let folderTitle = args['list-title']; // }
let promise = null; // promise.then((item) => {
// if (!item) {
// this.log(_('No item with title "%s" found.', title));
// end();
// return;
// }
if (folderTitle == '..') { // if (!currentFolder) {
promise = Promise.resolve('root'); // this.log(Folder.toFriendlyString(item));
} else if (folderTitle && folderTitle != '.') { // } else {
promise = Folder.loadByField('title', folderTitle); // this.log(Note.toFriendlyString(item));
} else if (currentFolder) { // }
promise = Promise.resolve(currentFolder); // }).catch((error) => {
} else { // this.log(error);
promise = Promise.resolve('root'); // }).then(() => {
} // end();
// });
// },
// autocomplete: autocompleteItems,
// });
promise.then((folder) => { // commands.push({
let p = null // usage: 'rm <item-title>',
let postfix = ''; // description: 'Deletes the given item. For a list, all the notes within that list will be deleted.',
if (folder === 'root') { // action: function (args, end) {
p = Folder.all(); // let title = args['item-title'];
postfix = '/';
} else if (!folder) {
throw new Error(_('Unknown list: "%s"', folderTitle));
} else {
p = Note.previews(folder.id);
}
return p.then((previews) => { // let promise = null;
for (let i = 0; i < previews.length; i++) { // let itemType = currentFolder ? 'note' : 'folder';
this.log(previews[i].title + postfix); // if (itemType == 'folder') {
} // promise = Folder.loadByField('title', title);
}); // } else {
}).catch((error) => { // promise = Folder.loadNoteByField(currentFolder.id, 'title', title);
this.log(error); // }
}).then(() => {
end();
});
},
autocomplete: autocompleteFolders,
});
commands.push({ // promise.then((item) => {
usage: 'sync', // if (!item) {
description: 'Synchronizes with remote storage.', // this.log(_('No item with title "%s" found.', title));
action: function (args, end) { // end();
synchronizer.start().catch((error) => { // return;
console.error(error); // }
}).then(() => {
end();
});
},
});
for (let i = 0; i < commands.length; i++) { // if (itemType == 'folder') {
let c = commands[i]; // return Folder.delete(item.id);
let o = vorpal.command(c.usage, c.description); // } else {
if (c.alias) { // return Note.delete(item.id);
o.alias(c.alias); // }
} // }).catch((error) => {
if (c.autocomplete) { // this.log(error);
o.autocomplete({ // }).then(() => {
data: c.autocomplete, // end();
}); // });
} // },
o.action(c.action); // autocomplete: autocompleteItems,
} // });
vorpal.delimiter(promptString()).show(); // commands.push({
}); // usage: 'ls [list-title]',
// alias: 'll',
// description: 'Lists items in [list-title].',
// action: function (args, end) {
// let folderTitle = args['list-title'];
// let promise = null;
// if (folderTitle == '..') {
// promise = Promise.resolve('root');
// } else if (folderTitle && folderTitle != '.') {
// promise = Folder.loadByField('title', folderTitle);
// } else if (currentFolder) {
// promise = Promise.resolve(currentFolder);
// } else {
// promise = Promise.resolve('root');
// }
// promise.then((folder) => {
// let p = null
// let postfix = '';
// if (folder === 'root') {
// p = Folder.all();
// postfix = '/';
// } else if (!folder) {
// throw new Error(_('Unknown list: "%s"', folderTitle));
// } else {
// p = Note.previews(folder.id);
// }
// return p.then((previews) => {
// for (let i = 0; i < previews.length; i++) {
// this.log(previews[i].title + postfix);
// }
// });
// }).catch((error) => {
// this.log(error);
// }).then(() => {
// end();
// });
// },
// autocomplete: autocompleteFolders,
// });
// commands.push({
// usage: 'sync',
// description: 'Synchronizes with remote storage.',
// action: function (args, end) {
// synchronizer.start().catch((error) => {
// console.error(error);
// }).then(() => {
// end();
// });
// },
// });
// for (let i = 0; i < commands.length; i++) {
// let c = commands[i];
// let o = vorpal.command(c.usage, c.description);
// if (c.alias) {
// o.alias(c.alias);
// }
// if (c.autocomplete) {
// o.autocomplete({
// data: c.autocomplete,
// });
// }
// o.action(c.action);
// }
// vorpal.delimiter(promptString()).show();
// });

View File

@ -36,7 +36,7 @@ class FileApiDriverLocal {
}; };
} }
setFileTimestamp(path, timestamp) { setTimestamp(path, timestamp) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.utimes(path, timestamp, timestamp, (error) => { fs.utimes(path, timestamp, timestamp, (error) => {
if (error) { if (error) {

View File

@ -0,0 +1,109 @@
class FileApiDriverMemory {
constructor(baseDir) {
this.items_ = [];
}
currentTimestamp() {
return Math.round((new Date()).getTime() / 1000);
}
itemIndexByPath(path) {
for (let i = 0; i < this.items_.length; i++) {
if (this.items_[i].path == path) return i;
}
return -1;
}
itemByPath(path) {
let index = this.itemIndexByPath(path);
return index < 0 ? null : this.items_[index];
}
newItem(path, isDir = false) {
return {
path: path,
isDir: isDir,
updatedTime: this.currentTimestamp(),
createdTime: this.currentTimestamp(),
content: '',
};
}
stat(path) {
let item = this.itemIndexByPath(path);
if (!item) return Promise.reject(new Error('File not found: ' + path));
return Promise.resolve(item);
}
setTimestamp(path, timestamp) {
let item = this.itemIndexByPath(path);
if (!item) return Promise.reject(new Error('File not found: ' + path));
item.updatedTime = timestamp;
return Promise.resolve();
}
list(path) {
let output = [];
for (let i = 0; i < this.items_.length; i++) {
let item = this.items_[i];
if (item.path == path) continue;
if (item.path.indexOf(path + '/') === 0) {
let s = item.path.substr(path.length + 1);
if (s.split('/').length === 1) {
let it = Object.assign({}, item);
it.path = it.path.substr(path.length + 1);
output.push(it);
}
}
}
return Promise.resolve(output);
}
get(path) {
let item = this.itemByPath(path);
if (!item) return Promise.reject(new Error('File not found: ' + path));
if (item.isDir) return Promise.reject(new Error(path + ' is a directory, not a file'));
return Promise.resolve(item.content);
}
mkdir(path) {
let index = this.itemIndexByPath(path);
if (index >= 0) return Promise.resolve();
this.items_.push(this.newItem(path, true));
return Promise.resolve();
}
put(path, content) {
let index = this.itemIndexByPath(path);
if (index < 0) {
let item = this.newItem(path, false);
item.content = content;
this.items_.push(item);
} else {
this.items_[index].content = content;
}
return Promise.resolve();
}
delete(path) {
let index = this.itemIndexByPath(path);
if (index >= 0) {
this.items_.splice(index, 1);
}
return Promise.resolve();
}
move(oldPath, newPath) {
let sourceItem = this.itemByPath(oldPath);
if (!sourceItem) return Promise.reject(new Error('Path not found: ' + oldPath));
this.delete(newPath); // Overwrite if newPath already exists
sourceItem.path = newPath;
return Promise.resolve();
}
}
export { FileApiDriverMemory };

View File

@ -7,8 +7,15 @@ class FileApi {
this.driver_ = driver; this.driver_ = driver;
} }
list(path, recursive = false) { fullPath_(path) {
return this.driver_.list(this.baseDir_ + '/' + path, recursive).then((items) => { let output = this.baseDir_;
if (path != '') output += '/' + path;
return output;
}
list(path = '', recursive = false) {
let fullPath = this.fullPath_(path);
return this.driver_.list(fullPath, recursive).then((items) => {
if (recursive) { if (recursive) {
let chain = []; let chain = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
@ -16,10 +23,10 @@ class FileApi {
if (!item.isDir) continue; if (!item.isDir) continue;
chain.push(() => { chain.push(() => {
return this.list(path + '/' + item.name, true).then((children) => { return this.list(item.path, true).then((children) => {
for (let j = 0; j < children.length; j++) { for (let j = 0; j < children.length; j++) {
let md = children[j]; let md = children[j];
md.name = item.name + '/' + md.name; md.path = item.path + '/' + md.path;
items.push(md); items.push(md);
} }
}); });
@ -35,28 +42,28 @@ class FileApi {
}); });
} }
setFileTimestamp(path, timestamp) { setTimestamp(path, timestamp) {
return this.driver_.setFileTimestamp(this.baseDir_ + '/' + path, timestamp); return this.driver_.setTimestamp(this.fullPath_(path), timestamp);
} }
mkdir(path) { mkdir(path) {
return this.driver_.mkdir(this.baseDir_ + '/' + path); return this.driver_.mkdir(this.fullPath_(path));
} }
get(path) { get(path) {
return this.driver_.get(this.baseDir_ + '/' + path); return this.driver_.get(this.fullPath_(path));
} }
put(path, content) { put(path, content) {
return this.driver_.put(this.baseDir_ + '/' + path, content); return this.driver_.put(this.fullPath_(path), content);
} }
delete(path) { delete(path) {
return this.driver_.delete(this.baseDir_ + '/' + path); return this.driver_.delete(this.fullPath_(path));
} }
move(oldPath, newPath) { move(oldPath, newPath) {
return this.driver_.move(this.baseDir_ + '/' + oldPath, this.baseDir_ + '/' + newPath); return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath));
} }
} }

View File

@ -122,11 +122,11 @@ class Synchronizer {
return this.api().mkdir(path).then(() => { return this.api().mkdir(path).then(() => {
return this.api().put(Folder.systemMetadataPath(parent, item), Folder.toFriendlyString(item)); return this.api().put(Folder.systemMetadataPath(parent, item), Folder.toFriendlyString(item));
}).then(() => { }).then(() => {
return this.api().setFileTimestamp(Folder.systemMetadataPath(parent, item), item.updated_time); return this.api().setTimestamp(Folder.systemMetadataPath(parent, item), item.updated_time);
}); });
} else { } else {
return this.api().put(path, Note.toFriendlyString(item)).then(() => { return this.api().put(path, Note.toFriendlyString(item)).then(() => {
return this.api().setFileTimestamp(path, item.updated_time); return this.api().setTimestamp(path, item.updated_time);
}); });
} }
}); });