2018-02-28 22:53:06 +01:00
'use strict' ;
const recursive = require ( 'recursive-readdir' ) ;
2020-06-23 22:31:49 +02:00
const minimatch = require ( "minimatch" ) ;
2018-02-28 22:53:06 +01:00
const ora = require ( 'ora' ) ;
const inquirer = require ( 'inquirer' ) ;
const preferences = require ( 'preferences' ) ;
const fs = require ( 'fs' ) ;
2018-03-05 14:36:30 +01:00
const argv = require ( 'minimist' ) ( process . argv ) ;
2018-03-28 16:48:16 +02:00
const _ = require ( 'underscore' ) ;
2018-02-28 22:53:06 +01:00
const request = require ( 'request' ) . defaults ( {
jar : true
} ) ;
2020-05-22 15:18:19 +02:00
const qs = require ( 'querystring' ) ;
2018-02-28 22:53:06 +01:00
// Load preferences
const prefs = new preferences ( 'com.sismics.docs.importer' , {
2018-03-01 12:28:29 +01:00
importer : {
daemon : false
}
2018-02-28 22:53:06 +01:00
} , {
encrypt : false ,
format : 'yaml'
} ) ;
// Welcome message
2021-01-25 21:27:22 +01:00
console . log ( 'Teedy Importer 1.9, https://teedy.io' +
2018-02-28 22:53:06 +01:00
'\n\n' +
2019-02-12 13:57:54 +01:00
'This program let you import files from your system to Teedy' +
2018-02-28 22:53:06 +01:00
'\n' ) ;
// Ask for the base URL
const askBaseUrl = ( ) => {
inquirer . prompt ( [
{
type : 'input' ,
name : 'baseUrl' ,
2019-02-12 13:57:54 +01:00
message : 'What is the base URL of your Teedy? (eg. https://teedy.mycompany.com)' ,
2018-02-28 22:53:06 +01:00
default : prefs . importer . baseUrl
}
] ) . then ( answers => {
// Save base URL
prefs . importer . baseUrl = answers . baseUrl ;
// Test base URL
const spinner = ora ( {
2019-02-12 13:57:54 +01:00
text : 'Checking connection to Teedy' ,
2018-02-28 22:53:06 +01:00
spinner : 'flips'
} ) . start ( ) ;
request ( answers . baseUrl + '/api/app' , function ( error , response ) {
if ( ! response || response . statusCode !== 200 ) {
2019-02-12 13:57:54 +01:00
spinner . fail ( 'Connection to Teedy failed: ' + error ) ;
2018-02-28 22:53:06 +01:00
askBaseUrl ( ) ;
return ;
}
spinner . succeed ( 'Connection OK' ) ;
askCredentials ( ) ;
} ) ;
} ) ;
} ;
// Ask for credentials
const askCredentials = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'input' ,
name : 'username' ,
message : 'Account\'s username?' ,
default : prefs . importer . username
} ,
{
type : 'password' ,
name : 'password' ,
message : 'Account\'s password?' ,
default : prefs . importer . password
}
] ) . then ( answers => {
// Save credentials
prefs . importer . username = answers . username ;
prefs . importer . password = answers . password ;
// Test credentials
const spinner = ora ( {
2019-02-12 13:57:54 +01:00
text : 'Checking connection to Teedy' ,
2018-02-28 22:53:06 +01:00
spinner : 'flips'
} ) . start ( ) ;
request . post ( {
url : prefs . importer . baseUrl + '/api/user/login' ,
form : {
username : answers . username ,
password : answers . password ,
remember : true
}
} , function ( error , response ) {
2018-03-01 12:28:29 +01:00
if ( error || ! response || response . statusCode !== 200 ) {
2018-02-28 22:53:06 +01:00
spinner . fail ( 'Username or password incorrect' ) ;
askCredentials ( ) ;
return ;
}
spinner . succeed ( 'Authentication OK' ) ;
askPath ( ) ;
} ) ;
} ) ;
} ;
// Ask for the path
const askPath = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'input' ,
name : 'path' ,
message : 'What is the folder path you want to import?' ,
default : prefs . importer . path
}
] ) . then ( answers => {
// Save path
prefs . importer . path = answers . path ;
// Test path
const spinner = ora ( {
text : 'Checking import path' ,
spinner : 'flips'
} ) . start ( ) ;
2018-03-01 12:28:29 +01:00
fs . lstat ( answers . path , ( error , stats ) => {
if ( error || ! stats . isDirectory ( ) ) {
2018-02-28 22:53:06 +01:00
spinner . fail ( 'Please enter a valid directory path' ) ;
2018-03-01 12:28:29 +01:00
askPath ( ) ;
2018-02-28 22:53:06 +01:00
return ;
}
2018-03-01 12:28:29 +01:00
fs . access ( answers . path , fs . W _OK | fs . R _OK , ( error ) => {
if ( error ) {
spinner . fail ( 'This directory is not writable' ) ;
askPath ( ) ;
return ;
}
recursive ( answers . path , function ( error , files ) {
spinner . succeed ( files . length + ' files in this directory' ) ;
2020-06-23 22:31:49 +02:00
askFileFilter ( ) ;
2018-03-01 12:28:29 +01:00
} ) ;
2018-02-28 22:53:06 +01:00
} ) ;
} ) ;
} ) ;
} ;
2018-03-01 12:28:29 +01:00
2020-06-23 22:31:49 +02:00
// Ask for the file filter
const askFileFilter = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'input' ,
name : 'fileFilter' ,
message : 'What pattern do you want to use to match files? (eg. *.+(pdf|txt|jpg))' ,
default : prefs . importer . fileFilter || "*"
}
] ) . then ( answers => {
// Save fileFilter
prefs . importer . fileFilter = answers . fileFilter ;
askTag ( ) ;
} ) ;
} ;
2018-03-28 16:48:16 +02:00
// Ask for the tag to add
const askTag = ( ) => {
console . log ( '' ) ;
// Load tags
const spinner = ora ( {
text : 'Loading tags' ,
spinner : 'flips'
} ) . start ( ) ;
request . get ( {
url : prefs . importer . baseUrl + '/api/tag/list' ,
} , function ( error , response , body ) {
if ( error || ! response || response . statusCode !== 200 ) {
spinner . fail ( 'Error loading tags' ) ;
askTag ( ) ;
return ;
}
spinner . succeed ( 'Tags loaded' ) ;
const tags = JSON . parse ( body ) . tags ;
const defaultTag = _ . findWhere ( tags , { id : prefs . importer . tag } ) ;
const defaultTagName = defaultTag ? defaultTag . name : 'No tag' ;
inquirer . prompt ( [
{
type : 'list' ,
name : 'tag' ,
2020-05-22 15:18:19 +02:00
message : 'Which tag to add to all imported documents?' ,
2018-03-28 16:48:16 +02:00
default : defaultTagName ,
choices : [ 'No tag' ] . concat ( _ . pluck ( tags , 'name' ) )
}
] ) . then ( answers => {
// Save tag
prefs . importer . tag = answers . tag === 'No tag' ?
'' : _ . findWhere ( tags , { name : answers . tag } ) . id ;
2020-05-22 15:18:19 +02:00
askAddTag ( ) ;
} ) ;
} ) ;
} ;
const askAddTag = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'confirm' ,
name : 'addtags' ,
message : 'Do you want to add tags from the filename given with # ?' ,
default : prefs . importer . addtags === true
}
] ) . then ( answers => {
// Save daemon
prefs . importer . addtags = answers . addtags ;
// Save all preferences in case the program is sig-killed
askLang ( ) ;
} ) ;
}
const askLang = ( ) => {
console . log ( '' ) ;
// Load tags
const spinner = ora ( {
text : 'Loading default language' ,
spinner : 'flips'
} ) . start ( ) ;
request . get ( {
url : prefs . importer . baseUrl + '/api/app' ,
} , function ( error , response , body ) {
if ( error || ! response || response . statusCode !== 200 ) {
spinner . fail ( 'Connection to Teedy failed: ' + error ) ;
askLang ( ) ;
return ;
}
spinner . succeed ( 'Language loaded' ) ;
const defaultLang = prefs . importer . lang ? prefs . importer . lang : JSON . parse ( body ) . default _language ;
inquirer . prompt ( [
{
type : 'input' ,
name : 'lang' ,
message : 'Which should be the default language of the document?' ,
default : defaultLang
}
] ) . then ( answers => {
// Save tag
prefs . importer . lang = answers . lang
2020-06-07 20:13:04 +02:00
askCopyFolder ( ) ;
2018-03-28 16:48:16 +02:00
} ) ;
} ) ;
} ;
2020-06-07 20:13:04 +02:00
const askCopyFolder = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'input' ,
name : 'copyFolder' ,
message : 'Enter a path to copy files before they are deleted or leave empty to disable. The path must end with a \'/\' on MacOS and Linux or with a \'\\\' on Windows. Entering \'undefined\' will disable this again after setting the folder.' ,
default : prefs . importer . copyFolder
}
] ) . then ( answers => {
// Save path
prefs . importer . copyFolder = answers . copyFolder == 'undefined' ? '' : answers . copyFolder ;
if ( prefs . importer . copyFolder ) {
// Test path
const spinner = ora ( {
text : 'Checking copy folder path' ,
spinner : 'flips'
} ) . start ( ) ;
fs . lstat ( answers . copyFolder , ( error , stats ) => {
if ( error || ! stats . isDirectory ( ) ) {
spinner . fail ( 'Please enter a valid directory path' ) ;
askCopyFolder ( ) ;
return ;
}
fs . access ( answers . copyFolder , fs . W _OK | fs . R _OK , ( error ) => {
if ( error ) {
spinner . fail ( 'This directory is not writable' ) ;
askCopyFolder ( ) ;
return ;
}
spinner . succeed ( 'Copy folder set!' ) ;
askDaemon ( ) ;
} ) ;
} ) ;
}
else { askDaemon ( ) ; }
} ) ;
} ;
2018-03-01 12:28:29 +01:00
// Ask for daemon mode
const askDaemon = ( ) => {
console . log ( '' ) ;
inquirer . prompt ( [
{
type : 'confirm' ,
name : 'daemon' ,
message : 'Do you want to run the importer in daemon mode (it will poll the input directory for new files, import and delete them)?' ,
default : prefs . importer . daemon === true
}
] ) . then ( answers => {
// Save daemon
prefs . importer . daemon = answers . daemon ;
2018-03-05 14:36:30 +01:00
// Save all preferences in case the program is sig-killed
prefs . save ( ) ;
2018-03-01 12:28:29 +01:00
start ( ) ;
} ) ;
} ;
2018-03-05 14:36:30 +01:00
// Start the importer
2018-03-01 12:28:29 +01:00
const start = ( ) => {
2018-03-05 14:36:30 +01:00
request . post ( {
url : prefs . importer . baseUrl + '/api/user/login' ,
form : {
username : prefs . importer . username ,
password : prefs . importer . password ,
remember : true
}
} , function ( error , response ) {
if ( error || ! response || response . statusCode !== 200 ) {
console . error ( '\nUsername or password incorrect' ) ;
return ;
}
2018-03-01 12:28:29 +01:00
2018-03-05 14:36:30 +01:00
// Start the actual import
if ( prefs . importer . daemon ) {
console . log ( '\nPolling the input folder for new files...' ) ;
let resolve = ( ) => {
importFiles ( true , ( ) => {
setTimeout ( resolve , 30000 ) ;
} ) ;
} ;
resolve ( ) ;
} else {
importFiles ( false , ( ) => { } ) ;
}
} ) ;
2018-03-01 12:28:29 +01:00
} ;
// Import the files
const importFiles = ( remove , filesImported ) => {
recursive ( prefs . importer . path , function ( error , files ) {
2020-06-23 22:31:49 +02:00
2021-01-25 21:27:22 +01:00
files = files . filter ( minimatch . filter ( prefs . importer . fileFilter || '*' , { matchBase : true } ) ) ;
2018-03-01 12:28:29 +01:00
if ( files . length === 0 ) {
filesImported ( ) ;
return ;
}
let index = 0 ;
let resolve = ( ) => {
const file = files [ index ++ ] ;
if ( file ) {
importFile ( file , remove , resolve ) ;
} else {
filesImported ( ) ;
}
} ;
resolve ( ) ;
} ) ;
} ;
// Import a file
const importFile = ( file , remove , resolve ) => {
const spinner = ora ( {
text : 'Importing: ' + file ,
spinner : 'flips'
} ) . start ( ) ;
2020-05-22 15:18:19 +02:00
// Remove path of file
let filename = file . replace ( /^.*[\\\/]/ , '' ) ;
// Get Tags given as hashtags from filename
let taglist = filename . match ( /#[^\s:#]+/mg ) ;
taglist = taglist ? taglist . map ( s => s . substr ( 1 ) ) : [ ] ;
// Get available tags and UUIDs from server
request . get ( {
url : prefs . importer . baseUrl + '/api/tag/list' ,
} , function ( error , response , body ) {
2018-03-01 12:28:29 +01:00
if ( error || ! response || response . statusCode !== 200 ) {
2020-05-22 15:18:19 +02:00
spinner . fail ( 'Error loading tags' ) ;
2018-03-01 12:28:29 +01:00
return ;
}
2020-05-22 15:18:19 +02:00
let tagsarray = { } ;
for ( let l of JSON . parse ( body ) . tags ) {
tagsarray [ l . name ] = l . id ;
}
2018-03-26 17:00:14 +02:00
2020-05-22 15:18:19 +02:00
// Intersect tags from filename with existing tags on server
let foundtags = [ ] ;
for ( let j of taglist ) {
2020-06-21 14:48:13 +02:00
// If the tag is last in the filename it could include a file extension and would not be recognized
if ( j . includes ( '.' ) && ! tagsarray . hasOwnProperty ( j ) && ! foundtags . includes ( tagsarray [ j ] ) ) {
while ( j . includes ( '.' ) && ! tagsarray . hasOwnProperty ( j ) ) {
j = j . replace ( /\.[^.]*$/ , '' ) ;
}
}
2020-05-22 15:18:19 +02:00
if ( tagsarray . hasOwnProperty ( j ) && ! foundtags . includes ( tagsarray [ j ] ) ) {
foundtags . push ( tagsarray [ j ] ) ;
filename = filename . split ( '#' + j ) . join ( '' ) ;
2018-03-26 17:00:14 +02:00
}
2020-05-22 15:18:19 +02:00
}
if ( prefs . importer . tag !== '' && ! foundtags . includes ( prefs . importer . tag ) ) {
foundtags . push ( prefs . importer . tag ) ;
}
let data = { }
if ( prefs . importer . addtags ) {
data = {
title : prefs . importer . addtags ? filename : file . replace ( /^.*[\\\/]/ , '' ) . substring ( 0 , 100 ) ,
language : prefs . importer . lang || 'eng' ,
tags : foundtags
}
}
else {
data = {
title : prefs . importer . addtags ? filename : file . replace ( /^.*[\\\/]/ , '' ) . substring ( 0 , 100 ) ,
2020-06-21 14:48:13 +02:00
language : prefs . importer . lang || 'eng' ,
tags : prefs . importer . tag === '' ? undefined : prefs . importer . tag
2020-05-22 15:18:19 +02:00
}
}
// Create document
request . put ( {
url : prefs . importer . baseUrl + '/api/document' ,
form : qs . stringify ( data )
} , function ( error , response , body ) {
2018-03-26 17:00:14 +02:00
if ( error || ! response || response . statusCode !== 200 ) {
spinner . fail ( 'Upload failed for ' + file + ': ' + error ) ;
resolve ( ) ;
return ;
}
2020-05-22 15:18:19 +02:00
// Upload file
request . put ( {
url : prefs . importer . baseUrl + '/api/file' ,
formData : {
id : JSON . parse ( body ) . id ,
file : fs . createReadStream ( file )
}
} , function ( error , response ) {
if ( error || ! response || response . statusCode !== 200 ) {
spinner . fail ( 'Upload failed for ' + file + ': ' + error ) ;
resolve ( ) ;
return ;
}
spinner . succeed ( 'Upload successful for ' + file ) ;
if ( remove ) {
2020-06-07 20:13:04 +02:00
if ( prefs . importer . copyFolder ) {
fs . copyFileSync ( file , prefs . importer . copyFolder + file . replace ( /^.*[\\\/]/ , '' ) ) ;
fs . unlinkSync ( file ) ;
}
else { fs . unlinkSync ( file ) ; }
2020-05-22 15:18:19 +02:00
}
resolve ( ) ;
} ) ;
2018-03-26 17:00:14 +02:00
} ) ;
2018-03-01 12:28:29 +01:00
} ) ;
2018-03-05 14:36:30 +01:00
} ;
// Entrypoint: daemon mode or wizard
if ( argv . hasOwnProperty ( 'd' ) ) {
console . log ( 'Starting in quiet mode with the following configuration:\n' +
'Base URL: ' + prefs . importer . baseUrl + '\n' +
2018-03-28 16:48:16 +02:00
'Username: ' + prefs . importer . username + '\n' +
2018-03-05 14:36:30 +01:00
'Password: ***********\n' +
2018-03-28 16:48:16 +02:00
'Tag: ' + prefs . importer . tag + '\n' +
2020-05-22 15:18:19 +02:00
'Add tags given #: ' + prefs . importer . addtags + '\n' +
'Language: ' + prefs . importer . lang + '\n' +
2020-06-07 20:13:04 +02:00
'Daemon mode: ' + prefs . importer . daemon + '\n' +
2020-06-23 22:31:49 +02:00
'Copy folder: ' + prefs . importer . copyFolder + '\n' +
'File filter: ' + prefs . importer . fileFilter
2020-05-22 15:18:19 +02:00
) ;
2018-03-05 14:36:30 +01:00
start ( ) ;
} else {
askBaseUrl ( ) ;
}