2
0

storageAdapter.server.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /* global FS, _storageAdapters:true, EventEmitter */
  2. // #############################################################################
  3. //
  4. // STORAGE ADAPTER
  5. //
  6. // #############################################################################
  7. _storageAdapters = {};
  8. FS.StorageAdapter = function(storeName, options, api) {
  9. var self = this, fileKeyMaker;
  10. options = options || {};
  11. // If storeName is the only argument, a string and the SA already found
  12. // we will just return that SA
  13. if (arguments.length === 1 && storeName === '' + storeName &&
  14. typeof _storageAdapters[storeName] !== 'undefined')
  15. return _storageAdapters[storeName];
  16. // Verify that the storage adapter defines all the necessary API methods
  17. if (typeof api === 'undefined') {
  18. throw new Error('FS.StorageAdapter please define an api');
  19. }
  20. FS.Utility.each('fileKey,remove,typeName,createReadStream,createWriteStream'.split(','), function(name) {
  21. if (typeof api[name] === 'undefined') {
  22. throw new Error('FS.StorageAdapter please define an api. "' + name + '" ' + (api.typeName || ''));
  23. }
  24. });
  25. // Create an internal namespace, starting a name with underscore is only
  26. // allowed for stores marked with options.internal === true
  27. if (options.internal !== true && storeName[0] === '_') {
  28. throw new Error('A storage adapter name may not begin with "_"');
  29. }
  30. if (storeName.indexOf('.') !== -1) {
  31. throw new Error('A storage adapter name may not contain a "."');
  32. }
  33. // store reference for easy lookup by storeName
  34. if (typeof _storageAdapters[storeName] !== 'undefined') {
  35. throw new Error('Storage name already exists: "' + storeName + '"');
  36. } else {
  37. _storageAdapters[storeName] = self;
  38. }
  39. // User can customize the file key generation function
  40. if (typeof options.fileKeyMaker === "function") {
  41. fileKeyMaker = options.fileKeyMaker;
  42. } else {
  43. fileKeyMaker = api.fileKey;
  44. }
  45. // User can provide a function to adjust the fileObj
  46. // before it is written to the store.
  47. var beforeWrite = options.beforeWrite;
  48. // extend self with options and other info
  49. FS.Utility.extend(this, options, {
  50. name: storeName,
  51. typeName: api.typeName
  52. });
  53. // Create a nicer abstracted adapter interface
  54. self.adapter = {};
  55. self.adapter.fileKey = function(fileObj) {
  56. return fileKeyMaker(fileObj);
  57. };
  58. // Return readable stream for fileKey
  59. self.adapter.createReadStreamForFileKey = function(fileKey, options) {
  60. if (FS.debug) console.log('createReadStreamForFileKey ' + storeName);
  61. return FS.Utility.safeStream( api.createReadStream(fileKey, options) );
  62. };
  63. // Return readable stream for fileObj
  64. self.adapter.createReadStream = function(fileObj, options) {
  65. if (FS.debug) console.log('createReadStream ' + storeName);
  66. if (self.internal) {
  67. // Internal stores take a fileKey
  68. return self.adapter.createReadStreamForFileKey(fileObj, options);
  69. }
  70. return FS.Utility.safeStream( self._transform.createReadStream(fileObj, options) );
  71. };
  72. function logEventsForStream(stream) {
  73. if (FS.debug) {
  74. stream.on('stored', function() {
  75. console.log('-----------STORED STREAM', storeName);
  76. });
  77. stream.on('close', function() {
  78. console.log('-----------CLOSE STREAM', storeName);
  79. });
  80. stream.on('end', function() {
  81. console.log('-----------END STREAM', storeName);
  82. });
  83. stream.on('finish', function() {
  84. console.log('-----------FINISH STREAM', storeName);
  85. });
  86. stream.on('error', function(error) {
  87. console.log('-----------ERROR STREAM', storeName, error && (error.message || error.code));
  88. });
  89. }
  90. }
  91. // Return writeable stream for fileKey
  92. self.adapter.createWriteStreamForFileKey = function(fileKey, options) {
  93. if (FS.debug) console.log('createWriteStreamForFileKey ' + storeName);
  94. var writeStream = FS.Utility.safeStream( api.createWriteStream(fileKey, options) );
  95. logEventsForStream(writeStream);
  96. return writeStream;
  97. };
  98. // Return writeable stream for fileObj
  99. self.adapter.createWriteStream = function(fileObj, options) {
  100. if (FS.debug) console.log('createWriteStream ' + storeName + ', internal: ' + !!self.internal);
  101. if (self.internal) {
  102. // Internal stores take a fileKey
  103. return self.adapter.createWriteStreamForFileKey(fileObj, options);
  104. }
  105. // If we haven't set name, type, or size for this version yet,
  106. // set it to same values as original version. We don't save
  107. // these to the DB right away because they might be changed
  108. // in a transformWrite function.
  109. if (!fileObj.name({store: storeName})) {
  110. fileObj.name(fileObj.name(), {store: storeName, save: false});
  111. }
  112. if (!fileObj.type({store: storeName})) {
  113. fileObj.type(fileObj.type(), {store: storeName, save: false});
  114. }
  115. if (!fileObj.size({store: storeName})) {
  116. fileObj.size(fileObj.size(), {store: storeName, save: false});
  117. }
  118. // Call user function to adjust file metadata for this store.
  119. // We support updating name, extension, and/or type based on
  120. // info returned in an object. Or `fileObj` could be
  121. // altered directly within the beforeWrite function.
  122. if (beforeWrite) {
  123. var fileChanges = beforeWrite(fileObj);
  124. if (typeof fileChanges === "object") {
  125. if (fileChanges.extension) {
  126. fileObj.extension(fileChanges.extension, {store: storeName, save: false});
  127. } else if (fileChanges.name) {
  128. fileObj.name(fileChanges.name, {store: storeName, save: false});
  129. }
  130. if (fileChanges.type) {
  131. fileObj.type(fileChanges.type, {store: storeName, save: false});
  132. }
  133. }
  134. }
  135. var writeStream = FS.Utility.safeStream( self._transform.createWriteStream(fileObj, options) );
  136. logEventsForStream(writeStream);
  137. // Its really only the storage adapter who knows if the file is uploaded
  138. //
  139. // We have to use our own event making sure the storage process is completed
  140. // this is mainly
  141. writeStream.safeOn('stored', function(result) {
  142. if (typeof result.fileKey === 'undefined') {
  143. throw new Error('SA ' + storeName + ' type ' + api.typeName + ' did not return a fileKey');
  144. }
  145. if (FS.debug) console.log('SA', storeName, 'stored', result.fileKey);
  146. // Set the fileKey
  147. fileObj.copies[storeName].key = result.fileKey;
  148. // Update the size, as provided by the SA, in case it was changed by stream transformation
  149. if (typeof result.size === "number") {
  150. fileObj.copies[storeName].size = result.size;
  151. }
  152. // Set last updated time, either provided by SA or now
  153. fileObj.copies[storeName].updatedAt = result.storedAt || new Date();
  154. // If the file object copy havent got a createdAt then set this
  155. if (typeof fileObj.copies[storeName].createdAt === 'undefined') {
  156. fileObj.copies[storeName].createdAt = fileObj.copies[storeName].updatedAt;
  157. }
  158. fileObj._saveChanges(storeName);
  159. // There is code in transform that may have set the original file size, too.
  160. fileObj._saveChanges('_original');
  161. });
  162. // Emit events from SA
  163. writeStream.once('stored', function(/*result*/) {
  164. // XXX Because of the way stores inherit from SA, this will emit on every store.
  165. // Maybe need to rewrite the way we inherit from SA?
  166. var emitted = self.emit('stored', storeName, fileObj);
  167. if (FS.debug && !emitted) {
  168. 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.');
  169. }
  170. });
  171. writeStream.on('error', function(error) {
  172. // XXX We could wrap and clarify error
  173. // XXX Because of the way stores inherit from SA, this will emit on every store.
  174. // Maybe need to rewrite the way we inherit from SA?
  175. var emitted = self.emit('error', storeName, error, fileObj);
  176. if (FS.debug && !emitted) {
  177. console.log(error);
  178. }
  179. });
  180. return writeStream;
  181. };
  182. //internal
  183. self._removeAsync = function(fileKey, callback) {
  184. // Remove the file from the store
  185. api.remove.call(self, fileKey, callback);
  186. };
  187. /**
  188. * @method FS.StorageAdapter.prototype.remove
  189. * @public
  190. * @param {FS.File} fsFile The FS.File instance to be stored.
  191. * @param {Function} [callback] If not provided, will block and return true or false
  192. *
  193. * Attempts to remove a file from the store. Returns true if removed or not
  194. * found, or false if the file couldn't be removed.
  195. */
  196. self.adapter.remove = function(fileObj, callback) {
  197. if (FS.debug) console.log("---SA REMOVE");
  198. // Get the fileKey
  199. var fileKey = (fileObj instanceof FS.File) ? self.adapter.fileKey(fileObj) : fileObj;
  200. if (callback) {
  201. return self._removeAsync(fileKey, FS.Utility.safeCallback(callback));
  202. } else {
  203. return Meteor.wrapAsync(self._removeAsync)(fileKey);
  204. }
  205. };
  206. self.remove = function(fileObj, callback) {
  207. // Add deprecation note
  208. console.warn('Storage.remove is deprecating, use "Storage.adapter.remove"');
  209. return self.adapter.remove(fileObj, callback);
  210. };
  211. if (typeof api.init === 'function') {
  212. Meteor.wrapAsync(api.init.bind(self))();
  213. }
  214. // This supports optional transformWrite and transformRead
  215. self._transform = new FS.Transform({
  216. adapter: self.adapter,
  217. // Optional transformation functions:
  218. transformWrite: options.transformWrite,
  219. transformRead: options.transformRead
  220. });
  221. };
  222. Npm.require('util').inherits(FS.StorageAdapter, EventEmitter);