123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- /* global FS, _storageAdapters:true, EventEmitter */
- // #############################################################################
- //
- // STORAGE ADAPTER
- //
- // #############################################################################
- _storageAdapters = {};
- FS.StorageAdapter = function(storeName, options, api) {
- var self = this, fileKeyMaker;
- options = options || {};
- // If storeName is the only argument, a string and the SA already found
- // we will just return that SA
- if (arguments.length === 1 && storeName === '' + storeName &&
- typeof _storageAdapters[storeName] !== 'undefined')
- return _storageAdapters[storeName];
- // Verify that the storage adapter defines all the necessary API methods
- if (typeof api === 'undefined') {
- throw new Error('FS.StorageAdapter please define an api');
- }
-
- FS.Utility.each('fileKey,remove,typeName,createReadStream,createWriteStream'.split(','), function(name) {
- if (typeof api[name] === 'undefined') {
- throw new Error('FS.StorageAdapter please define an api. "' + name + '" ' + (api.typeName || ''));
- }
- });
- // Create an internal namespace, starting a name with underscore is only
- // allowed for stores marked with options.internal === true
- if (options.internal !== true && storeName[0] === '_') {
- throw new Error('A storage adapter name may not begin with "_"');
- }
- if (storeName.indexOf('.') !== -1) {
- throw new Error('A storage adapter name may not contain a "."');
- }
- // store reference for easy lookup by storeName
- if (typeof _storageAdapters[storeName] !== 'undefined') {
- throw new Error('Storage name already exists: "' + storeName + '"');
- } else {
- _storageAdapters[storeName] = self;
- }
- // User can customize the file key generation function
- if (typeof options.fileKeyMaker === "function") {
- fileKeyMaker = options.fileKeyMaker;
- } else {
- fileKeyMaker = api.fileKey;
- }
- // User can provide a function to adjust the fileObj
- // before it is written to the store.
- var beforeWrite = options.beforeWrite;
- // extend self with options and other info
- FS.Utility.extend(this, options, {
- name: storeName,
- typeName: api.typeName
- });
- // Create a nicer abstracted adapter interface
- self.adapter = {};
- self.adapter.fileKey = function(fileObj) {
- return fileKeyMaker(fileObj);
- };
- // Return readable stream for fileKey
- self.adapter.createReadStreamForFileKey = function(fileKey, options) {
- if (FS.debug) console.log('createReadStreamForFileKey ' + storeName);
- return FS.Utility.safeStream( api.createReadStream(fileKey, options) );
- };
- // Return readable stream for fileObj
- self.adapter.createReadStream = function(fileObj, options) {
- if (FS.debug) console.log('createReadStream ' + storeName);
- if (self.internal) {
- // Internal stores take a fileKey
- return self.adapter.createReadStreamForFileKey(fileObj, options);
- }
- return FS.Utility.safeStream( self._transform.createReadStream(fileObj, options) );
- };
- function logEventsForStream(stream) {
- if (FS.debug) {
- stream.on('stored', function() {
- console.log('-----------STORED STREAM', storeName);
- });
- stream.on('close', function() {
- console.log('-----------CLOSE STREAM', storeName);
- });
- stream.on('end', function() {
- console.log('-----------END STREAM', storeName);
- });
- stream.on('finish', function() {
- console.log('-----------FINISH STREAM', storeName);
- });
- stream.on('error', function(error) {
- console.log('-----------ERROR STREAM', storeName, error && (error.message || error.code));
- });
- }
- }
- // Return writeable stream for fileKey
- self.adapter.createWriteStreamForFileKey = function(fileKey, options) {
- if (FS.debug) console.log('createWriteStreamForFileKey ' + storeName);
- var writeStream = FS.Utility.safeStream( api.createWriteStream(fileKey, options) );
- logEventsForStream(writeStream);
- return writeStream;
- };
- // Return writeable stream for fileObj
- self.adapter.createWriteStream = function(fileObj, options) {
- if (FS.debug) console.log('createWriteStream ' + storeName + ', internal: ' + !!self.internal);
-
- if (self.internal) {
- // Internal stores take a fileKey
- return self.adapter.createWriteStreamForFileKey(fileObj, options);
- }
- // If we haven't set name, type, or size for this version yet,
- // set it to same values as original version. We don't save
- // these to the DB right away because they might be changed
- // in a transformWrite function.
- if (!fileObj.name({store: storeName})) {
- fileObj.name(fileObj.name(), {store: storeName, save: false});
- }
- if (!fileObj.type({store: storeName})) {
- fileObj.type(fileObj.type(), {store: storeName, save: false});
- }
- if (!fileObj.size({store: storeName})) {
- fileObj.size(fileObj.size(), {store: storeName, save: false});
- }
- // Call user function to adjust file metadata for this store.
- // We support updating name, extension, and/or type based on
- // info returned in an object. Or `fileObj` could be
- // altered directly within the beforeWrite function.
- if (beforeWrite) {
- var fileChanges = beforeWrite(fileObj);
- if (typeof fileChanges === "object") {
- if (fileChanges.extension) {
- fileObj.extension(fileChanges.extension, {store: storeName, save: false});
- } else if (fileChanges.name) {
- fileObj.name(fileChanges.name, {store: storeName, save: false});
- }
- if (fileChanges.type) {
- fileObj.type(fileChanges.type, {store: storeName, save: false});
- }
- }
- }
- var writeStream = FS.Utility.safeStream( self._transform.createWriteStream(fileObj, options) );
- logEventsForStream(writeStream);
- // Its really only the storage adapter who knows if the file is uploaded
- //
- // We have to use our own event making sure the storage process is completed
- // this is mainly
- writeStream.safeOn('stored', function(result) {
- if (typeof result.fileKey === 'undefined') {
- throw new Error('SA ' + storeName + ' type ' + api.typeName + ' did not return a fileKey');
- }
- if (FS.debug) console.log('SA', storeName, 'stored', result.fileKey);
- // Set the fileKey
- fileObj.copies[storeName].key = result.fileKey;
- // Update the size, as provided by the SA, in case it was changed by stream transformation
- if (typeof result.size === "number") {
- fileObj.copies[storeName].size = result.size;
- }
- // Set last updated time, either provided by SA or now
- fileObj.copies[storeName].updatedAt = result.storedAt || new Date();
- // If the file object copy havent got a createdAt then set this
- if (typeof fileObj.copies[storeName].createdAt === 'undefined') {
- fileObj.copies[storeName].createdAt = fileObj.copies[storeName].updatedAt;
- }
- fileObj._saveChanges(storeName);
- // There is code in transform that may have set the original file size, too.
- fileObj._saveChanges('_original');
- });
- // Emit events from SA
- writeStream.once('stored', function(/*result*/) {
- // XXX Because of the way stores inherit from SA, this will emit on every store.
- // Maybe need to rewrite the way we inherit from SA?
- var emitted = self.emit('stored', storeName, fileObj);
- if (FS.debug && !emitted) {
- console.log(fileObj.name() + ' was successfully stored in the ' + storeName + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on this store.');
- }
- });
- writeStream.on('error', function(error) {
- // XXX We could wrap and clarify error
- // XXX Because of the way stores inherit from SA, this will emit on every store.
- // Maybe need to rewrite the way we inherit from SA?
- var emitted = self.emit('error', storeName, error, fileObj);
- if (FS.debug && !emitted) {
- console.log(error);
- }
- });
- return writeStream;
- };
- //internal
- self._removeAsync = function(fileKey, callback) {
- // Remove the file from the store
- api.remove.call(self, fileKey, callback);
- };
- /**
- * @method FS.StorageAdapter.prototype.remove
- * @public
- * @param {FS.File} fsFile The FS.File instance to be stored.
- * @param {Function} [callback] If not provided, will block and return true or false
- *
- * Attempts to remove a file from the store. Returns true if removed or not
- * found, or false if the file couldn't be removed.
- */
- self.adapter.remove = function(fileObj, callback) {
- if (FS.debug) console.log("---SA REMOVE");
- // Get the fileKey
- var fileKey = (fileObj instanceof FS.File) ? self.adapter.fileKey(fileObj) : fileObj;
- if (callback) {
- return self._removeAsync(fileKey, FS.Utility.safeCallback(callback));
- } else {
- return Meteor.wrapAsync(self._removeAsync)(fileKey);
- }
- };
- self.remove = function(fileObj, callback) {
- // Add deprecation note
- console.warn('Storage.remove is deprecating, use "Storage.adapter.remove"');
- return self.adapter.remove(fileObj, callback);
- };
- if (typeof api.init === 'function') {
- Meteor.wrapAsync(api.init.bind(self))();
- }
- // This supports optional transformWrite and transformRead
- self._transform = new FS.Transform({
- adapter: self.adapter,
- // Optional transformation functions:
- transformWrite: options.transformWrite,
- transformRead: options.transformRead
- });
- };
- Npm.require('util').inherits(FS.StorageAdapter, EventEmitter);
|