common.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /**
  2. *
  3. * @constructor
  4. * @param {string} name A name for the collection
  5. * @param {Object} options
  6. * @param {FS.StorageAdapter[]} options.stores An array of stores in which files should be saved. At least one is required.
  7. * @param {Object} [options.filter] Filter definitions
  8. * @param {Number} [options.chunkSize=2MB] Override the chunk size in bytes for uploads
  9. * @param {Function} [options.uploader] A function to pass FS.File instances after inserting, which will begin uploading them. By default, `FS.HTTP.uploadQueue.uploadFile` is used if the `cfs-upload-http` package is present, or `FS.DDP.uploadQueue.uploadFile` is used if the `cfs-upload-ddp` package is present. You can override with your own, or set to `null` to prevent automatic uploading.
  10. * @returns {undefined}
  11. */
  12. FS.Collection = function(name, options) {
  13. var self = this;
  14. self.storesLookup = {};
  15. self.primaryStore = {};
  16. self.options = {
  17. filter: null, //optional
  18. stores: [], //required
  19. chunkSize: null
  20. };
  21. // Define a default uploader based on which upload packages are present,
  22. // preferring HTTP. You may override with your own function or
  23. // set to null to skip automatic uploading of data after file insert/update.
  24. if (FS.HTTP && FS.HTTP.uploadQueue) {
  25. self.options.uploader = FS.HTTP.uploadQueue.uploadFile;
  26. } else if (FS.DDP && FS.DDP.uploadQueue) {
  27. self.options.uploader = FS.DDP.uploadQueue.uploadFile;
  28. }
  29. // Extend and overwrite options
  30. FS.Utility.extend(self.options, options || {});
  31. // Set the FS.Collection name
  32. self.name = name;
  33. // Make sure at least one store has been supplied.
  34. // Usually the stores aren't used on the client, but we need them defined
  35. // so that we can access their names and use the first one as the default.
  36. if (FS.Utility.isEmpty(self.options.stores)) {
  37. throw new Error("You must specify at least one store. Please consult the documentation.");
  38. }
  39. FS.Utility.each(self.options.stores, function(store, i) {
  40. // Set the primary store
  41. if (i === 0) {
  42. self.primaryStore = store;
  43. }
  44. // Check for duplicate naming
  45. if (typeof self.storesLookup[store.name] !== 'undefined') {
  46. throw new Error('FS.Collection store names must be uniq, duplicate found: ' + store.name);
  47. }
  48. // Set the lookup
  49. self.storesLookup[store.name] = store;
  50. if (Meteor.isServer) {
  51. // Emit events based on store events
  52. store.on('stored', function (storeName, fileObj) {
  53. // This is weird, but currently there is a bug where each store will emit the
  54. // events for all other stores, too, so we need to make sure that this event
  55. // is truly for this store.
  56. if (storeName !== store.name)
  57. return;
  58. // When a file is successfully stored into the store, we emit a "stored" event on the FS.Collection only if the file belongs to this collection
  59. if (fileObj.collectionName === name) {
  60. var emitted = self.emit('stored', fileObj, store.name);
  61. if (FS.debug && !emitted) {
  62. console.log(fileObj.name({store: store.name}) + ' was successfully saved to the ' + store.name + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on the ' + name + ' collection.');
  63. }
  64. }
  65. fileObj.emit('stored', store.name);
  66. });
  67. store.on('error', function (storeName, error, fileObj) {
  68. // This is weird, but currently there is a bug where each store will emit the
  69. // events for all other stores, too, so we need to make sure that this event
  70. // is truly for this store.
  71. if (storeName !== store.name)
  72. return;
  73. // When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
  74. if (fileObj.collectionName === name) {
  75. error = new Error('Error storing file to the ' + store.name + ' store: ' + error.message);
  76. var emitted = self.emit('error', error, fileObj, store.name);
  77. if (FS.debug && !emitted) {
  78. console.log(error.message);
  79. }
  80. }
  81. fileObj.emit('error', store.name);
  82. });
  83. }
  84. });
  85. var _filesOptions = {
  86. transform: function(doc) {
  87. // This should keep the filerecord in the file object updated in reactive
  88. // context
  89. var result = new FS.File(doc, true);
  90. result.collectionName = name;
  91. return result;
  92. }
  93. };
  94. if(self.options.idGeneration) _filesOptions.idGeneration = self.options.idGeneration;
  95. // Enable specifying an alternate driver, to change where the filerecord is stored
  96. // Drivers can be created with MongoInternals.RemoteCollectionDriver()
  97. if(self.options._driver){
  98. _filesOptions._driver = self.options._driver;
  99. }
  100. // Create the 'cfs.' ++ ".filerecord" and use fsFile
  101. var collectionName = 'cfs.' + name + '.filerecord';
  102. self.files = new Mongo.Collection(collectionName, _filesOptions);
  103. // For storing custom allow/deny functions
  104. self._validators = {
  105. download: {allow: [], deny: []}
  106. };
  107. // Set up filters
  108. // XXX Should we deprecate the filter option now that this is done with a separate pkg, or just keep it?
  109. if (self.filters) {
  110. self.filters(self.options.filter);
  111. }
  112. // Save the collection reference (we want it without the 'cfs.' prefix and '.filerecord' suffix)
  113. FS._collections[name] = this;
  114. // Set up observers
  115. Meteor.isServer && FS.FileWorker && FS.FileWorker.observe(this);
  116. // Emit "removed" event on collection
  117. self.files.find().observe({
  118. removed: function(fileObj) {
  119. self.emit('removed', fileObj);
  120. }
  121. });
  122. // Emit events based on TempStore events
  123. if (FS.TempStore) {
  124. FS.TempStore.on('stored', function (fileObj, result) {
  125. // When a file is successfully stored into the temp store, we emit an "uploaded" event on the FS.Collection only if the file belongs to this collection
  126. if (fileObj.collectionName === name) {
  127. var emitted = self.emit('uploaded', fileObj);
  128. if (FS.debug && !emitted) {
  129. console.log(fileObj.name() + ' was successfully uploaded. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "uploaded" event on the ' + name + ' collection.');
  130. }
  131. }
  132. });
  133. FS.TempStore.on('error', function (error, fileObj) {
  134. // When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection
  135. if (fileObj.collectionName === name) {
  136. self.emit('error', new Error('Error storing uploaded file to TempStore: ' + error.message), fileObj);
  137. }
  138. });
  139. } else if (Meteor.isServer) {
  140. throw new Error("FS.Collection constructor: FS.TempStore must be defined before constructing any FS.Collections.")
  141. }
  142. };
  143. // An FS.Collection can emit events
  144. FS.Collection.prototype = new EventEmitter();