UNPKG

aws-commons

Version:

A collection of small utilities to manage AWS services and actions

164 lines (128 loc) 4.89 kB
/** * @class CopyToS3 * @classdesc CopyToS3 is a generic script to copy specified file from disk to S3. This script is intended to copy * large files to S3 in a separate thread where the file size may run a risk of overwhelming the calling * service. * * CopyToS3 is evented and emits the following events: * - progress : a message fired at each step; file stat, read, and copy * - complete : fired when the copy has been completed * * @author: darryl.west@raincitysoftware.com * @created: 4/12/14 7:57 PM */ const events = require('events' ), util = require('util' ), crypto = require('crypto' ), mime = require('mime-types'); /** * @param options - source file, target bucket/key, s3 instance * @constructor */ const CopyToS3 = function(options) { 'use strict'; const copier = this, fs = options.fs || require('fs'), log = options.log, bucket = options.bucket, s3 = options.s3, sourceFile = options.sourceFile, key = options.key, acl = options.acl || 'public-read'; let stats = {}; let callback = null; let contentType = options.contentType; this.copyCompleteCallback = function(err, response) { stats.endTime = Date.now(); stats.elapsedTime = stats.endTime - stats.startTime; if (!err) { // remove the amazon response quotes stats.etag = response.ETag.replace(/"/g, ''); if (stats.etag === stats.md5) { stats.dataVerified = true; } else { stats.dataVerified = false; stats.message = util.format('file copy failed, hashes do not match: file md5 %s, S3 etag %s...', stats.md5, stats.etag); err = new Error( stats.message ); } } if (err) { log.error("copy failed: ", err.message); stats.error = err.message; copier.emit('error', stats); } log.info('copy complete, stats:', util.format('%j', stats)); copier.emit('complete', stats); if (typeof callback === 'function') { setImmediate(() => { callback(err, stats); callback = null; }); } }; this.readFileCallback = function(err, data) { if (err) { return copier.copyCompleteCallback( err ); } stats.message = 'file data read'; stats.md5 = crypto.createHash('md5').update( data ).digest( 'hex' ); log.info('copy user file:', sourceFile, 'to S3:', bucket + '/' + key,', bytes:', data.length); if (!contentType) { contentType = mime.lookup( key ) || 'text/plain'; } log.info(`using default mime type for ${key}, ${contentType}`); const params = { Bucket:bucket, Key:key, Body:data, ContentType:contentType }; if (acl !== 'private') { params.ACL = acl; } log.info('mime-type: ', params.ContentType); s3.putObject( params, copier.copyCompleteCallback ); }; this.readFile = function() { stats.message = 'read the source file: ' + sourceFile; log.info(stats.message); copier.emit('progress', stats); fs.readFile(sourceFile, copier.readFileCallback); }; this.statFileCallback = function(err, fileStat) { stats.message = 'file stat complete'; if (err) { log.error( err.message ); log.info('exiting without copy...'); stats.message = 'exiting without copy'; copier.copyCompleteCallback( err ); } else { log.debug( fileStat ); stats.fileSize = fileStat.size; stats.fileMTime = fileStat.mtime; copier.emit('progress', stats); copier.readFile(); } }; this.copy = function(completeCallback) { // begin with a new stats object callback = completeCallback; stats = { message:'read file stats for ' + sourceFile, startTime:Date.now(), sourceFile:sourceFile }; log.info( stats.message ); copier.emit('progress', stats); fs.stat(sourceFile, copier.statFileCallback); }; // constructor validations if (!log) throw new Error('copier must be constructed with a log'); if (!bucket) throw new Error('copier must be constructed with a bucket name'); if (!s3) throw new Error('copier must be constructed with an S3 object'); if (!sourceFile) throw new Error('copier must be constructed with a source file name'); if (!key) throw new Error('copier must be constructed with a target key'); events.EventEmitter.call( this ); }; util.inherits( CopyToS3, events.EventEmitter ); module.exports = CopyToS3;