Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

Commit

Permalink
Add helpers for file upload (#61)
Browse files Browse the repository at this point in the history
* Add helpers for file upload

* Add verification for bytes size

* Tune quote position

* tune error text
  • Loading branch information
Mykola Mokhnach committed Jan 18, 2018
1 parent 0371942 commit 2f79d46
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 2 deletions.
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as system from './lib/system';
import * as util from './lib/util'; // eslint-disable-line import/no-duplicates
import { cancellableDelay } from './lib/util'; // eslint-disable-line import/no-duplicates
import fs from './lib/fs';
import * as net from './lib/net';
import * as plist from './lib/plist';
import { mkdirp } from './lib/mkdirp';
import * as logger from './lib/logging';
Expand All @@ -13,4 +14,4 @@ import * as imageUtil from './lib/image-util';
// can't add to other exports `as default`
// until JSHint figures out how to parse that pattern
export default { tempDir, system, util, fs, cancellableDelay, plist, mkdirp,
logger, process, zip, imageUtil };
logger, process, zip, imageUtil, net };
66 changes: 66 additions & 0 deletions lib/net.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { createReadStream } from 'fs';
import { stat, exists } from './fs';
import url from 'url';
import B from 'bluebird';
import { toReadableSizeString } from './util';
import log from './logger';
import request from 'request-promise';
import Ftp from 'jsftp';


async function uploadFileToHttp (localFileStream, remoteUrl, uploadOptions = {}) {
log.debug(`${remoteUrl.protocol} upload options: ${JSON.stringify(uploadOptions)}`);
const response = await request(uploadOptions);
const responseDebugMsg = `Response code: ${response.statusCode}. ` +
`Response body: ${JSON.stringify(response.body)}`;
log.debug(responseDebugMsg);
if (response.statusCode >= 400) {
throw new Error(`Cannot upload the recorded media to '${remoteUrl.href}'. ${responseDebugMsg}`);
}
}

async function uploadFileToFtp (localFileStream, remoteUrl, uploadOptions = {}) {
log.debug(`${remoteUrl.protocol} upload options: ${JSON.stringify(uploadOptions)}`);
return await new B((resolve, reject) => {
new Ftp(uploadOptions).put(localFileStream, remoteUrl.pathname, (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
}

/**
* Uploads the given file to a remote location. HTTP(S) and FTP
* protocols are supported.
*
* @param {string} localPath - The path to a file on the local storage.
* @param {string} remotePath - The remote upload URL to upload the file to.
* @param {Object} uploadOptions - The options set, which depends on the protocol set for remotePath.
* See https://www.npmjs.com/package/request-promise and
* https://www.npmjs.com/package/jsftp for more details.
*/
async function uploadFile (localPath, remotePath, uploadOptions = {}) {
if (!await exists(localPath)) {
throw new Error (`'${localPath}' does not exists or is not accessible`);
}
const remoteUrl = url.parse(remotePath);
const {size} = await stat(localPath);
const localFileStream = createReadStream(localPath);
log.info(`Uploading '${localPath}' of ${toReadableSizeString(size)} size to '${remotePath}'...`);
const timeStarted = process.hrtime();
if (remoteUrl.protocol.startsWith('http')) {
await uploadFileToHttp(localFileStream, remoteUrl, uploadOptions);
} else if (remoteUrl.protocol === 'ftp') {
await uploadFileToFtp(localFileStream, remoteUrl, uploadOptions);
} else {
throw new Error(`Cannot upload the file at '${localPath}' to '${remotePath}'. ` +
`Unsupported remote protocol '${remoteUrl.protocol}'. ` +
`Only http/https and ftp protocols are supported.`);
}
const timeElapsed = process.hrtime(timeStarted)[0];
log.info(`Uploaded '${localPath}' of ${toReadableSizeString(size)} size in ${timeElapsed} second${timeElapsed === 1 ? '' : 's'}`);
}

export { uploadFile };
27 changes: 26 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,30 @@ function filterObject (obj, predicate) {
return newObj;
}

/**
* Converts number of bytes to a readable size string.
*
* @param {number|string} bytes - The actual number of bytes.
* @returns {string} The actual string representation, for example
* '1.00 KB' for '1024 B'
* @throws {Error} If bytes count cannot be converted to an integer or
* if it is less than zero.
*/
function toReadableSizeString (bytes) {
const intBytes = parseInt(bytes, 10);
if (isNaN(intBytes) || intBytes < 0) {
throw new Error(`Cannot convert '${bytes}' to a readable size format`);
}
if (intBytes >= 1024 * 1024 * 1024) {
return `${parseFloat(intBytes / (1024 * 1024 * 1024.0)).toFixed(2)} GB`;
} else if (intBytes >= 1024 * 1024) {
return `${parseFloat(intBytes / (1024 * 1024.0)).toFixed(2)} MB`;
} else if (intBytes >= 1024) {
return `${parseFloat(intBytes / 1024.0).toFixed(2)} KB`;
}
return `${intBytes} B`;
}

export { hasValue, escapeSpace, escapeSpecialChars, localIp, cancellableDelay,
multiResolve, safeJsonParse, unwrapElement, filterObject };
multiResolve, safeJsonParse, unwrapElement, filterObject,
toReadableSizeString };
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"bplist-parser": "^0.1.0",
"extract-zip": "^1.6.0",
"glob": "^6.0.4",
"jsftp": "^2.1.2",
"lodash": "^4.2.1",
"md5-file": "^2.0.4",
"mkdirp": "^0.5.1",
Expand All @@ -38,6 +39,8 @@
"npmlog": "^2.0.4",
"plist": "^1.2.0",
"rimraf": "^2.5.1",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"source-map-support": "^0.4.6",
"teen_process": "^1.5.1",
"which": "^1.2.4",
Expand Down
18 changes: 18 additions & 0 deletions test/util-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,24 @@ describe('util', function () {
});
});

describe('toReadableSizeString', function () {
it('should fail if cannot convert to Bytes', function () {
(() => util.toReadableSizeString('asdasd')).should.throw(/Cannot convert/);
});
it('should properly convert to Bytes', function () {
util.toReadableSizeString(0).should.equal('0 B');
});
it('should properly convert to KBytes', function () {
util.toReadableSizeString(2048 + 12).should.equal('2.01 KB');
});
it('should properly convert to MBytes', function () {
util.toReadableSizeString(1024 * 1024 * 3 + 1024 * 10).should.equal('3.01 MB');
});
it('should properly convert to GBytes', function () {
util.toReadableSizeString(1024 * 1024 * 1024 * 5).should.equal('5.00 GB');
});
});

describe('filterObject', function () {
describe('with undefined predicate', function () {
it('should filter out undefineds', function () {
Expand Down

0 comments on commit 2f79d46

Please sign in to comment.