diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af0d5d..c86d125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All Notable changes to `clouddrive-node` will be documented in this file - Now using custom progress bar since others were either broken in some way or no longer maintained - `UploadCommand` now supports the `--checksum` option in addition to the default config value - `delete-everything` command now used to delete all CLI files and folders +- `upload` and `download` now supports encryption ### Breaking Changes - `cache` and `config` directories are now stored using the [env-paths](https://github.com/sindresorhus/env-paths) package. NOTE: you will need to either manually move your existing files or re-run `init` and `sync` with this new version. diff --git a/lib/Commands/DeleteEverythingCommand.js b/lib/Commands/DeleteEverythingCommand.js index f1d8d12..2bf8b81 100644 --- a/lib/Commands/DeleteEverythingCommand.js +++ b/lib/Commands/DeleteEverythingCommand.js @@ -4,33 +4,47 @@ let Command = require('./Command'), Node = require('../Node'), Logger = require('../Logger'), async = require('async'), - fs = require('fs-extra'); + fs = require('fs-extra'), + inquirer = require('inquirer'); class DeleteEverythingCommand extends Command { run(args, options) { return new Promise((resolve, reject) => { - Command.startSpinner('Removing all files and folders'); - async.waterfall([ - callback => { - Logger.verbose(`Removing cache directory ${Command.getCacheDirectory()}`); - fs.remove(Command.getCacheDirectory(), callback); - }, - callback => { - Logger.verbose(`Removing config directory ${Command.getConfigDirectory()}`); - fs.remove(Command.getConfigDirectory(), callback); - }, - callback => { - Logger.verbose(`Removing log directory ${Command.getLogDirectory()}`); - fs.remove(Command.getLogDirectory(), callback); - }, - ], err => { - if (err) { - return reject(err); + inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'really delete everything? ', + default: false, } + ], answers => { + if (!answers.confirm) { + return resolve(); + } + + Command.startSpinner('Removing all files and folders'); + async.waterfall([ + callback => { + Logger.verbose(`Removing cache directory ${Command.getCacheDirectory()}`); + fs.remove(Command.getCacheDirectory(), callback); + }, + callback => { + Logger.verbose(`Removing config directory ${Command.getConfigDirectory()}`); + fs.remove(Command.getConfigDirectory(), callback); + }, + callback => { + Logger.verbose(`Removing log directory ${Command.getLogDirectory()}`); + fs.remove(Command.getLogDirectory(), callback); + }, + ], err => { + if (err) { + return reject(err); + } - Command.stopSpinner('Done.'); + Command.stopSpinner('Done.'); - return resolve(); + return resolve(); + }); }); }); } diff --git a/lib/Commands/DownloadCommand.js b/lib/Commands/DownloadCommand.js index 13e3754..ec9df83 100644 --- a/lib/Commands/DownloadCommand.js +++ b/lib/Commands/DownloadCommand.js @@ -5,6 +5,8 @@ let Command = require('./Command'), Utils = require('../Utils'), Logger = require('../Logger'), ProgressBar = require('../ProgressBar'), + async = require('async'), + inquirer = require('inquirer'), logUpdate = require('log-update'); class DownloadCommand extends Command { @@ -52,82 +54,102 @@ class DownloadCommand extends Command { remote: options.remote, queryParams: queryParams, checkMd5: this.config.get('download.checkMd5'), + decrypt: options.decrypt || false, }; - node.on('fileDownload', node => { - if (this.config.get('cli.progressBars')) { - startTime = Date.now(); - bytesDownloaded = 0; - downloadingNode = node; - lastRun = Date.now(); - bar = new ProgressBar(`Downloading ${downloadingNode.getName()}\n:percent[:bar] :speed eta :etas (:downloaded / :filesize)`, { - total: node.getSize(), - incomplete: ' ', - width: 40, - clear: false, - renderThrottle: this.config.get('cli.progressInterval') + async.waterfall([ + callback => { + if (!options.decrypt) { + return callback(); + } + + inquirer.prompt([ + { + type: 'password', + name: 'password', + message: 'password: ' + } + ], answers => { + opts.password = answers.password; + callback(); }); - } - }); + }, + ], err => { + node.on('fileDownload', node => { + if (this.config.get('cli.progressBars')) { + startTime = Date.now(); + bytesDownloaded = 0; + downloadingNode = node; + lastRun = Date.now(); + bar = new ProgressBar(`Downloading ${downloadingNode.getName()}\n:percent[:bar] :speed eta :etas (:downloaded / :filesize)`, { + total: node.getSize(), + incomplete: ' ', + width: 40, + clear: false, + renderThrottle: this.config.get('cli.progressInterval') + }); + } + }); + + node.on('downloadProgress', data => { + if (bar) { + bytesDownloaded += data.length; + bytesTransfered += data.length; + + let timeDiff = Date.now() - lastRun, + elapsedTime = Date.now() - startTime, + eta = Math.round((elapsedTime * (downloadingNode.getSize() / bytesDownloaded - 1)) / 1000); + + if (timeDiff >= this.config.get('cli.progressInterval') || bytesDownloaded >= downloadingNode.getSize()) { + lastRun = Date.now(); + bar.tick(bytesTransfered, { + speed: `${Utils.convertFileSize(Math.round(bytesTransfered / (timeDiff / 1000)), 2)}/s`, + downloaded: Utils.convertFileSize(bytesDownloaded), + filesize: Utils.convertFileSize(downloadingNode.getSize()), + }); + bytesTransfered = 0; + } + } + }); + + node.on('downloadComplete', (response, body, retval, data) => { + // Clear out progress bar + if (bar !== null) { + bar.clear(); + bar = null; + } - node.on('downloadProgress', data => { - if (bar) { - bytesDownloaded += data.length; - bytesTransfered += data.length; + if (response) { + Logger.debug(`Response returned with status code ${response.statusCode}`); + } - let timeDiff = Date.now() - lastRun, - elapsedTime = Date.now() - startTime, - eta = Math.round((elapsedTime * (downloadingNode.getSize() / bytesDownloaded - 1)) / 1000); + if (retval.success) { + return Logger.info(`Successfully downloaded '${data.localPath}'`); + } - if (timeDiff >= this.config.get('cli.progressInterval') || bytesDownloaded >= downloadingNode.getSize()) { - lastRun = Date.now(); - bar.tick(bytesTransfered, { - speed: `${Utils.convertFileSize(Math.round(bytesTransfered / (timeDiff / 1000)), 2)}/s`, - downloaded: Utils.convertFileSize(bytesDownloaded), - filesize: Utils.convertFileSize(downloadingNode.getSize()), - }); - bytesTransfered = 0; + let message = `Failed to download '${data.localPath}'`; + if (retval.data.message) { + message += ': ' + retval.data.message; } - } - }); - node.on('downloadComplete', (response, body, retval, data) => { - // Clear out progress bar - if (bar !== null) { - bar.clear(); - bar = null; - } - - if (response) { - Logger.debug(`Response returned with status code ${response.statusCode}`); - } - - if (retval.success) { - return Logger.info(`Successfully downloaded '${data.localPath}'`); - } - - let message = `Failed to download '${data.localPath}'`; - if (retval.data.message) { - message += ': ' + retval.data.message; - } - - if (retval.data.exists) { - Logger.warn(message); - } else { - Logger.error(message); - } - }); + if (retval.data.exists) { + Logger.warn(message); + } else { + Logger.error(message); + } + }); - node.download(localPath, opts, (err, data) => { - if (err) { - return reject(err); - } + node.download(localPath, opts, (err, data) => { + if (err) { + return reject(err); + } - if (data.success) { - return resolve(); - } + if (data.success) { + return resolve(); + } - return reject(); + return reject(); + }); }); }); }); diff --git a/lib/Commands/RestoreCommand.js b/lib/Commands/RestoreCommand.js index b1cfa55..89f4d4b 100644 --- a/lib/Commands/RestoreCommand.js +++ b/lib/Commands/RestoreCommand.js @@ -46,9 +46,9 @@ class RestoreCommand extends Command { } if (result.success) { - Logger.info(`Successfully trashed node ${node.getPath()} (${node.getId()})`); + Logger.info(`Successfully restored node ${node.getName()} (${node.getId()})`); } else { - Logger.error(`Failed to trash node ${node.getPath()} (${node.getId()}): ${JSON.stringify(result)}`); + Logger.error(`Failed to restore node ${node.getName()} (${node.getId()}): ${JSON.stringify(result)}`); } return resolve(); diff --git a/lib/Commands/UploadCommand.js b/lib/Commands/UploadCommand.js index e1a2e53..9c0456a 100644 --- a/lib/Commands/UploadCommand.js +++ b/lib/Commands/UploadCommand.js @@ -4,9 +4,9 @@ let fs = require('fs'), Command = require('./Command'), Node = require('../Node'), chalk = require('chalk'), - // ProgressBar = require('ascii-progress'), ProgressBar = require('../ProgressBar'), async = require('async'), + inquirer = require('inquirer'), logUpdate = require('log-update'), Utils = require('../Utils'), Logger = require('../Logger'); @@ -42,122 +42,142 @@ class UploadCommand extends Command { numRetries: this.config.get('upload.numRetries'), suppressDedupe: options.duplicates === true ? true : this.config.get('upload.duplicates'), checkMd5: options.checksum || this.config.get('upload.checkMd5'), + encrypt: options.encrypt || false, }; if (options.overwrite) { opts.overwrite = true; } - Node.on('fileUpload', (localPath) => { - if (this.config.get('cli.progressBars')) { - startTime = Date.now(); - bytesUploaded = 0; - localFilesize = fs.statSync(localPath).size; - lastRun = Date.now(); - bar = new ProgressBar(`Uploading '${localPath}'\n:percent [:bar] :speed eta :etas (:uploaded / :filesize)`, { - total: localFilesize, - incomplete: ' ', - width: 40, - clear: false, - renderThrottle: this.config.get('cli.progressInterval') - }); - } - }); - - Node.on('uploadProgress', (localPath, chunk) => { - if (bar) { - bytesUploaded += chunk.length; - bytesTransfered += chunk.length; - - let timeDiff = Date.now() - lastRun, - elapsedTime = Date.now() - startTime; + async.waterfall([ + callback => { + if (!options.encrypt) { + return callback(); + } - if (timeDiff >= this.config.get('cli.progressInterval') || bytesUploaded >= localFilesize) { + inquirer.prompt([ + { + type: 'password', + name: 'password', + message: 'password: ' + } + ], answers => { + opts.password = answers.password; + callback(); + }); + }, + ], err => { + Node.on('fileUpload', (localPath) => { + if (this.config.get('cli.progressBars')) { + startTime = Date.now(); + bytesUploaded = 0; + localFilesize = fs.statSync(localPath).size; lastRun = Date.now(); - bar.tick(bytesTransfered, { - speed: `${Utils.convertFileSize(Math.round(bytesTransfered / (timeDiff / 1000)), 2)}/s`, - uploaded: Utils.convertFileSize(bytesUploaded), - filesize: Utils.convertFileSize(localFilesize), + bar = new ProgressBar(`Uploading '${localPath}'\n:percent [:bar] :speed eta :etas (:uploaded / :filesize)`, { + total: localFilesize, + incomplete: ' ', + width: 40, + clear: false, + renderThrottle: this.config.get('cli.progressInterval') }); - bytesTransfered = 0; } - } - }); + }); - Node.on('uploadComplete', (response, body, retval, data) => { - // Clear out progress bar - if (bar !== null) { - bar.clear(); - bar = null; - localFilesize = null; - } - - if (response) { - if (!body) { - return Logger.error(`Failed to upload file '${data.localPath}'. Invalid body returned: ${body}`); + Node.on('uploadProgress', (localPath, chunk) => { + if (bar) { + bytesUploaded += chunk.length; + bytesTransfered += chunk.length; + + let timeDiff = Date.now() - lastRun, + elapsedTime = Date.now() - startTime; + + if (timeDiff >= this.config.get('cli.progressInterval') || bytesUploaded >= localFilesize) { + lastRun = Date.now(); + bar.tick(bytesTransfered, { + speed: `${Utils.convertFileSize(Math.round(bytesTransfered / (timeDiff / 1000)), 2)}/s`, + uploaded: Utils.convertFileSize(bytesUploaded), + filesize: Utils.convertFileSize(localFilesize), + }); + bytesTransfered = 0; + } } - } + }); - if (retval.success) { - Logger.info(`Successfully uploaded file '${data.localPath}' to '${data.remotePath}'`); - } else { - let message = `Failed to upload file '${data.localPath}'`; + Node.on('uploadComplete', (response, body, retval, data) => { + // Clear out progress bar + if (bar !== null) { + bar.clear(); + bar = null; + localFilesize = null; + } - if (retval.data.message) { - message += `: ${retval.data.message}`; - } else { - message += `: ${JSON.stringify(retval.data)}`; + if (response) { + if (!body) { + return Logger.error(`Failed to upload file '${data.localPath}'. Invalid body returned: ${body}`); + } } - if (retval.data.exists === true) { - if ((retval.data.md5Match === true || retval.data.sizeMatch === true) && retval.data.pathMatch === true) { - Logger.warn(message); + if (retval.success) { + Logger.info(`Successfully uploaded file '${data.localPath}' to '${data.remotePath}'`); + } else { + let message = `Failed to upload file '${data.localPath}'`; + + if (retval.data.message) { + message += `: ${retval.data.message}`; } else { - Logger.error(message); + message += `: ${JSON.stringify(retval.data)}`; } - } else { - if (retval.data.retry !== undefined && retval.data.retry === true) { - Logger.warn(message); + + if (retval.data.exists === true) { + if ((retval.data.md5Match === true || retval.data.sizeMatch === true) && retval.data.pathMatch === true) { + Logger.warn(message); + } else { + Logger.error(message); + } } else { - Logger.error(message); + if (retval.data.retry !== undefined && retval.data.retry === true) { + Logger.warn(message); + } else { + Logger.error(message); + } } } - } - }); + }); + + Logger.debug(`Beginning upload...`); + async.forEachSeries(args, (localPath, callback) => { + try { + fs.statSync(localPath); + } catch (e) { + return reject(Error(`No file exists at '${localPath}'`)); + } + + if (fs.lstatSync(localPath).isDirectory()) { + Logger.debug(`Local path '${localPath}' is a directory. Uploading recursively...`); + return Node.uploadDirectory(localPath, remotePath, opts, (err, data) => { + if (err) { + return reject(err); + } - Logger.debug(`Beginning upload...`); - async.forEachSeries(args, (localPath, callback) => { - try { - fs.statSync(localPath); - } catch (e) { - return reject(Error(`No file exists at '${localPath}'`)); - } - - if (fs.lstatSync(localPath).isDirectory()) { - Logger.debug(`Local path '${localPath}' is a directory. Uploading recursively...`); - return Node.uploadDirectory(localPath, remotePath, opts, (err, data) => { + callback(); + }); + } + + Logger.debug(`Preparing to upload file '${localPath}'...`); + Node.uploadFile(localPath, remotePath, opts, (err, data) => { if (err) { return reject(err); } callback(); }); - } - - Logger.debug(`Preparing to upload file '${localPath}'...`); - Node.uploadFile(localPath, remotePath, opts, (err, data) => { + }, err => { if (err) { return reject(err); } - callback(); + return resolve(); }); - }, err => { - if (err) { - return reject(err); - } - - return resolve(); }); }); }); diff --git a/lib/Logger.js b/lib/Logger.js index 3fe9bf3..59c8fc4 100644 --- a/lib/Logger.js +++ b/lib/Logger.js @@ -3,6 +3,7 @@ let chalk = require('chalk'), winston = require('winston'), moment = require('moment'), + logUpdate = require('log-update'), Utils = require('./Utils'), instance = null, levels = { @@ -61,27 +62,39 @@ class Logger { } static info(message) { + logUpdate.clear(); Logger.getInstance().info(message); + logUpdate.done(); } static error(message) { + logUpdate.clear(); Logger.getInstance().error(message); + logUpdate.done(); } static warn(message) { + logUpdate.clear(); Logger.getInstance().warn(message); + logUpdate.done(); } static verbose(message) { + logUpdate.clear(); Logger.getInstance().verbose(message); + logUpdate.done(); } static debug(message, data = null) { + logUpdate.clear(); Logger.getInstance().debug(message, data); + logUpdate.done(); } static silly(message, data = null) { + logUpdate.clear(); Logger.getInstance().silly(message, data); + logUpdate.done(); } static setConsoleLevel(level) { diff --git a/lib/Node.js b/lib/Node.js index 3f67ec2..c600430 100644 --- a/lib/Node.js +++ b/lib/Node.js @@ -1,13 +1,15 @@ 'use strict'; let ParameterBag = require('./ParameterBag'), - fs = require('fs'), + fs = require('fs-extra'), path = require('path'), async = require('async'), got = require('got'), Utils = require('./Utils'), Logger = require('./Logger'), FormData = require('form-data'), + crypto = require('crypto'), + algorithm = 'aes-256-ctr', initialized = false, account = null, cache = null; @@ -39,6 +41,8 @@ class Node extends ParameterBag { stream: null, checkMd5: false, queryParams: null, + decrypt: false, + password: '', }; for (let key in options) { @@ -135,7 +139,13 @@ class Node extends ParameterBag { this.emit('downloadProgress', data); }); - request.pipe(stream); + if (downloadOptions.decrypt) { + Logger.verbose(`Creating decipher with password ${downloadOptions.password}`); + let decipher = crypto.createDecipher(algorithm, downloadOptions.password); + request.pipe(decipher).pipe(stream); + } else { + request.pipe(stream); + } } downloadFolder(localPath, options, callback) { @@ -513,100 +523,118 @@ class Node extends ParameterBag { this.emit('fileUpload', localPath); let progressInterval = null, form = new FormData(), - stream = fs.createReadStream(localPath); - - form.append('content', stream); - let headers = form.getHeaders(); - headers.Authorization = `Bearer ${account.token.access_token}`; + stream = Node.getUploadStream(localPath, { + encrypt: options.encrypt, + password: options.password, + }, (err, stream) => { + if (err) { + return callback(err); + } - stream.on('data', chunk => { - this.emit('uploadProgress', localPath, chunk); - }); + form.append('content', stream); + let headers = form.getHeaders(); + headers.Authorization = `Bearer ${account.token.access_token}`; - Logger.verbose('Requesting nodes:files:overwrite endpoint'); - Logger.debug(`HTTP Request: PUT '${account.contentUrl}nodes/${this.getId()}/content'`); - got.put(`${account.contentUrl}nodes/${this.getId()}/content`, { - headers: headers, - body: form, - timeout: 3600000, - }) - .then(response => { - Logger.debug(`Response returned with status code ${response.statusCode}.`); - Logger.silly(`Response body: ${response.body}`); - stream.close(); + stream.on('data', chunk => { + this.emit('uploadProgress', localPath, chunk); + }); - this.getPath((err, remotePath) => { - if (err) { - return callback(err); - } + Logger.verbose('Requesting nodes:files:overwrite endpoint'); + Logger.debug(`HTTP Request: PUT '${account.contentUrl}nodes/${this.getId()}/content'`); + got.put(`${account.contentUrl}nodes/${this.getId()}/content`, { + headers: headers, + body: form, + timeout: 3600000, + }) + .then(response => { + Logger.debug(`Response returned with status code ${response.statusCode}.`); + Logger.silly(`Response body: ${response.body}`); + stream.close(); - retval.data = JSON.parse(response.body); - retval.success = true; - this.replace(retval.data); + if (options.encrypt) { + Logger.debug(`Removing encrypted cache file at ${stream.path}`); + fs.unlinkSync(stream.path); + } - return this.save(() => { - this.emit('uploadComplete', response, response.body, retval, { - localPath: localPath, - remotePath: remotePath, - }); + this.getPath((err, remotePath) => { + if (err) { + return callback(err); + } - return callback(null, retval); - }); - }); - }) - .catch(err => { - stream.close(); - Logger.error(`Failed to overwrite file: ${err}`); - this.getPath((err, remotePath) => { - if (err) { - return callback(err); - } + retval.data = JSON.parse(response.body); + retval.success = true; + this.replace(retval.data); - if (options.retryAttempt >= options.numRetries) { - retval.data.message = 'Failed retry attempt(s). Skipping file.'; + return this.save(() => { + this.emit('uploadComplete', response, response.body, retval, { + localPath: localPath, + remotePath: remotePath, + }); - this.emit('uploadComplete', null, null, retval, { - localPath: localPath, - remotePath: remotePath, + return callback(null, retval); + }); }); + }) + .catch(err => { + stream.close(); - return callback(null, retval); - } - - return account.authorize(null, {force: true}, (authErr, data) => { - if (authErr) { - return callback(authErr); + if (options.encrypt) { + Logger.debug(`Removing encrypted cache file at ${stream.path}`); + fs.unlinkSync(stream.path); } - if (data.success) { - return account.sync({}, (syncErr, data) => { - if (syncErr) { - return callback(syncErr); - } - - let retryOptions = {}; - for (let key in options) { - retryOptions[key] = options[key]; - } - - retryOptions.retryAttempt++; + Logger.error(`Failed to overwrite file: ${err}`); + this.getPath((err, remotePath) => { + if (err) { + return callback(err); + } - retval.success = false; - retval.data.message = `${err}. Reauthenticating and retrying.`; - retval.retry = true; + if (options.retryAttempt >= options.numRetries) { + retval.data.message = 'Failed retry attempt(s). Skipping file.'; this.emit('uploadComplete', null, null, retval, { localPath: localPath, remotePath: remotePath, }); - return this.overwrite(localPath, retryOptions, callback); - }); - } + return callback(null, retval); + } + + return account.authorize(null, {force: true}, (authErr, data) => { + if (authErr) { + return callback(authErr); + } + + if (data.success) { + return account.sync({}, (syncErr, data) => { + if (syncErr) { + return callback(syncErr); + } + + let retryOptions = {}; + for (let key in options) { + retryOptions[key] = options[key]; + } + + retryOptions.retryAttempt++; + + retval.success = false; + retval.data.message = `${err}. Reauthenticating and retrying.`; + retval.retry = true; - return callback(Error(`Failed to reauthenticate with Cloud Drive: ${JSON.stringify(data.data)}`)); + this.emit('uploadComplete', null, null, retval, { + localPath: localPath, + remotePath: remotePath, + }); + + return this.overwrite(localPath, retryOptions, callback); + }); + } + + return callback(Error(`Failed to reauthenticate with Cloud Drive: ${JSON.stringify(data.data)}`)); + }); + }); }); - }); }); } @@ -983,6 +1011,24 @@ class Node extends ParameterBag { }); } + static getUploadStream(path, options, callback) { + let stream = fs.createReadStream(path); + if (!options.encrypt) { + Logger.debug('Not encrypting upload stream'); + + return callback(null, stream); + } + + Logger.debug(`Encrypting upload stream`); + let cipher = crypto.createCipher(algorithm, options.password), + tmpFile = `${path}.enc`; + + stream.pipe(cipher).pipe(fs.createWriteStream(tmpFile)) + .on('finish', () => { + callback(null, fs.createReadStream(tmpFile)); + }); + } + static init(userAccount, cacheStore) { if (initialized === false) { account = userAccount; @@ -1150,6 +1196,8 @@ class Node extends ParameterBag { retryAttempt: 0, numRetries: 0, ignoreFiles: null, + encrypt: false, + password: '', }; for (let key in options) { @@ -1268,100 +1316,119 @@ class Node extends ParameterBag { Node.emit('fileUpload', localPath); let progressInterval = null, form = new FormData(), - stream = fs.createReadStream(localPath); - - form.append('metadata', JSON.stringify({ - kind: 'FILE', - name: basename, - parents: [ - remoteFolder.getId(), - ], - })); - form.append('content', stream); + stream = Node.getUploadStream(localPath, { + encrypt: uploadOptions.encrypt, + password: uploadOptions.password, + }, (err, stream) => { + if (err) { + return callback(err); + } - stream.on('data', chunk => { - Node.emit('uploadProgress', localPath, chunk); - }); + form.append('metadata', JSON.stringify({ + kind: 'FILE', + name: basename, + parents: [ + remoteFolder.getId(), + ], + })); + form.append('content', stream); + + stream.on('data', chunk => { + Node.emit('uploadProgress', localPath, chunk); + }); - let headers = form.getHeaders(); - headers.Authorization = `Bearer ${account.token.access_token}`; + let headers = form.getHeaders(); + headers.Authorization = `Bearer ${account.token.access_token}`; + + Logger.verbose('Requesting nodes:files:upload endpoint'); + Logger.debug(`HTTP Request: POST '${account.contentUrl}nodes'`); + got.post(`${account.contentUrl}nodes`, { + headers: headers, + query: params, + body: form, + timeout: 3600000, + }) + .then(response => { + Logger.debug(`Response returned with status code ${response.statusCode}.`); + Logger.silly(`Response body: ${response.body}`); + stream.close(); + + if (uploadOptions.encrypt) { + Logger.debug(`Removing encrypted cache file at ${stream.path}`); + fs.unlinkSync(stream.path); + } - Logger.verbose('Requesting nodes:files:upload endpoint'); - Logger.debug(`HTTP Request: POST '${account.contentUrl}nodes'`); - got.post(`${account.contentUrl}nodes`, { - headers: headers, - query: params, - body: form, - timeout: 3600000, - }) - .then(response => { - Logger.debug(`Response returned with status code ${response.statusCode}.`); - Logger.silly(`Response body: ${response.body}`); - stream.close(); - retval.data.statusCode = response.statusCode; - retval.data = JSON.parse(response.body); - retval.success = true; - retval.data = new Node(retval.data); + retval.data.statusCode = response.statusCode; + retval.data = JSON.parse(response.body); + retval.success = true; + retval.data = new Node(retval.data); - return retval.data.save((err, data) => { - if (err) { - return callback(err); - } + return retval.data.save((err, data) => { + if (err) { + return callback(err); + } - Node.emit('uploadComplete', response, response.body, retval, { - localPath: localPath, - remotePath: remotePath, - }); + Node.emit('uploadComplete', response, response.body, retval, { + localPath: localPath, + remotePath: remotePath, + }); - return callback(null, retval); - }); - }) - .catch(err => { - stream.close(); - if (uploadOptions.retryAttempt >= uploadOptions.numRetries) { - retval.data.message = `${err.message}. Failed retry attempt(s). Skipping file.`; + return callback(null, retval); + }); + }) + .catch(err => { + stream.close(); - Node.emit('uploadComplete', null, err.message, retval, { - localPath: localPath, - remotePath: remotePath, - }); + if (uploadOptions.encrypt) { + Logger.debug(`Removing encrypted cache file at ${stream.path}`); + fs.unlinkSync(stream.path); + } - return callback(null, retval); - } + if (uploadOptions.retryAttempt >= uploadOptions.numRetries) { + retval.data.message = `${err.message}. Failed retry attempt(s). Skipping file.`; - return account.authorize(null, {force: true}, (authErr, data) => { - if (authErr) { - return callback(authErr); - } + Node.emit('uploadComplete', null, err.message, retval, { + localPath: localPath, + remotePath: remotePath, + }); - if (data.success) { - return account.sync({}, (syncErr, data) => { - if (syncErr) { - return callback(syncErr); - } + return callback(null, retval); + } - let retryOptions = {}; - for (let key in uploadOptions) { - retryOptions[key] = uploadOptions[key]; + return account.authorize(null, {force: true}, (authErr, data) => { + if (authErr) { + return callback(authErr); } - retryOptions.retryAttempt++; + if (data.success) { + return account.sync({}, (syncErr, data) => { + if (syncErr) { + return callback(syncErr); + } - retval.success = false; - retval.data.message = `${err}. Reauthenticating and retrying.`; - retval.retry = true; + let retryOptions = {}; + for (let key in uploadOptions) { + retryOptions[key] = uploadOptions[key]; + } - Node.emit('uploadComplete', null, err.message, retval, { - localPath: localPath, - remotePath: remotePath, - }); + retryOptions.retryAttempt++; - return Node.uploadFile(localPath, remotePath, retryOptions, callback); - }); - } + retval.success = false; + retval.data.message = `${err}. Reauthenticating and retrying.`; + retval.retry = true; - return callback(Error('Failed to reauthenticate with Cloud Drive: ' + JSON.stringify(data.data))); - }); + Node.emit('uploadComplete', null, err.message, retval, { + localPath: localPath, + remotePath: remotePath, + }); + + return Node.uploadFile(localPath, remotePath, retryOptions, callback); + }); + } + + return callback(Error('Failed to reauthenticate with Cloud Drive: ' + JSON.stringify(data.data))); + }); + }); }); }); }); diff --git a/lib/cli.js b/lib/cli.js index 73d5102..df1abb1 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -15,7 +15,7 @@ let semver = require('semver'), \\____/_/\\____/\\__,_/\\__,_/ /_____/_/ /_/ |___/\\___/ `; -// check that we're using Node.js 0.10 or newer +// check that we're using Node.js 0.12 or newer try { if (semver.lt(process.version, '0.12.0')) { console.error(`${chalk.cyan.bold(pkgJson.name)}, CLI version ${pkgJson.version} @@ -95,6 +95,12 @@ let cliConfig = { desc: 'Specify the remote node by its ID instead of path', type: 'boolean', }, + decrypt: { + group: 'Flags:', + demand: false, + desc: 'Decrypt files when downloading', + type: 'boolean', + }, remote: { group: 'Flags:', demand: false, @@ -198,17 +204,17 @@ let cliConfig = { desc: 'Specify the remote node by its ID instead of path', type: 'boolean', }, - remote: { + t: { group: 'Flags:', + alias: 'time', demand: false, - desc: 'Force the command to fetch from the API', + desc: 'Sort nodes by modified time', type: 'boolean', }, - t: { + remote: { group: 'Flags:', - alias: 'time', demand: false, - desc: 'Sort nodes by modified time', + desc: 'Force the command to fetch from the API', type: 'boolean', }, }, @@ -485,6 +491,12 @@ let cliConfig = { desc: 'Compare remote MD5 checksum instead of filesize', type: 'boolean', }, + encrypt: { + group: 'Flags:', + demand: false, + desc: 'Encrypt files before uploading', + type: 'boolean', + } }, file: './Commands/UploadCommand', }, @@ -604,6 +616,10 @@ let cliConfig = { type: 'bool', default: true, }, + 'encrypt.password': { + type: 'string', + default: '', + }, 'json.pretty': { type: 'bool', default: false, @@ -659,7 +675,7 @@ Logger.getInstance({ async.forEachOfSeries(cliConfig.commands, (command, name, callback) => { yargs.command(name, command.desc, yargs => { - return yargs.usage(`\n${chalk.magenta('Usage:')}\n ${name} ${command.usage}`) + return yargs.usage(`${command.desc}\n\n${chalk.magenta('Usage:')}\n ${name} ${command.usage}`) .options(command.options) .options(cliConfig.global.options) .demand(command.demand || 0) @@ -728,6 +744,7 @@ ${chalk.magenta('Usage:')} .updateStrings({ 'Commands:': chalk.magenta('Commands:'), 'Flags:': chalk.magenta('Flags:'), + 'Options:': chalk.magenta('Options:'), 'Global Flags:': chalk.magenta('Global Flags:'), }) .options(cliConfig.global.options) diff --git a/package.json b/package.json index adba018..7eac546 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "env-paths": "^0.1.0", "form-data": "^1.0.1", "fs-extra": "^1.0.0", - "got": "^6.5.0", + "got": "^6.6.3", "inquirer": "^0.10.1", "knex": "^0.8.6", "log-update": "^1.0.2",