mirror of
https://github.com/bluxmit/alnoda-workspaces.git
synced 2024-09-19 03:07:10 +12:00
611 lines
18 KiB
JavaScript
611 lines
18 KiB
JavaScript
|
#!/usr/bin/env node
|
||
|
|
||
|
// CLI for Storage System
|
||
|
// Copyright (c) 2015 Joseph Huckaby
|
||
|
// Released under the MIT License
|
||
|
|
||
|
var path = require('path');
|
||
|
var cp = require('child_process');
|
||
|
var os = require('os');
|
||
|
var fs = require('fs');
|
||
|
var async = require('async');
|
||
|
var bcrypt = require('bcrypt-node');
|
||
|
|
||
|
var Args = require('pixl-args');
|
||
|
var Tools = require('pixl-tools');
|
||
|
var StandaloneStorage = require('pixl-server-storage/standalone');
|
||
|
|
||
|
// chdir to the proper server root dir
|
||
|
process.chdir( path.dirname( __dirname ) );
|
||
|
|
||
|
// load app's config file
|
||
|
var config = require('../conf/config.json');
|
||
|
|
||
|
// shift commands off beginning of arg array
|
||
|
var argv = JSON.parse( JSON.stringify(process.argv.slice(2)) );
|
||
|
var commands = [];
|
||
|
while (argv.length && !argv[0].match(/^\-/)) {
|
||
|
commands.push( argv.shift() );
|
||
|
}
|
||
|
|
||
|
// now parse rest of cmdline args, if any
|
||
|
var args = new Args( argv, {
|
||
|
debug: false,
|
||
|
verbose: false,
|
||
|
quiet: false
|
||
|
} );
|
||
|
args = args.get(); // simple hash
|
||
|
|
||
|
// copy debug flag into config (for standalone)
|
||
|
config.Storage.debug = args.debug;
|
||
|
|
||
|
var print = function(msg) {
|
||
|
// print message to console
|
||
|
if (!args.quiet) process.stdout.write(msg);
|
||
|
};
|
||
|
var verbose = function(msg) {
|
||
|
// print only in verbose mode
|
||
|
if (args.verbose) print(msg);
|
||
|
};
|
||
|
var warn = function(msg) {
|
||
|
// print to stderr unless quiet
|
||
|
if (!args.quiet) process.stderr.write(msg);
|
||
|
};
|
||
|
var verbose_warn = function(msg) {
|
||
|
// verbose print to stderr unless quiet
|
||
|
if (args.verbose && !args.quiet) process.stderr.write(msg);
|
||
|
};
|
||
|
|
||
|
if (config.uid && (process.getuid() != 0)) {
|
||
|
print( "ERROR: Must be root to use this script.\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
// determine server hostname
|
||
|
var hostname = (process.env['HOSTNAME'] || process.env['HOST'] || os.hostname()).toLowerCase();
|
||
|
|
||
|
// find the first external IPv4 address
|
||
|
var ip = '';
|
||
|
var ifaces = os.networkInterfaces();
|
||
|
var addrs = [];
|
||
|
for (var key in ifaces) {
|
||
|
if (ifaces[key] && ifaces[key].length) {
|
||
|
Array.from(ifaces[key]).forEach( function(item) { addrs.push(item); } );
|
||
|
}
|
||
|
}
|
||
|
var addr = Tools.findObject( addrs, { family: 'IPv4', internal: false } );
|
||
|
if (addr && addr.address && addr.address.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
||
|
ip = addr.address;
|
||
|
}
|
||
|
else {
|
||
|
print( "ERROR: Could not determine server's IP address.\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
// util.isArray is DEPRECATED??? Nooooooooode!
|
||
|
var isArray = Array.isArray || util.isArray;
|
||
|
|
||
|
// prevent logging transactions to STDOUT
|
||
|
config.Storage.log_event_types = {};
|
||
|
|
||
|
// allow APPNAME_key env vars to override config
|
||
|
var env_regex = new RegExp( "^CRONICLE_(.+)$" );
|
||
|
for (var env_key in process.env) {
|
||
|
if (env_key.match(env_regex)) {
|
||
|
var env_path = RegExp.$1.trim().replace(/^_+/, '').replace(/_+$/, '').replace(/__/g, '/');
|
||
|
var env_value = process.env[env_key].toString();
|
||
|
|
||
|
// massage value into various types
|
||
|
if (env_value === 'true') env_value = true;
|
||
|
else if (env_value === 'false') env_value = false;
|
||
|
else if (env_value.match(/^\-?\d+$/)) env_value = parseInt(env_value);
|
||
|
else if (env_value.match(/^\-?\d+\.\d+$/)) env_value = parseFloat(env_value);
|
||
|
|
||
|
Tools.setPath(config, env_path, env_value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// construct standalone storage server
|
||
|
var storage = new StandaloneStorage(config.Storage, function(err) {
|
||
|
if (err) throw err;
|
||
|
// storage system is ready to go
|
||
|
|
||
|
// become correct user
|
||
|
if (config.uid && (process.getuid() == 0)) {
|
||
|
verbose( "Switching to user: " + config.uid + "\n" );
|
||
|
process.setuid( config.uid );
|
||
|
}
|
||
|
|
||
|
// custom job data expire handler
|
||
|
storage.addRecordType( 'cronicle_job', {
|
||
|
'delete': function(key, value, callback) {
|
||
|
storage.delete( key, function(err) {
|
||
|
storage.delete( key + '/log.txt.gz', function(err) {
|
||
|
callback();
|
||
|
} ); // delete
|
||
|
} ); // delete
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
// process command
|
||
|
var cmd = commands.shift();
|
||
|
verbose("\n");
|
||
|
|
||
|
switch (cmd) {
|
||
|
case 'setup':
|
||
|
case 'install':
|
||
|
// setup new master server
|
||
|
var setup = require('../conf/setup.json');
|
||
|
|
||
|
// make sure this is only run once
|
||
|
storage.get( 'global/users', function(err) {
|
||
|
if (!err) {
|
||
|
print( "Storage has already been set up. There is no need to run this command again.\n\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
async.eachSeries( setup.storage,
|
||
|
function(params, callback) {
|
||
|
verbose( "Executing: " + JSON.stringify(params) + "\n" );
|
||
|
// [ "listCreate", "global/users", { "page_size": 100 } ]
|
||
|
var func = params.shift();
|
||
|
params.push( callback );
|
||
|
|
||
|
// massage a few params
|
||
|
if (typeof(params[1]) == 'object') {
|
||
|
var obj = params[1];
|
||
|
if (obj.created) obj.created = Tools.timeNow(true);
|
||
|
if (obj.modified) obj.modified = Tools.timeNow(true);
|
||
|
if (obj.regexp && (obj.regexp == '_HOSTNAME_')) obj.regexp = '^(' + Tools.escapeRegExp( hostname ) + ')$';
|
||
|
if (obj.hostname && (obj.hostname == '_HOSTNAME_')) obj.hostname = hostname;
|
||
|
if (obj.ip && (obj.ip == '_IP_')) obj.ip = ip;
|
||
|
}
|
||
|
|
||
|
// call storage directly
|
||
|
storage[func].apply( storage, params );
|
||
|
},
|
||
|
function(err) {
|
||
|
if (err) throw err;
|
||
|
|
||
|
print("\n");
|
||
|
print( "Setup completed successfully!\n" );
|
||
|
print( "This server ("+hostname+") has been added as the single primary master server.\n" );
|
||
|
print( "An administrator account has been created with username 'admin' and password 'admin'.\n" );
|
||
|
print( "You should now be able to start the service by typing: '/opt/cronicle/bin/control.sh start'\n" );
|
||
|
print( "Then, the web interface should be available at: http://"+hostname+":"+config.WebServer.http_port+"/\n" );
|
||
|
print( "Please allow for up to 60 seconds for the server to become master.\n\n" );
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
}
|
||
|
);
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'admin':
|
||
|
// create or replace admin account
|
||
|
// Usage: ./storage-cli.js admin USERNAME PASSWORD [EMAIL]
|
||
|
var username = commands.shift();
|
||
|
var password = commands.shift();
|
||
|
var email = commands.shift() || 'admin@localhost';
|
||
|
if (!username || !password) {
|
||
|
print( "\nUsage: bin/storage-cli.js admin USERNAME PASSWORD [EMAIL]\n\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
if (!username.match(/^[\w\-\.]+$/)) {
|
||
|
print( "\nERROR: Username must contain only alphanumerics, dash and period.\n\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
username = username.toLowerCase();
|
||
|
|
||
|
var user = {
|
||
|
username: username,
|
||
|
password: password,
|
||
|
full_name: "Administrator",
|
||
|
email: email
|
||
|
};
|
||
|
|
||
|
user.active = 1;
|
||
|
user.created = user.modified = Tools.timeNow(true);
|
||
|
user.salt = Tools.generateUniqueID( 64, user.username );
|
||
|
user.password = bcrypt.hashSync( user.password + user.salt );
|
||
|
user.privileges = { admin: 1 };
|
||
|
|
||
|
storage.put( 'users/' + username, user, function(err) {
|
||
|
if (err) throw err;
|
||
|
print( "\nAdministrator '"+username+"' created successfully.\n" );
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'get':
|
||
|
case 'fetch':
|
||
|
case 'view':
|
||
|
case 'cat':
|
||
|
// get storage key
|
||
|
// Usage: ./storage-cli.js get users/jhuckaby
|
||
|
var key = commands.shift();
|
||
|
storage.get( key, function(err, data) {
|
||
|
if (err) throw err;
|
||
|
if (storage.isBinaryKey(key)) print( data.toString() + "\n" );
|
||
|
else print( ((typeof(data) == 'object') ? JSON.stringify(data, null, "\t") : data) + "\n" );
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'put':
|
||
|
case 'save':
|
||
|
case 'store':
|
||
|
// put storage key (read data from STDIN)
|
||
|
// Usage: cat USER.json | ./storage-cli.js put users/jhuckaby
|
||
|
var key = commands.shift();
|
||
|
var json_raw = '';
|
||
|
var rl = require('readline').createInterface({ input: process.stdin });
|
||
|
rl.on('line', function(line) { json_raw += line; });
|
||
|
rl.on('close', function() {
|
||
|
print( "Writing record from STDIN: " + key + "\n" );
|
||
|
|
||
|
var data = null;
|
||
|
try { data = JSON.parse(json_raw); }
|
||
|
catch (err) {
|
||
|
warn( "Failed to parse JSON for key: " + key + ": " + err + "\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
storage.put( key, data, function(err) {
|
||
|
if (err) {
|
||
|
warn( "Failed to store record: " + key + ": " + err + "\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
print("Record successfully saved: "+key+"\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case 'edit':
|
||
|
case 'vi':
|
||
|
var key = commands.shift();
|
||
|
|
||
|
if ((cmd == 'edit') && !process.env.EDITOR) {
|
||
|
warn( "No EDITOR environment variable is set.\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
storage.get( key, function(err, data) {
|
||
|
if (err) data = {};
|
||
|
print("Spawning editor to edit record: " + key + "\n");
|
||
|
|
||
|
// save to local temp file
|
||
|
var temp_file = path.join( os.tmpdir(), 'cli-temp-' + process.pid + '.json' );
|
||
|
fs.writeFileSync( temp_file, JSON.stringify(data, null, "\t") + "\n" );
|
||
|
var stats = fs.statSync( temp_file );
|
||
|
var old_mod = Math.floor( stats.mtime.getTime() / 1000 );
|
||
|
|
||
|
// spawn vi but inherit terminal
|
||
|
var child = cp.spawn( (cmd == 'vi') ? 'vi' : process.env.EDITOR, [temp_file], {
|
||
|
stdio: 'inherit'
|
||
|
} );
|
||
|
child.on('exit', function (e, code) {
|
||
|
var stats = fs.statSync( temp_file );
|
||
|
var new_mod = Math.floor( stats.mtime.getTime() / 1000 );
|
||
|
if (new_mod != old_mod) {
|
||
|
print("Saving new data back into record: "+key+"\n");
|
||
|
|
||
|
var json_raw = fs.readFileSync( temp_file, { encoding: 'utf8' } );
|
||
|
fs.unlinkSync( temp_file );
|
||
|
|
||
|
var data = JSON.parse( json_raw );
|
||
|
|
||
|
storage.put( key, data, function(err, data) {
|
||
|
if (err) throw err;
|
||
|
print("Record successfully saved with your changes: "+key+"\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
}
|
||
|
else {
|
||
|
fs.unlinkSync( temp_file );
|
||
|
print("File has not been changed, record was not touched: "+key+"\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
} ); // got data
|
||
|
break;
|
||
|
|
||
|
case 'delete':
|
||
|
// delete storage key
|
||
|
// Usage: ./storage-cli.js delete users/jhuckaby
|
||
|
var key = commands.shift();
|
||
|
storage.delete( key, function(err, data) {
|
||
|
if (err) throw err;
|
||
|
print("Record '"+key+"' deleted successfully.\n");
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'list_create':
|
||
|
// create new list
|
||
|
// Usage: ./storage-cli.js list_create key
|
||
|
var key = commands.shift();
|
||
|
storage.listCreate( key, null, function(err) {
|
||
|
if (err) throw err;
|
||
|
print("List created successfully: " + key + "\n");
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'list_pop':
|
||
|
// pop item off end of list
|
||
|
// Usage: ./storage-cli.js list_pop key
|
||
|
var key = commands.shift();
|
||
|
storage.listPop( key, function(err, item) {
|
||
|
if (err) throw err;
|
||
|
print("Item popped off list: " + key + ": " + JSON.stringify(item, null, "\t") + "\n");
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'list_get':
|
||
|
// fetch items from list
|
||
|
// Usage: ./storage-cli.js list_get key idx len
|
||
|
var key = commands.shift();
|
||
|
var idx = parseInt( commands.shift() || 0 );
|
||
|
var len = parseInt( commands.shift() || 0 );
|
||
|
storage.listGet( key, idx, len, function(err, items) {
|
||
|
if (err) throw err;
|
||
|
print("Got " + items.length + " items.\n");
|
||
|
print("Items from list: " + key + ": " + JSON.stringify(items, null, "\t") + "\n");
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'list_info':
|
||
|
// fetch info about list
|
||
|
// Usage: ./storage-cli.js list_info key
|
||
|
var key = commands.shift();
|
||
|
|
||
|
storage.listGetInfo( key, function(err, list) {
|
||
|
if (err) throw err;
|
||
|
print("List Header: " + key + ": " + JSON.stringify(list, null, "\t") + "\n\n");
|
||
|
var page_idx = list.first_page;
|
||
|
var item_idx = 0;
|
||
|
async.whilst(
|
||
|
function() { return page_idx <= list.last_page; },
|
||
|
function(callback) {
|
||
|
// load each page
|
||
|
storage._listLoadPage(key, page_idx++, false, function(err, page) {
|
||
|
if (err) return callback(err);
|
||
|
print("Page " + Math.floor(page_idx - 1) + ": " + page.items.length + " items\n");
|
||
|
callback();
|
||
|
} ); // page loaded
|
||
|
},
|
||
|
function(err) {
|
||
|
// all pages iterated
|
||
|
if (err) throw err;
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} // pages complete
|
||
|
); // whilst
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'list_delete':
|
||
|
// delete list
|
||
|
// Usage: ./storage-cli.js list_delete key
|
||
|
var key = commands.shift();
|
||
|
storage.listDelete( key, null, function(err) {
|
||
|
if (err) throw err;
|
||
|
print("List deleted successfully: " + key + "\n");
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'maint':
|
||
|
case 'maintenance':
|
||
|
// perform daily maintenance, specify date or defaults to current day
|
||
|
// Usage: ./storage-cli.js maint 2015-05-31
|
||
|
storage.runMaintenance( commands.shift(), function() {
|
||
|
print( "Daily maintenance completed successfully.\n" );
|
||
|
print("\n");
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} );
|
||
|
break;
|
||
|
|
||
|
case 'export':
|
||
|
// export all storage data (except completed jobs, sessions)
|
||
|
var file = commands.shift();
|
||
|
export_data(file);
|
||
|
break;
|
||
|
|
||
|
case 'import':
|
||
|
// import storage data from file
|
||
|
var file = commands.shift();
|
||
|
import_data(file);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
print("Unknown command: " + cmd + "\n");
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
break;
|
||
|
|
||
|
} // switch
|
||
|
});
|
||
|
|
||
|
function export_data(file) {
|
||
|
// export data to file or stdout (except for completed jobs, logs, and sessions)
|
||
|
// one record per line: KEY - JSON
|
||
|
var stream = file ? fs.createWriteStream(file) : process.stdout;
|
||
|
|
||
|
// file header (for humans)
|
||
|
var file_header = "# Cronicle Data Export v1.0\n" +
|
||
|
"# Hostname: " + hostname + "\n" +
|
||
|
"# Date/Time: " + (new Date()).toString() + "\n" +
|
||
|
"# Format: KEY - JSON\n\n";
|
||
|
|
||
|
stream.write( file_header );
|
||
|
verbose_warn( file_header );
|
||
|
|
||
|
if (file) verbose_warn("Exporting to file: " + file + "\n\n");
|
||
|
|
||
|
// need to handle users separately, as they're stored as a list + individual records
|
||
|
storage.listEach( 'global/users',
|
||
|
function(item, idx, callback) {
|
||
|
var username = item.username;
|
||
|
var key = 'users/' + username.toString().toLowerCase().replace(/\W+/g, '');
|
||
|
verbose_warn( "Exporting user: " + username + "\n" );
|
||
|
|
||
|
storage.get( key, function(err, user) {
|
||
|
if (err) {
|
||
|
// user deleted?
|
||
|
warn( "\nFailed to fetch user: " + key + ": " + err + "\n\n" );
|
||
|
return callback();
|
||
|
}
|
||
|
|
||
|
stream.write( key + ' - ' + JSON.stringify(user) + "\n", 'utf8', callback );
|
||
|
} ); // get
|
||
|
},
|
||
|
function(err) {
|
||
|
// ignoring errors here
|
||
|
// proceed to the rest of the lists
|
||
|
async.eachSeries(
|
||
|
[
|
||
|
'global/users',
|
||
|
'global/plugins',
|
||
|
'global/categories',
|
||
|
'global/server_groups',
|
||
|
'global/schedule',
|
||
|
'global/servers',
|
||
|
'global/api_keys'
|
||
|
],
|
||
|
function(list_key, callback) {
|
||
|
// first get the list header
|
||
|
verbose_warn( "Exporting list: " + list_key + "\n" );
|
||
|
|
||
|
storage.get( list_key, function(err, list) {
|
||
|
if (err) return callback( new Error("Failed to fetch list: " + list_key + ": " + err) );
|
||
|
|
||
|
stream.write( list_key + ' - ' + JSON.stringify(list) + "\n" );
|
||
|
|
||
|
// now iterate over all the list pages
|
||
|
var page_idx = list.first_page;
|
||
|
|
||
|
async.whilst(
|
||
|
function() { return page_idx <= list.last_page; },
|
||
|
function(callback) {
|
||
|
// load each page
|
||
|
var page_key = list_key + '/' + page_idx;
|
||
|
page_idx++;
|
||
|
|
||
|
verbose_warn( "Exporting list page: " + page_key + "\n");
|
||
|
|
||
|
storage.get(page_key, function(err, page) {
|
||
|
if (err) return callback( new Error("Failed to fetch list page: " + page_key + ": " + err) );
|
||
|
|
||
|
// write page data
|
||
|
stream.write( page_key + ' - ' + JSON.stringify(page) + "\n", 'utf8', callback );
|
||
|
} ); // page get
|
||
|
}, // iterator
|
||
|
callback
|
||
|
); // whilst
|
||
|
|
||
|
} ); // get
|
||
|
}, // iterator
|
||
|
function(err) {
|
||
|
if (err) {
|
||
|
warn( "\nEXPORT ERROR: " + err + "\n" );
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
verbose_warn( "\nExport completed at " + (new Date()).toString() + ".\nExiting.\n\n" );
|
||
|
|
||
|
if (file) stream.end();
|
||
|
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
} // done done
|
||
|
); // list eachSeries
|
||
|
} // done with users
|
||
|
); // users listEach
|
||
|
};
|
||
|
|
||
|
function import_data(file) {
|
||
|
// import storage data from specified file or stdin
|
||
|
// one record per line: KEY - JSON
|
||
|
print( "\nCronicle Data Importer v1.0\n" );
|
||
|
if (file) print( "Importing from file: " + file + "\n" );
|
||
|
else print( "Importing from STDIN\n" );
|
||
|
print( "\n" );
|
||
|
|
||
|
var count = 0;
|
||
|
var queue = async.queue( function(line, callback) {
|
||
|
// process each line
|
||
|
if (line.match(/^(\w[\w\-\.\/]*)\s+\-\s+(\{.+\})\s*$/)) {
|
||
|
var key = RegExp.$1;
|
||
|
var json_raw = RegExp.$2;
|
||
|
print( "Importing record: " + key + "\n" );
|
||
|
|
||
|
var data = null;
|
||
|
try { data = JSON.parse(json_raw); }
|
||
|
catch (err) {
|
||
|
warn( "Failed to parse JSON for key: " + key + ": " + err + "\n" );
|
||
|
return callback();
|
||
|
}
|
||
|
|
||
|
storage.put( key, data, function(err) {
|
||
|
if (err) {
|
||
|
warn( "Failed to store record: " + key + ": " + err + "\n" );
|
||
|
return callback();
|
||
|
}
|
||
|
count++;
|
||
|
callback();
|
||
|
} );
|
||
|
}
|
||
|
else callback();
|
||
|
}, 1 );
|
||
|
|
||
|
// setup readline to line-read from file or stdin
|
||
|
var readline = require('readline');
|
||
|
var rl = readline.createInterface({
|
||
|
input: file ? fs.createReadStream(file) : process.stdin
|
||
|
});
|
||
|
|
||
|
rl.on('line', function(line) {
|
||
|
// enqueue each line
|
||
|
queue.push( line );
|
||
|
});
|
||
|
|
||
|
rl.on('close', function() {
|
||
|
// end of input stream
|
||
|
var complete = function() {
|
||
|
// finally, delete state so cronicle recreates it
|
||
|
storage.delete( 'global/state', function(err) {
|
||
|
// ignore error here, as state may not exist yet
|
||
|
print( "\nImport complete. " + count + " records imported.\nExiting.\n\n" );
|
||
|
storage.shutdown( function() { process.exit(0); } );
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// fire complete on queue drain
|
||
|
if (queue.idle()) complete();
|
||
|
else queue.drain = complete;
|
||
|
}); // rl close
|
||
|
};
|