2017-10-08 00:17:10 +02:00
import { createStore , applyMiddleware } from 'redux' ;
2017-10-07 23:01:03 +02:00
import { reducer , defaultState } from 'lib/reducer.js' ;
2017-07-10 22:03:46 +02:00
import { JoplinDatabase } from 'lib/joplin-database.js' ;
import { Database } from 'lib/database.js' ;
2017-10-08 00:17:10 +02:00
import { FoldersScreenUtils } from 'lib/folders-screen-utils.js' ;
2017-07-10 22:03:46 +02:00
import { DatabaseDriverNode } from 'lib/database-driver-node.js' ;
import { BaseModel } from 'lib/base-model.js' ;
import { Folder } from 'lib/models/folder.js' ;
import { BaseItem } from 'lib/models/base-item.js' ;
import { Note } from 'lib/models/note.js' ;
2017-10-22 19:12:16 +02:00
import { Tag } from 'lib/models/tag.js' ;
2017-07-10 22:03:46 +02:00
import { Setting } from 'lib/models/setting.js' ;
import { Logger } from 'lib/logger.js' ;
import { sprintf } from 'sprintf-js' ;
import { reg } from 'lib/registry.js' ;
import { fileExtension } from 'lib/path-utils.js' ;
2017-07-25 20:57:06 +02:00
import { _ , setLocale , defaultLocale , closestSupportedLocale } from 'lib/locale.js' ;
2017-07-10 22:03:46 +02:00
import os from 'os' ;
import fs from 'fs-extra' ;
2017-08-03 19:48:14 +02:00
import yargParser from 'yargs-parser' ;
2017-08-04 18:02:43 +02:00
import { handleAutocompletion , installAutocompletionFile } from './autocompletion.js' ;
2017-08-04 19:51:01 +02:00
import { cliUtils } from './cli-utils.js' ;
2017-10-06 19:38:17 +02:00
const EventEmitter = require ( 'events' ) ;
2017-07-10 22:03:46 +02:00
class Application {
constructor ( ) {
this . showPromptString _ = true ;
this . logger _ = new Logger ( ) ;
this . dbLogger _ = new Logger ( ) ;
2017-08-04 19:51:01 +02:00
this . autocompletion _ = { active : false } ;
this . commands _ = { } ;
this . commandMetadata _ = null ;
2017-08-04 18:50:12 +02:00
this . activeCommand _ = null ;
2017-08-04 19:11:10 +02:00
this . allCommandsLoaded _ = false ;
2017-08-21 20:32:43 +02:00
this . showStackTraces _ = false ;
2017-10-05 19:17:56 +02:00
this . gui _ = null ;
2017-10-06 19:38:17 +02:00
this . eventEmitter _ = new EventEmitter ( ) ;
2017-10-24 20:09:01 +02:00
// Note: this is basically a cache of state.selectedFolderId. It should *only*
// be derived from the state and not set directly since that would make the
// state and UI out of sync.
this . currentFolder _ = null ;
2017-07-10 22:03:46 +02:00
}
2017-10-09 00:34:01 +02:00
gui ( ) {
return this . gui _ ;
}
2017-10-07 18:30:27 +02:00
logger ( ) {
return this . logger _ ;
}
2017-10-08 00:17:10 +02:00
store ( ) {
return this . store _ ;
}
2017-07-10 22:03:46 +02:00
currentFolder ( ) {
return this . currentFolder _ ;
}
2017-10-09 00:34:01 +02:00
commandStdoutMaxWidth ( ) {
2017-10-24 22:22:57 +02:00
return this . gui ( ) . stdoutMaxWidth ( ) ;
2017-10-09 00:34:01 +02:00
}
2017-07-16 00:47:11 +02:00
async refreshCurrentFolder ( ) {
let newFolder = null ;
if ( this . currentFolder _ ) newFolder = await Folder . load ( this . currentFolder _ . id ) ;
if ( ! newFolder ) newFolder = await Folder . defaultFolder ( ) ;
this . switchCurrentFolder ( newFolder ) ;
}
2017-07-10 22:03:46 +02:00
switchCurrentFolder ( folder ) {
2017-10-21 19:01:02 +02:00
this . dispatch ( {
type : 'FOLDERS_SELECT' ,
2017-10-23 23:48:29 +02:00
id : folder ? folder . id : '' ,
2017-10-21 19:01:02 +02:00
} ) ;
2017-07-10 22:03:46 +02:00
}
2017-07-17 21:19:01 +02:00
async guessTypeAndLoadItem ( pattern , options = null ) {
let type = BaseModel . TYPE _NOTE ;
if ( pattern . indexOf ( '/' ) === 0 ) {
type = BaseModel . TYPE _FOLDER ;
pattern = pattern . substr ( 1 ) ;
}
return this . loadItem ( type , pattern , options ) ;
}
2017-07-15 17:35:40 +02:00
async loadItem ( type , pattern , options = null ) {
let output = await this . loadItems ( type , pattern , options ) ;
2017-09-10 19:32:04 +02:00
if ( output . length > 1 ) {
2017-10-27 00:22:36 +02:00
// output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; });
2017-09-24 16:48:23 +02:00
2017-10-27 00:22:36 +02:00
// let answers = { 0: _('[Cancel]') };
// for (let i = 0; i < output.length; i++) {
// answers[i + 1] = output[i].title;
// }
2017-09-24 16:48:23 +02:00
2017-10-08 19:50:43 +02:00
// Not really useful with new UI?
throw new Error ( _ ( 'More than one item match "%s". Please narrow down your query.' , pattern ) ) ;
// let msg = _('More than one item match "%s". Please select one:', pattern);
// const response = await cliUtils.promptMcq(msg, answers);
// if (!response) return null;
2017-09-10 19:32:04 +02:00
return output [ response - 1 ] ;
} else {
return output . length ? output [ 0 ] : null ;
}
2017-07-10 22:03:46 +02:00
}
2017-07-11 20:17:23 +02:00
async loadItems ( type , pattern , options = null ) {
2017-10-27 00:22:36 +02:00
if ( type === 'folderOrNote' ) {
const folders = await this . loadItems ( BaseModel . TYPE _FOLDER , pattern , options ) ;
if ( folders . length ) return folders ;
return await this . loadItems ( BaseModel . TYPE _NOTE , pattern , options ) ;
}
2017-07-22 20:16:16 +02:00
pattern = pattern ? pattern . toString ( ) : '' ;
2017-07-15 17:35:40 +02:00
if ( type == BaseModel . TYPE _FOLDER && ( pattern == Folder . conflictFolderTitle ( ) || pattern == Folder . conflictFolderId ( ) ) ) return [ Folder . conflictFolder ( ) ] ;
2017-07-11 20:17:23 +02:00
if ( ! options ) options = { } ;
2017-07-13 23:26:45 +02:00
2017-07-11 20:17:23 +02:00
const parent = options . parent ? options . parent : app ( ) . currentFolder ( ) ;
const ItemClass = BaseItem . itemClass ( type ) ;
if ( type == BaseModel . TYPE _NOTE && pattern . indexOf ( '*' ) >= 0 ) { // Handle it as pattern
if ( ! parent ) throw new Error ( _ ( 'No notebook selected.' ) ) ;
return await Note . previews ( parent . id , { titlePattern : pattern } ) ;
} else { // Single item
let item = null ;
if ( type == BaseModel . TYPE _NOTE ) {
if ( ! parent ) throw new Error ( _ ( 'No notebook has been specified.' ) ) ;
item = await ItemClass . loadFolderNoteByField ( parent . id , 'title' , pattern ) ;
} else {
item = await ItemClass . loadByTitle ( pattern ) ;
}
if ( item ) return [ item ] ;
item = await ItemClass . load ( pattern ) ; // Load by id
if ( item ) return [ item ] ;
2017-09-10 19:32:04 +02:00
if ( pattern . length >= 2 ) {
return await ItemClass . loadByPartialId ( pattern ) ;
2017-07-11 20:17:23 +02:00
}
2017-07-10 22:47:01 +02:00
}
return [ ] ;
2017-07-10 22:03:46 +02:00
}
// Handles the initial flags passed to main script and
// returns the remaining args.
async handleStartFlags _ ( argv ) {
let matched = { } ;
argv = argv . slice ( 0 ) ;
argv . splice ( 0 , 2 ) ; // First arguments are the node executable, and the node JS file
while ( argv . length ) {
let arg = argv [ 0 ] ;
let nextArg = argv . length >= 2 ? argv [ 1 ] : null ;
if ( arg == '--profile' ) {
2017-07-28 20:13:07 +02:00
if ( ! nextArg ) throw new Error ( _ ( 'Usage: %s' , '--profile <dir-path>' ) ) ;
2017-07-10 22:03:46 +02:00
matched . profileDir = nextArg ;
argv . splice ( 0 , 2 ) ;
continue ;
}
if ( arg == '--env' ) {
2017-07-28 20:13:07 +02:00
if ( ! nextArg ) throw new Error ( _ ( 'Usage: %s' , '--env <dev|prod>' ) ) ;
2017-07-10 22:03:46 +02:00
matched . env = nextArg ;
argv . splice ( 0 , 2 ) ;
continue ;
}
2017-07-10 22:59:58 +02:00
if ( arg == '--update-geolocation-disabled' ) {
Note . updateGeolocationEnabled _ = false ;
argv . splice ( 0 , 1 ) ;
continue ;
}
2017-07-10 22:03:46 +02:00
if ( arg == '--stack-trace-enabled' ) {
2017-08-21 20:32:43 +02:00
this . showStackTraces _ = true ;
2017-07-10 22:03:46 +02:00
argv . splice ( 0 , 1 ) ;
continue ;
}
if ( arg == '--log-level' ) {
2017-07-28 20:13:07 +02:00
if ( ! nextArg ) throw new Error ( _ ( 'Usage: %s' , '--log-level <none|error|warn|info|debug>' ) ) ;
2017-07-10 22:03:46 +02:00
matched . logLevel = Logger . levelStringToId ( nextArg ) ;
argv . splice ( 0 , 2 ) ;
continue ;
}
2017-08-04 19:51:01 +02:00
if ( arg == '--autocompletion' ) {
this . autocompletion _ . active = true ;
argv . splice ( 0 , 1 ) ;
continue ;
}
2017-08-04 18:02:43 +02:00
if ( arg == '--ac-install' ) {
this . autocompletion _ . install = true ;
argv . splice ( 0 , 1 ) ;
continue ;
}
2017-08-04 19:51:01 +02:00
if ( arg == '--ac-current' ) {
if ( ! nextArg ) throw new Error ( _ ( 'Usage: %s' , '--ac-current <num>' ) ) ;
this . autocompletion _ . current = nextArg ;
argv . splice ( 0 , 2 ) ;
continue ;
}
if ( arg == '--ac-line' ) {
if ( ! nextArg ) throw new Error ( _ ( 'Usage: %s' , '--ac-line <line>' ) ) ;
let line = nextArg . replace ( /\|__QUOTE__\|/g , '"' ) ;
2017-08-04 22:13:29 +02:00
line = line . replace ( /\|__SPACE__\|/g , ' ' ) ;
line = line . replace ( /\|__OPEN_RB__\|/g , '(' ) ;
line = line . replace ( /\|__OPEN_CB__\|/g , ')' ) ;
2017-08-04 19:51:01 +02:00
line = line . split ( '|__SEP__|' ) ;
this . autocompletion _ . line = line ;
argv . splice ( 0 , 2 ) ;
continue ;
}
2017-07-10 22:03:46 +02:00
if ( arg . length && arg [ 0 ] == '-' ) {
throw new Error ( _ ( 'Unknown flag: %s' , arg ) ) ;
} else {
break ;
}
}
if ( ! matched . logLevel ) matched . logLevel = Logger . LEVEL _INFO ;
2017-07-11 01:17:03 +02:00
if ( ! matched . env ) matched . env = 'prod' ;
2017-07-10 22:03:46 +02:00
return {
matched : matched ,
argv : argv ,
} ;
}
escapeShellArg ( arg ) {
if ( arg . indexOf ( '"' ) >= 0 && arg . indexOf ( "'" ) >= 0 ) throw new Error ( _ ( 'Command line argument "%s" contains both quotes and double-quotes - aborting.' , arg ) ) ; // Hopeless case
let quote = '"' ;
if ( arg . indexOf ( '"' ) >= 0 ) quote = "'" ;
if ( arg . indexOf ( ' ' ) >= 0 || arg . indexOf ( "\t" ) >= 0 ) return quote + arg + quote ;
return arg ;
}
shellArgsToString ( args ) {
let output = [ ] ;
for ( let i = 0 ; i < args . length ; i ++ ) {
output . push ( this . escapeShellArg ( args [ i ] ) ) ;
}
return output . join ( ' ' ) ;
}
2017-07-18 20:49:47 +02:00
onLocaleChanged ( ) {
2017-08-03 19:48:14 +02:00
return ;
2017-07-18 20:49:47 +02:00
let currentCommands = this . vorpal ( ) . commands ;
for ( let i = 0 ; i < currentCommands . length ; i ++ ) {
let cmd = currentCommands [ i ] ;
if ( cmd . _name == 'help' ) {
cmd . description ( _ ( 'Provides help for a given command.' ) ) ;
} else if ( cmd . _name == 'exit' ) {
cmd . description ( _ ( 'Exits the application.' ) ) ;
} else if ( cmd . _ _commandObject ) {
cmd . description ( cmd . _ _commandObject . description ( ) ) ;
}
}
}
2017-08-03 19:48:14 +02:00
2017-08-04 19:51:01 +02:00
baseModelListener ( action ) {
2017-10-06 19:38:17 +02:00
this . eventEmitter _ . emit ( 'modelAction' , { action : action } ) ;
}
on ( eventName , callback ) {
return this . eventEmitter _ . on ( eventName , callback ) ;
2017-08-04 19:51:01 +02:00
}
2017-10-20 00:02:13 +02:00
stdout ( text ) {
return this . gui ( ) . stdout ( text ) ;
}
2017-10-07 19:07:38 +02:00
setupCommand ( cmd ) {
2017-10-17 19:18:31 +02:00
cmd . setStdout ( ( text ) => {
2017-10-20 00:02:13 +02:00
return this . stdout ( text ) ;
2017-10-07 19:07:38 +02:00
} ) ;
2017-10-14 23:44:50 +02:00
cmd . setDispatcher ( ( action ) => {
2017-10-20 00:02:13 +02:00
if ( this . store ( ) ) {
return this . store ( ) . dispatch ( action ) ;
} else {
return ( action ) => { } ;
}
2017-10-14 20:03:23 +02:00
} ) ;
2017-10-07 19:07:38 +02:00
cmd . setPrompt ( async ( message , options ) => {
if ( options . type == 'boolean' ) {
message += ' (' + options . answers . join ( '/' ) + ')' ;
}
2017-10-20 00:02:13 +02:00
const answer = await this . gui ( ) . prompt ( '' , message + ' ' ) ;
2017-10-15 18:57:09 +02:00
if ( options . type === 'boolean' ) {
2017-10-07 19:07:38 +02:00
if ( answer === null ) return false ;
2017-10-23 23:48:29 +02:00
let output = false ;
if ( answer === '' || answer . toLowerCase ( ) == options . answers [ 0 ] . toLowerCase ( ) ) {
output = true ;
}
return options . booleanAnswerDefault === 'y' ? output : ! output ;
2017-10-07 19:07:38 +02:00
}
} ) ;
return cmd ;
}
2017-10-09 22:29:49 +02:00
async exit ( code = 0 ) {
2017-10-24 21:40:15 +02:00
const doExit = async ( ) => {
await Setting . saveAll ( ) ;
this . gui ( ) . exit ( ) ;
process . exit ( code ) ;
} ;
// Give it a few seconds to cancel otherwise exit anyway
setTimeout ( async ( ) => {
await doExit ( ) ;
} , 5000 ) ;
if ( await reg . syncStarted ( ) ) {
this . stdout ( _ ( 'Cancelling background synchronisation... Please wait.' ) ) ;
const sync = await reg . synchronizer ( Setting . value ( 'sync.target' ) ) ;
await sync . cancel ( ) ;
}
await doExit ( ) ;
2017-10-09 22:29:49 +02:00
}
2017-08-04 19:11:10 +02:00
commands ( ) {
if ( this . allCommandsLoaded _ ) return this . commands _ ;
2017-07-10 22:03:46 +02:00
fs . readdirSync ( _ _dirname ) . forEach ( ( path ) => {
if ( path . indexOf ( 'command-' ) !== 0 ) return ;
const ext = fileExtension ( path )
if ( ext != 'js' ) return ;
let CommandClass = require ( './' + path ) ;
let cmd = new CommandClass ( ) ;
2017-07-19 00:14:20 +02:00
if ( ! cmd . enabled ( ) ) return ;
2017-10-07 19:07:38 +02:00
cmd = this . setupCommand ( cmd ) ;
2017-08-04 19:51:01 +02:00
this . commands _ [ cmd . name ( ) ] = cmd ;
2017-07-10 22:03:46 +02:00
} ) ;
2017-08-04 19:11:10 +02:00
this . allCommandsLoaded _ = true ;
return this . commands _ ;
}
async commandNames ( ) {
const metadata = await this . commandMetadata ( ) ;
let output = [ ] ;
for ( let n in metadata ) {
if ( ! metadata . hasOwnProperty ( n ) ) continue ;
output . push ( n ) ;
}
return output ;
2017-07-10 22:03:46 +02:00
}
2017-08-04 19:51:01 +02:00
async commandMetadata ( ) {
if ( this . commandMetadata _ ) return this . commandMetadata _ ;
2017-07-24 22:36:49 +02:00
2017-08-04 22:13:29 +02:00
const osTmpdir = require ( 'os-tmpdir' ) ;
2017-08-04 19:51:01 +02:00
const storage = require ( 'node-persist' ) ;
2017-08-04 22:13:29 +02:00
await storage . init ( { dir : osTmpdir ( ) + '/commandMetadata' , ttl : 1000 * 60 * 60 * 24 } ) ;
2017-07-24 22:36:49 +02:00
2017-08-04 19:51:01 +02:00
let output = await storage . getItem ( 'metadata' ) ;
if ( Setting . value ( 'env' ) != 'dev' && output ) {
this . commandMetadata _ = output ;
return Object . assign ( { } , this . commandMetadata _ ) ;
}
2017-08-04 19:11:10 +02:00
const commands = this . commands ( ) ;
2017-07-24 22:36:49 +02:00
2017-08-04 19:51:01 +02:00
output = { } ;
2017-08-04 19:11:10 +02:00
for ( let n in commands ) {
if ( ! commands . hasOwnProperty ( n ) ) continue ;
const cmd = commands [ n ] ;
2017-08-04 19:51:01 +02:00
output [ n ] = cmd . metadata ( ) ;
2017-07-24 22:36:49 +02:00
}
2017-08-04 19:51:01 +02:00
await storage . setItem ( 'metadata' , output ) ;
this . commandMetadata _ = output ;
return Object . assign ( { } , this . commandMetadata _ ) ;
2017-07-24 22:36:49 +02:00
}
2017-08-03 19:48:14 +02:00
findCommandByName ( name ) {
2017-08-04 19:11:10 +02:00
if ( this . commands _ [ name ] ) return this . commands _ [ name ] ;
2017-08-03 19:48:14 +02:00
let CommandClass = null ;
try {
2017-08-18 22:46:39 +02:00
CommandClass = require ( _ _dirname + '/command-' + name + '.js' ) ;
2017-08-03 19:48:14 +02:00
} catch ( error ) {
let e = new Error ( 'No such command: ' + name ) ;
e . type = 'notFound' ;
throw e ;
}
2017-10-06 19:38:17 +02:00
2017-08-03 19:48:14 +02:00
let cmd = new CommandClass ( ) ;
2017-10-07 19:07:38 +02:00
cmd = this . setupCommand ( cmd ) ;
2017-08-04 19:11:10 +02:00
this . commands _ [ name ] = cmd ;
return this . commands _ [ name ] ;
2017-08-03 19:48:14 +02:00
}
2017-10-20 00:02:13 +02:00
dummyGui ( ) {
return {
2017-10-24 23:37:36 +02:00
isDummy : ( ) => { return true ; } ,
2017-10-20 00:02:13 +02:00
prompt : ( initialText = '' , promptString = '' ) => { return cliUtils . prompt ( initialText , promptString ) ; } ,
showConsole : ( ) => { } ,
maximizeConsole : ( ) => { } ,
stdout : ( text ) => { console . info ( text ) ; } ,
fullScreen : ( b = true ) => { } ,
2017-10-24 21:40:15 +02:00
exit : ( ) => { } ,
2017-10-24 21:52:26 +02:00
showModalOverlay : ( text ) => { } ,
hideModalOverlay : ( ) => { } ,
2017-10-24 22:22:57 +02:00
stdoutMaxWidth : ( ) => { return 78 ; }
2017-10-20 00:02:13 +02:00
} ;
}
2017-08-03 19:48:14 +02:00
async execCommand ( argv ) {
2017-08-04 22:13:29 +02:00
if ( ! argv . length ) return this . execCommand ( [ 'help' ] ) ;
2017-10-07 18:30:27 +02:00
reg . logger ( ) . info ( 'execCommand()' , argv ) ;
2017-08-03 19:48:14 +02:00
const commandName = argv [ 0 ] ;
2017-08-04 18:50:12 +02:00
this . activeCommand _ = this . findCommandByName ( commandName ) ;
2017-10-24 23:37:36 +02:00
2017-10-17 19:18:31 +02:00
let outException = null ;
try {
2017-10-24 23:37:36 +02:00
if ( this . gui ( ) . isDummy ( ) && ! this . activeCommand _ . supportsUi ( 'cli' ) ) throw new Error ( _ ( 'The command "%s" is only available in GUI mode' , this . activeCommand _ . name ( ) ) ) ;
2017-10-23 23:48:29 +02:00
const cmdArgs = cliUtils . makeCommandArgs ( this . activeCommand _ , argv ) ;
2017-10-17 19:18:31 +02:00
await this . activeCommand _ . action ( cmdArgs ) ;
} catch ( error ) {
outException = error ;
}
2017-10-14 20:03:23 +02:00
this . activeCommand _ = null ;
2017-10-17 19:18:31 +02:00
if ( outException ) throw outException ;
2017-08-04 18:50:12 +02:00
}
2017-08-20 16:29:18 +02:00
currentCommand ( ) {
return this . activeCommand _ ;
2017-08-03 19:48:14 +02:00
}
2017-10-22 19:12:16 +02:00
async refreshNotes ( parentType , parentId ) {
2017-10-23 23:48:29 +02:00
this . logger ( ) . debug ( 'Refreshing notes:' , parentType , parentId ) ;
2017-10-08 00:17:10 +02:00
const state = this . store ( ) . getState ( ) ;
let options = {
order : state . notesOrder ,
uncompletedTodosOnTop : Setting . value ( 'uncompletedTodosOnTop' ) ,
} ;
2017-10-22 19:12:16 +02:00
const source = JSON . stringify ( {
options : options ,
parentId : parentId ,
} ) ;
2017-10-23 22:34:04 +02:00
let notes = [ ] ;
2017-10-23 23:48:29 +02:00
if ( parentId ) {
if ( parentType === Folder . modelType ( ) ) {
notes = await Note . previews ( parentId , options ) ;
} else if ( parentType === Tag . modelType ( ) ) {
notes = await Tag . notes ( parentId ) ;
} else if ( parentType === BaseModel . TYPE _SEARCH ) {
let fields = Note . previewFields ( ) ;
let search = BaseModel . byId ( state . searches , parentId ) ;
notes = await Note . previews ( null , {
fields : fields ,
anywherePattern : '*' + search . query _pattern + '*' ,
} ) ;
}
2017-10-23 22:34:04 +02:00
}
2017-10-08 00:17:10 +02:00
this . store ( ) . dispatch ( {
type : 'NOTES_UPDATE_ALL' ,
notes : notes ,
2017-10-22 19:12:16 +02:00
notesSource : source ,
2017-10-08 00:17:10 +02:00
} ) ;
this . store ( ) . dispatch ( {
type : 'NOTES_SELECT' ,
noteId : notes . length ? notes [ 0 ] . id : null ,
} ) ;
}
2017-10-19 00:13:53 +02:00
reducerActionToString ( action ) {
let o = [ action . type ] ;
2017-10-24 00:11:50 +02:00
if ( action . id ) o . push ( action . id ) ;
2017-10-19 00:13:53 +02:00
if ( action . noteId ) o . push ( action . noteId ) ;
2017-10-22 19:12:16 +02:00
if ( action . folderId ) o . push ( action . folderId ) ;
if ( action . tagId ) o . push ( action . tagId ) ;
if ( action . tag ) o . push ( action . tag . id ) ;
if ( action . folder ) o . push ( action . folder . id ) ;
if ( action . notesSource ) o . push ( JSON . stringify ( action . notesSource ) ) ;
2017-10-19 00:13:53 +02:00
return o . join ( ', ' ) ;
}
2017-10-08 00:17:10 +02:00
generalMiddleware ( ) {
const middleware = store => next => async ( action ) => {
2017-10-22 19:12:16 +02:00
this . logger ( ) . debug ( 'Reducer action' , this . reducerActionToString ( action ) ) ;
2017-10-08 00:17:10 +02:00
const result = next ( action ) ;
const newState = store . getState ( ) ;
if ( action . type == 'FOLDERS_SELECT' ) {
Setting . setValue ( 'activeFolderId' , newState . selectedFolderId ) ;
2017-10-24 20:09:01 +02:00
this . currentFolder _ = newState . selectedFolderId ? await Folder . load ( newState . selectedFolderId ) : null ;
2017-10-22 19:12:16 +02:00
await this . refreshNotes ( Folder . modelType ( ) , newState . selectedFolderId ) ;
}
if ( action . type == 'TAGS_SELECT' ) {
2017-10-24 00:11:50 +02:00
await this . refreshNotes ( Tag . modelType ( ) , action . id ) ;
2017-10-08 00:17:10 +02:00
}
2017-10-23 22:34:04 +02:00
if ( action . type == 'SEARCH_SELECT' ) {
2017-10-23 23:48:29 +02:00
await this . refreshNotes ( BaseModel . TYPE _SEARCH , action . id ) ;
2017-10-23 22:34:04 +02:00
}
2017-10-19 00:13:53 +02:00
if ( this . gui ( ) && action . type == 'SETTINGS_UPDATE_ONE' && action . key == 'sync.interval' || action . type == 'SETTINGS_UPDATE_ALL' ) {
reg . setupRecurrentSync ( ) ;
}
2017-10-08 00:17:10 +02:00
return result ;
}
return middleware ;
}
2017-10-21 19:01:02 +02:00
dispatch ( action ) {
if ( this . store ( ) ) return this . store ( ) . dispatch ( action ) ;
}
2017-08-03 19:48:14 +02:00
async start ( ) {
2017-07-10 22:03:46 +02:00
let argv = process . argv ;
let startFlags = await this . handleStartFlags _ ( argv ) ;
argv = startFlags . argv ;
let initArgs = startFlags . matched ;
if ( argv . length ) this . showPromptString _ = false ;
2017-08-20 16:29:18 +02:00
if ( process . argv [ 1 ] . indexOf ( 'joplindev' ) >= 0 ) {
if ( ! initArgs . profileDir ) initArgs . profileDir = '/mnt/d/Temp/TestNotes2' ;
initArgs . logLevel = Logger . LEVEL _DEBUG ;
initArgs . env = 'dev' ;
}
2017-08-04 18:02:43 +02:00
Setting . setConstant ( 'appName' , initArgs . env == 'dev' ? 'joplindev' : 'joplin' ) ;
2017-07-10 22:03:46 +02:00
const profileDir = initArgs . profileDir ? initArgs . profileDir : os . homedir ( ) + '/.config/' + Setting . value ( 'appName' ) ;
const resourceDir = profileDir + '/resources' ;
const tempDir = profileDir + '/tmp' ;
Setting . setConstant ( 'env' , initArgs . env ) ;
Setting . setConstant ( 'profileDir' , profileDir ) ;
Setting . setConstant ( 'resourceDir' , resourceDir ) ;
Setting . setConstant ( 'tempDir' , tempDir ) ;
await fs . mkdirp ( profileDir , 0o755 ) ;
await fs . mkdirp ( resourceDir , 0o755 ) ;
await fs . mkdirp ( tempDir , 0o755 ) ;
this . logger _ . addTarget ( 'file' , { path : profileDir + '/log.txt' } ) ;
this . logger _ . setLevel ( initArgs . logLevel ) ;
reg . setLogger ( this . logger _ ) ;
2017-07-24 22:36:49 +02:00
reg . dispatch = ( o ) => { } ;
2017-07-10 22:03:46 +02:00
this . dbLogger _ . addTarget ( 'file' , { path : profileDir + '/log-database.txt' } ) ;
2017-07-23 16:11:44 +02:00
this . dbLogger _ . setLevel ( initArgs . logLevel ) ;
2017-07-10 22:03:46 +02:00
2017-10-08 00:17:10 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2017-10-22 19:12:16 +02:00
this . dbLogger _ . setLevel ( Logger . LEVEL _WARN ) ;
2017-10-08 00:17:10 +02:00
}
2017-07-10 22:03:46 +02:00
const packageJson = require ( './package.json' ) ;
this . logger _ . info ( sprintf ( 'Starting %s %s (%s)...' , packageJson . name , packageJson . version , Setting . value ( 'env' ) ) ) ;
this . logger _ . info ( 'Profile directory: ' + profileDir ) ;
this . database _ = new JoplinDatabase ( new DatabaseDriverNode ( ) ) ;
2017-10-09 22:29:49 +02:00
//this.database_.setLogExcludedQueryTypes(['SELECT']);
2017-07-10 22:03:46 +02:00
this . database _ . setLogger ( this . dbLogger _ ) ;
await this . database _ . open ( { name : profileDir + '/database.sqlite' } ) ;
2017-07-24 20:01:40 +02:00
reg . setDb ( this . database _ ) ;
2017-07-10 22:03:46 +02:00
BaseModel . db _ = this . database _ ;
2017-07-24 20:01:40 +02:00
2017-10-25 19:41:36 +02:00
cliUtils . setStdout ( ( object ) => {
return this . stdout ( object ) ;
} ) ;
2017-10-20 00:02:13 +02:00
// this.store_ = createStore(reducer, applyMiddleware(this.generalMiddleware()));
// BaseModel.dispatch = this.store().dispatch;
// FoldersScreenUtils.dispatch = this.store().dispatch;
2017-10-09 22:29:49 +02:00
2017-07-10 22:03:46 +02:00
await Setting . load ( ) ;
2017-07-25 20:57:06 +02:00
if ( Setting . value ( 'firstStart' ) ) {
let locale = process . env . LANG ;
if ( ! locale ) locale = defaultLocale ( ) ;
locale = locale . split ( '.' ) ;
locale = locale [ 0 ] ;
reg . logger ( ) . info ( 'First start: detected locale as ' + locale ) ;
Setting . setValue ( 'locale' , closestSupportedLocale ( locale ) ) ;
Setting . setValue ( 'firstStart' , 0 )
}
2017-07-18 20:04:47 +02:00
setLocale ( Setting . value ( 'locale' ) ) ;
2017-07-10 22:03:46 +02:00
let currentFolderId = Setting . value ( 'activeFolderId' ) ;
2017-10-24 20:09:01 +02:00
let currentFolder = null ;
if ( currentFolderId ) currentFolder = await Folder . load ( currentFolderId ) ;
if ( ! currentFolder ) currentFolder = await Folder . defaultFolder ( ) ;
Setting . setValue ( 'activeFolderId' , currentFolder ? currentFolder . id : '' ) ;
2017-10-20 00:02:13 +02:00
// If we have some arguments left at this point, it's a command
// so execute it.
if ( argv . length ) {
this . gui _ = this . dummyGui ( ) ;
try {
await this . execCommand ( argv ) ;
} catch ( error ) {
if ( this . showStackTraces _ ) {
console . info ( error ) ;
} else {
console . info ( error . message ) ;
}
}
} else { // Otherwise open the GUI
this . store _ = createStore ( reducer , applyMiddleware ( this . generalMiddleware ( ) ) ) ;
BaseModel . dispatch = this . store ( ) . dispatch ;
FoldersScreenUtils . dispatch = this . store ( ) . dispatch ;
const AppGui = require ( './app-gui.js' ) ;
this . gui _ = new AppGui ( this , this . store ( ) ) ;
this . gui _ . setLogger ( this . logger _ ) ;
await this . gui _ . start ( ) ;
2017-10-21 18:53:43 +02:00
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTINGS_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting . dispatchUpdateAll ( ) ;
2017-10-20 00:02:13 +02:00
await FoldersScreenUtils . refreshFolders ( ) ;
2017-10-22 19:12:16 +02:00
const tags = await Tag . allWithNotes ( ) ;
this . dispatch ( {
type : 'TAGS_UPDATE_ALL' ,
tags : tags ,
} ) ;
2017-10-20 00:02:13 +02:00
this . store ( ) . dispatch ( {
type : 'FOLDERS_SELECT' ,
2017-10-23 23:48:29 +02:00
id : Setting . value ( 'activeFolderId' ) ,
2017-10-20 00:02:13 +02:00
} ) ;
}
2017-07-10 22:03:46 +02:00
}
}
let application _ = null ;
function app ( ) {
if ( application _ ) return application _ ;
application _ = new Application ( ) ;
return application _ ;
}
export { app } ;