2017-06-22 23:52:27 +02:00
require ( 'source-map-support' ) . install ( ) ;
require ( 'babel-plugin-transform-runtime' ) ;
2017-06-24 20:06:28 +02:00
import { FileApi } from 'lib/file-api.js' ;
import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js' ;
import { Database } from 'lib/database.js' ;
import { DatabaseDriverNode } from 'lib/database-driver-node.js' ;
import { BaseModel } from 'lib/base-model.js' ;
import { Folder } from 'lib/models/folder.js' ;
2017-06-25 09:35:59 +02:00
import { BaseItem } from 'lib/models/base-item.js' ;
2017-06-24 20:06:28 +02:00
import { Note } from 'lib/models/note.js' ;
import { Setting } from 'lib/models/setting.js' ;
import { Synchronizer } from 'lib/synchronizer.js' ;
import { Logger } from 'lib/logger.js' ;
import { uuid } from 'lib/uuid.js' ;
2017-06-22 23:52:27 +02:00
import { sprintf } from 'sprintf-js' ;
2017-06-24 19:40:03 +02:00
import { importEnex } from 'import-enex' ;
2017-06-24 20:06:28 +02:00
import { _ } from 'lib/locale.js' ;
2017-06-22 23:52:27 +02:00
import os from 'os' ;
import fs from 'fs-extra' ;
2017-06-24 21:06:31 +02:00
//const dataDir = os.homedir() + '/.local/share/' + Setting.value('appName');
const dataDir = os . homedir ( ) + '/Temp/TestJoplin' ;
2017-06-24 19:40:03 +02:00
const resourceDir = dataDir + '/resources' ;
2017-06-23 23:32:24 +02:00
2017-06-24 20:51:43 +02:00
Setting . setConstant ( 'dataDir' , dataDir ) ;
Setting . setConstant ( 'resourceDir' , resourceDir ) ;
2017-06-23 23:32:24 +02:00
process . on ( 'unhandledRejection' , ( reason , p ) => {
console . log ( 'Unhandled Rejection at: Promise' , p , 'reason:' , reason ) ;
} ) ;
const logger = new Logger ( ) ;
logger . addTarget ( 'file' , { path : dataDir + '/log.txt' } ) ;
logger . setLevel ( Logger . LEVEL _DEBUG ) ;
2017-06-24 19:40:03 +02:00
const dbLogger = new Logger ( ) ;
dbLogger . addTarget ( 'file' , { path : dataDir + '/log-database.txt' } ) ;
dbLogger . setLevel ( Logger . LEVEL _DEBUG ) ;
2017-06-23 23:32:24 +02:00
const syncLogger = new Logger ( ) ;
syncLogger . addTarget ( 'file' , { path : dataDir + '/log-sync.txt' } ) ;
syncLogger . setLevel ( Logger . LEVEL _DEBUG ) ;
2017-06-22 23:52:27 +02:00
let db = new Database ( new DatabaseDriverNode ( ) ) ;
2017-06-24 19:40:03 +02:00
db . setDebugMode ( true ) ;
db . setLogger ( dbLogger ) ;
2017-06-22 23:52:27 +02:00
let synchronizer _ = null ;
const vorpal = require ( 'vorpal' ) ( ) ;
async function main ( ) {
await fs . mkdirp ( dataDir , 0o755 ) ;
2017-06-24 19:40:03 +02:00
await fs . mkdirp ( resourceDir , 0o755 ) ;
2017-06-22 23:52:27 +02:00
2017-06-24 19:40:03 +02:00
await db . open ( { name : dataDir + '/database2.sqlite' } ) ;
2017-06-22 23:52:27 +02:00
BaseModel . db _ = db ;
await Setting . load ( ) ;
2017-06-24 19:40:03 +02:00
2017-06-25 09:35:59 +02:00
// console.info('DELETING ALL DATA');
// await db.exec('DELETE FROM notes');
// await db.exec('DELETE FROM changes');
2017-06-25 09:52:25 +02:00
// await db.exec('DELETE FROM folders');
2017-06-25 09:35:59 +02:00
// await db.exec('DELETE FROM resources');
// await db.exec('DELETE FROM deleted_items');
// await db.exec('DELETE FROM tags');
// await db.exec('DELETE FROM note_tags');
2017-06-25 09:52:25 +02:00
// let folder1 = await Folder.save({ title: 'test1' });
2017-06-25 09:35:59 +02:00
// let folder2 = await Folder.save({ title: 'test2' });
// await importEnex(folder1.id, '/mnt/c/Users/Laurent/Desktop/Laurent.enex');
// return;
2017-06-24 19:40:03 +02:00
2017-06-22 23:52:27 +02:00
let commands = [ ] ;
let currentFolder = null ;
async function synchronizer ( remoteBackend ) {
if ( synchronizer _ ) return synchronizer _ ;
let fileApi = null ;
if ( remoteBackend == 'onedrive' ) {
const CLIENT _ID = 'e09fc0de-c958-424f-83a2-e56a721d331b' ;
const CLIENT _SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5' ;
let driver = new FileApiDriverOneDrive ( CLIENT _ID , CLIENT _SECRET ) ;
let auth = Setting . value ( 'sync.onedrive.auth' ) ;
if ( auth ) {
auth = JSON . parse ( auth ) ;
} else {
auth = await driver . api ( ) . oauthDance ( ) ;
Setting . setValue ( 'sync.onedrive.auth' , JSON . stringify ( auth ) ) ;
}
driver . api ( ) . setAuth ( auth ) ;
2017-06-23 20:51:02 +02:00
driver . api ( ) . on ( 'authRefreshed' , ( a ) => {
Setting . setValue ( 'sync.onedrive.auth' , JSON . stringify ( a ) ) ;
} ) ;
2017-06-22 23:52:27 +02:00
let appDir = await driver . api ( ) . appDirectory ( ) ;
2017-06-23 23:32:24 +02:00
logger . info ( 'App dir: ' + appDir ) ;
2017-06-22 23:52:27 +02:00
fileApi = new FileApi ( appDir , driver ) ;
2017-06-23 23:32:24 +02:00
fileApi . setLogger ( logger ) ;
2017-06-22 23:52:27 +02:00
} else {
2017-06-23 20:51:02 +02:00
throw new Error ( 'Unknown backend: ' + remoteBackend ) ;
2017-06-22 23:52:27 +02:00
}
synchronizer _ = new Synchronizer ( db , fileApi ) ;
2017-06-23 23:32:24 +02:00
synchronizer _ . setLogger ( syncLogger ) ;
2017-06-22 23:52:27 +02:00
return synchronizer _ ;
}
2017-06-23 23:32:24 +02:00
// let s = await synchronizer('onedrive');
// await synchronizer_.start();
// return;
2017-06-22 23:52:27 +02:00
function switchCurrentFolder ( folder ) {
2017-06-25 12:41:03 +02:00
if ( ! folder ) throw new Error ( _ ( 'No active folder is defined.' ) ) ;
2017-06-22 23:52:27 +02:00
currentFolder = folder ;
2017-06-25 12:41:03 +02:00
Setting . setValue ( 'activeFolderId' , folder . id ) ;
2017-06-22 23:52:27 +02:00
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 ) => {
let output = [ ] ;
for ( let i = 0 ; i < items . length ; i ++ ) {
output . push ( quotePromptArg ( items [ i ] . title ) ) ;
}
return output ;
} ) ;
}
2017-06-25 09:35:59 +02:00
function commandError ( commandInstance , msg , end ) {
commandInstance . log ( msg ) ;
end ( ) ;
}
2017-06-22 23:52:27 +02:00
process . stdin . on ( 'keypress' , ( _ , key ) => {
if ( key && key . name === 'return' ) {
updatePrompt ( ) ;
}
if ( key . name === 'tab' ) {
vorpal . ui . imprint ( ) ;
vorpal . log ( vorpal . ui . input ( ) ) ;
}
} ) ;
commands . push ( {
2017-06-25 09:35:59 +02:00
usage : 'mkbook <notebook-title>' ,
aliases : [ 'mkdir' ] ,
description : 'Creates a new notebook' ,
action : function ( args , end ) {
Folder . save ( { title : args [ 'notebook-title' ] } ) . catch ( ( error ) => {
2017-06-22 23:52:27 +02:00
this . log ( error ) ;
} ) . then ( ( folder ) => {
switchCurrentFolder ( folder ) ;
end ( ) ;
} ) ;
} ,
} ) ;
commands . push ( {
usage : 'mknote <note-title>' ,
2017-06-25 09:35:59 +02:00
aliases : [ 'touch' ] ,
2017-06-22 23:52:27 +02:00
description : 'Creates a new note' ,
2017-06-25 09:35:59 +02:00
action : function ( args , end ) {
2017-06-22 23:52:27 +02:00
if ( ! currentFolder ) {
2017-06-25 09:35:59 +02:00
this . log ( 'Notes can only be created within a notebook.' ) ;
2017-06-22 23:52:27 +02:00
end ( ) ;
return ;
}
let note = {
title : args [ 'note-title' ] ,
parent _id : currentFolder . id ,
} ;
Note . save ( note ) . catch ( ( error ) => {
this . log ( error ) ;
} ) . then ( ( note ) => {
end ( ) ;
} ) ;
} ,
} ) ;
2017-06-25 12:41:03 +02:00
commands . push ( {
usage : 'use <notebook-title>' ,
aliases : [ 'cd' ] ,
description : 'Switches to [notebook-title] - all further operations will happen within this notebook.' ,
action : async function ( args , end ) {
let folderTitle = args [ 'notebook-title' ] ;
let folder = await Folder . loadByField ( 'title' , folderTitle ) ;
if ( ! folder ) return commandError ( this , _ ( 'Invalid folder title: %s' , folderTitle ) , end ) ;
switchCurrentFolder ( folder ) ;
end ( ) ;
} ,
autocomplete : autocompleteFolders ,
} ) ;
2017-06-22 23:52:27 +02:00
commands . push ( {
usage : 'set <item-title> <prop-name> [prop-value]' ,
description : 'Sets the given <prop-name> of the given item.' ,
2017-06-25 09:35:59 +02:00
action : function ( args , end ) {
2017-06-22 23:52:27 +02:00
let promise = null ;
let title = args [ 'item-title' ] ;
let propName = args [ 'prop-name' ] ;
let propValue = args [ 'prop-value' ] ;
if ( ! propValue ) propValue = '' ;
if ( ! currentFolder ) {
promise = Folder . loadByField ( 'title' , title ) ;
} else {
promise = Folder . loadNoteByField ( currentFolder . id , 'title' , title ) ;
}
promise . then ( ( item ) => {
if ( ! item ) {
this . log ( _ ( 'No item with title "%s" found.' , title ) ) ;
end ( ) ;
return ;
}
let newItem = {
id : item . id ,
type _ : item . type _ ,
} ;
newItem [ propName ] = propValue ;
let ItemClass = BaseItem . itemClass ( ) ;
return ItemClass . save ( newItem ) ;
} ) . catch ( ( error ) => {
this . log ( error ) ;
} ) . then ( ( ) => {
end ( ) ;
} ) ;
} ,
autocomplete : autocompleteItems ,
} ) ;
commands . push ( {
usage : 'cat <item-title>' ,
description : 'Displays the given item data.' ,
2017-06-25 09:35:59 +02:00
action : function ( args , end ) {
2017-06-22 23:52:27 +02:00
let title = args [ 'item-title' ] ;
let promise = null ;
if ( ! currentFolder ) {
promise = Folder . loadByField ( 'title' , title ) ;
} else {
promise = Folder . loadNoteByField ( currentFolder . id , 'title' , title ) ;
}
promise . then ( ( item ) => {
if ( ! item ) {
this . log ( _ ( 'No item with title "%s" found.' , title ) ) ;
end ( ) ;
return ;
}
if ( ! currentFolder ) {
this . log ( Folder . serialize ( item ) ) ;
} else {
this . log ( Note . serialize ( item ) ) ;
}
} ) . catch ( ( error ) => {
this . log ( error ) ;
} ) . then ( ( ) => {
end ( ) ;
} ) ;
} ,
autocomplete : autocompleteItems ,
} ) ;
commands . push ( {
usage : 'rm <item-title>' ,
2017-06-25 09:52:25 +02:00
description : 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../<notebook-name>` to delete a notebook.' ,
2017-06-25 09:35:59 +02:00
action : async function ( args , end ) {
2017-06-22 23:52:27 +02:00
let title = args [ 'item-title' ] ;
2017-06-25 09:52:25 +02:00
let itemType = null ;
if ( title . substr ( 0 , 3 ) == '../' ) {
itemType = BaseModel . MODEL _TYPE _FOLDER ;
title = title . substr ( 3 ) ;
} else {
itemType = BaseModel . MODEL _TYPE _NOTE ;
}
2017-06-25 09:35:59 +02:00
let item = await BaseItem . loadItemByField ( itemType , 'title' , title ) ;
if ( ! item ) return commandError ( this , _ ( 'No item with title "%s" found.' , title ) , end ) ;
await BaseItem . deleteItem ( itemType , item . id ) ;
2017-06-25 09:52:25 +02:00
if ( currentFolder && currentFolder . id == item . id ) {
let f = await Folder . defaultFolder ( ) ;
switchCurrentFolder ( f ) ;
}
2017-06-25 09:35:59 +02:00
end ( ) ;
2017-06-22 23:52:27 +02:00
} ,
autocomplete : autocompleteItems ,
} ) ;
commands . push ( {
2017-06-25 09:35:59 +02:00
usage : 'ls [notebook-title]' ,
description : 'Displays the notes in [notebook-title]. Use `ls ..` to display the list of notebooks.' ,
options : [
[ '-n, --lines <num>' , 'Displays only the first top <num> lines.' ] ,
2017-06-25 11:00:54 +02:00
[ '-s, --sort <field>' , 'Sorts the item by <field> (eg. title, updated_time, created_time).' ] ,
[ '-r, --reverse' , 'Reverses the sorting order.' ] ,
[ '-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.' ] ,
2017-06-25 09:35:59 +02:00
] ,
action : async function ( args , end ) {
let folderTitle = args [ 'notebook-title' ] ;
let suffix = '' ;
let items = [ ] ;
2017-06-25 11:00:54 +02:00
let options = args . options ;
let queryOptions = { } ;
if ( options . lines ) queryOptions . limit = options . lines ;
if ( options . sort ) {
queryOptions . orderBy = options . sort ;
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' ) ;
}
2017-06-22 23:52:27 +02:00
if ( folderTitle == '..' ) {
2017-06-25 11:00:54 +02:00
items = await Folder . all ( queryOptions ) ;
2017-06-25 09:35:59 +02:00
suffix = '/' ;
2017-06-22 23:52:27 +02:00
} else {
2017-06-25 09:35:59 +02:00
let folder = null ;
if ( folderTitle ) {
folder = await Folder . loadByField ( 'title' , folderTitle ) ;
} else if ( currentFolder ) {
folder = currentFolder ;
2017-06-22 23:52:27 +02:00
}
2017-06-25 09:35:59 +02:00
if ( ! folder ) return commandError ( this , _ ( 'Unknown notebook: "%s"' , folderTitle ) , end ) ;
2017-06-22 23:52:27 +02:00
2017-06-25 11:00:54 +02:00
items = await Note . previews ( folder . id , queryOptions ) ;
2017-06-25 09:35:59 +02:00
}
2017-06-25 11:00:54 +02:00
2017-06-25 09:35:59 +02:00
for ( let i = 0 ; i < items . length ; i ++ ) {
let item = items [ i ] ;
2017-06-25 11:00:54 +02:00
let line = '' ;
if ( ! ! item . is _todo ) {
line += sprintf ( '[%s] ' , ! ! item . todo _completed ? 'X' : ' ' ) ;
}
line += item . title + suffix ;
this . log ( line ) ;
2017-06-25 09:35:59 +02:00
}
end ( ) ;
2017-06-22 23:52:27 +02:00
} ,
autocomplete : autocompleteFolders ,
} ) ;
commands . push ( {
usage : 'sync' ,
description : 'Synchronizes with remote storage.' ,
2017-06-25 09:35:59 +02:00
action : function ( args , end ) {
2017-06-22 23:52:27 +02:00
synchronizer ( 'onedrive' ) . then ( ( s ) => {
return s . start ( ) ;
} ) . catch ( ( error ) => {
2017-06-23 23:32:24 +02:00
logger . error ( error ) ;
2017-06-22 23:52:27 +02:00
} ) . then ( ( ) => {
end ( ) ;
} ) ;
} ,
} ) ;
2017-06-24 19:40:03 +02:00
commands . push ( {
usage : 'import-enex' ,
2017-06-25 12:41:03 +02:00
description : _ ( 'Imports a .enex file (Evernote notebook file).' ) ,
2017-06-25 09:35:59 +02:00
action : function ( args , end ) {
2017-06-24 19:40:03 +02:00
end ( ) ;
} ,
} ) ;
2017-06-25 09:35:59 +02:00
function commandByName ( name ) {
for ( let i = 0 ; i < commands . length ; i ++ ) {
let c = commands [ i ] ;
let n = c . usage . split ( ' ' ) ;
n = n [ 0 ] ;
if ( n == name ) return c ;
if ( c . aliases && c . aliases . indexOf ( name ) >= 0 ) return c ;
}
return null ;
}
function execCommand ( name , args ) {
return new Promise ( ( resolve , reject ) => {
let cmd = commandByName ( name ) ;
if ( ! cmd ) {
reject ( new Error ( 'Unknown command: ' + name ) ) ;
} else {
cmd . action ( args , function ( ) {
resolve ( ) ;
} ) ;
}
} ) ;
}
for ( let commandIndex = 0 ; commandIndex < commands . length ; commandIndex ++ ) {
let c = commands [ commandIndex ] ;
2017-06-22 23:52:27 +02:00
let o = vorpal . command ( c . usage , c . description ) ;
2017-06-25 09:35:59 +02:00
if ( c . options ) {
for ( let i = 0 ; i < c . options . length ; i ++ ) {
let options = c . options [ i ] ;
if ( options . length == 2 ) o . option ( options [ 0 ] , options [ 1 ] ) ;
if ( options . length == 3 ) o . option ( options [ 0 ] , options [ 1 ] , options [ 2 ] ) ;
}
}
if ( c . aliases ) {
for ( let i = 0 ; i < c . aliases . length ; i ++ ) {
o . alias ( c . aliases [ i ] ) ;
}
2017-06-22 23:52:27 +02:00
}
if ( c . autocomplete ) {
o . autocomplete ( {
data : c . autocomplete ,
} ) ;
}
o . action ( c . action ) ;
}
2017-06-25 09:35:59 +02:00
vorpal . history ( 'net.cozic.joplin' ) ; // Enables persistent history
2017-06-25 12:41:03 +02:00
let activeFolderId = Setting . value ( 'activeFolderId' ) ;
let activeFolder = null ;
if ( activeFolderId ) activeFolder = await Folder . load ( activeFolderId ) ;
if ( ! activeFolder ) activeFolder = await Folder . defaultFolder ( ) ;
if ( activeFolder ) await execCommand ( 'cd' , { 'notebook-title' : activeFolder . title } ) ; // Use execCommand() so that no history entry is created
2017-06-25 09:35:59 +02:00
2017-06-22 23:52:27 +02:00
vorpal . delimiter ( promptString ( ) ) . show ( ) ;
2017-06-25 09:35:59 +02:00
vorpal . on ( 'client_prompt_submit' , function ( cmd ) {
// Called when command is started
} ) ;
vorpal . on ( 'client_command_executed' , function ( cmd ) {
// Called when command is finished
} ) ;
2017-06-22 23:52:27 +02:00
}
main ( ) . catch ( ( error ) => {
console . error ( 'Fatal error: ' , error ) ;
} ) ;