2
0

fsFile-common.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. /**
  2. * @method FS.File
  3. * @namespace FS.File
  4. * @public
  5. * @constructor
  6. * @param {object|FS.File|data to attach} [ref] Another FS.File instance, a filerecord, or some data to pass to attachData
  7. */
  8. FS.File = function(ref, createdByTransform) {
  9. var self = this;
  10. self.createdByTransform = !!createdByTransform;
  11. if (ref instanceof FS.File || isBasicObject(ref)) {
  12. // Extend self with filerecord related data
  13. FS.Utility.extend(self, FS.Utility.cloneFileRecord(ref, {full: true}));
  14. } else if (ref) {
  15. self.attachData(ref);
  16. }
  17. };
  18. // An FS.File can emit events
  19. FS.File.prototype = new EventEmitter();
  20. /**
  21. * @method FS.File.prototype.attachData
  22. * @public
  23. * @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to attach to the file.
  24. * @param {Object} [options] Options
  25. * @param {String} [options.type] The data content (MIME) type, if known.
  26. * @param {String} [options.headers] When attaching a URL, headers to be used for the GET request (currently server only)
  27. * @param {String} [options.auth] When attaching a URL, "username:password" to be used for the GET request (currently server only)
  28. * @param {Function} [callback] Callback function, callback(error). On the client, a callback is required if data is a URL.
  29. * @returns {FS.File} This FS.File instance.
  30. *
  31. */
  32. FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) {
  33. var self = this;
  34. if (!callback && typeof options === "function") {
  35. callback = options;
  36. options = {};
  37. }
  38. options = options || {};
  39. if (!data) {
  40. throw new Error('FS.File.attachData requires a data argument with some data');
  41. }
  42. var urlOpts;
  43. // Set any other properties we can determine from the source data
  44. // File
  45. if (typeof File !== "undefined" && data instanceof File) {
  46. self.name(data.name);
  47. self.updatedAt(data.lastModifiedDate);
  48. self.size(data.size);
  49. setData(data.type);
  50. }
  51. // Blob
  52. else if (typeof Blob !== "undefined" && data instanceof Blob) {
  53. self.name(data.name);
  54. self.updatedAt(new Date());
  55. self.size(data.size);
  56. setData(data.type);
  57. }
  58. // URL: we need to do a HEAD request to get the type because type
  59. // is required for filtering to work.
  60. else if (typeof data === "string" && (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:")) {
  61. urlOpts = FS.Utility.extend({}, options);
  62. if (urlOpts.type) {
  63. delete urlOpts.type;
  64. }
  65. if (!callback) {
  66. if (Meteor.isClient) {
  67. throw new Error('FS.File.attachData requires a callback when attaching a URL on the client');
  68. }
  69. var result = Meteor.call('_cfs_getUrlInfo', data, urlOpts);
  70. FS.Utility.extend(self, {original: result});
  71. setData(result.type);
  72. } else {
  73. Meteor.call('_cfs_getUrlInfo', data, urlOpts, function (error, result) {
  74. FS.debug && console.log("URL HEAD RESULT:", result);
  75. if (error) {
  76. callback(error);
  77. } else {
  78. var type = result.type || options.type;
  79. if (! type) {
  80. throw new Error('FS.File.attachData got a URL for which it could not determine the MIME type and none was provided using options.type');
  81. }
  82. FS.Utility.extend(self, {original: result});
  83. setData(type);
  84. }
  85. });
  86. }
  87. }
  88. // Everything else
  89. else {
  90. setData(options.type);
  91. }
  92. // Set the data
  93. function setData(type) {
  94. self.data = new DataMan(data, type, urlOpts);
  95. // Update the type to match what the data is
  96. self.type(self.data.type());
  97. // Update the size to match what the data is.
  98. // It's always safe to call self.data.size() without supplying a callback
  99. // because it requires a callback only for URLs on the client, and we
  100. // already added size for URLs when we got the result from '_cfs_getUrlInfo' method.
  101. if (!self.size()) {
  102. if (callback) {
  103. self.data.size(function (error, size) {
  104. if (error) {
  105. callback && callback(error);
  106. } else {
  107. self.size(size);
  108. setName();
  109. }
  110. });
  111. } else {
  112. self.size(self.data.size());
  113. setName();
  114. }
  115. } else {
  116. setName();
  117. }
  118. }
  119. function setName() {
  120. // See if we can extract a file name from URL or filepath
  121. if (!self.name() && typeof data === "string") {
  122. // name from URL
  123. if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
  124. if (FS.Utility.getFileExtension(data).length) {
  125. // for a URL we assume the end is a filename only if it has an extension
  126. self.name(FS.Utility.getFileName(data));
  127. }
  128. }
  129. // name from filepath
  130. else if (data.slice(0, 5) !== "data:") {
  131. self.name(FS.Utility.getFileName(data));
  132. }
  133. }
  134. callback && callback();
  135. }
  136. return self; //allow chaining
  137. };
  138. /**
  139. * @method FS.File.prototype.uploadProgress
  140. * @public
  141. * @returns {number} The server confirmed upload progress
  142. */
  143. FS.File.prototype.uploadProgress = function() {
  144. var self = this;
  145. // Make sure our file record is updated
  146. self.getFileRecord();
  147. // If fully uploaded, return 100
  148. if (self.uploadedAt) {
  149. return 100;
  150. }
  151. // Otherwise return the confirmed progress or 0
  152. else {
  153. return Math.round((self.chunkCount || 0) / (self.chunkSum || 1) * 100);
  154. }
  155. };
  156. /**
  157. * @method FS.File.prototype.controlledByDeps
  158. * @public
  159. * @returns {FS.Collection} Returns true if this FS.File is reactive
  160. *
  161. * > Note: Returns true if this FS.File object was created by a FS.Collection
  162. * > and we are in a reactive computations. What does this mean? Well it should
  163. * > mean that our fileRecord is fully updated by Meteor and we are mounted on
  164. * > a collection
  165. */
  166. FS.File.prototype.controlledByDeps = function() {
  167. var self = this;
  168. return self.createdByTransform && Deps.active;
  169. };
  170. /**
  171. * @method FS.File.prototype.getCollection
  172. * @public
  173. * @returns {FS.Collection} Returns attached collection or undefined if not mounted
  174. */
  175. FS.File.prototype.getCollection = function() {
  176. // Get the collection reference
  177. var self = this;
  178. // If we already made the link then do no more
  179. if (self.collection) {
  180. return self.collection;
  181. }
  182. // If we don't have a collectionName then there's not much to do, the file is
  183. // not mounted yet
  184. if (!self.collectionName) {
  185. // Should not throw an error here - could be common that the file is not
  186. // yet mounted into a collection
  187. return;
  188. }
  189. // Link the collection to the file
  190. self.collection = FS._collections[self.collectionName];
  191. return self.collection; //possibly undefined, but that's desired behavior
  192. };
  193. /**
  194. * @method FS.File.prototype.isMounted
  195. * @public
  196. * @returns {FS.Collection} Returns attached collection or undefined if not mounted
  197. */
  198. FS.File.prototype.isMounted = FS.File.prototype.getCollection;
  199. /**
  200. * @method FS.File.prototype.getFileRecord Returns the fileRecord
  201. * @public
  202. * @returns {object} The filerecord
  203. */
  204. FS.File.prototype.getFileRecord = function() {
  205. var self = this;
  206. // Check if this file object fileRecord is kept updated by Meteor, if so
  207. // return self
  208. if (self.controlledByDeps()) {
  209. return self;
  210. }
  211. // Go for manually updating the file record
  212. if (self.isMounted()) {
  213. FS.debug && console.log('GET FILERECORD: ' + self._id);
  214. // Return the fileRecord or an empty object
  215. var fileRecord = self.collection.files.findOne({_id: self._id}) || {};
  216. FS.Utility.extend(self, fileRecord);
  217. return fileRecord;
  218. } else {
  219. // We return an empty object, this way users can still do `getRecord().size`
  220. // Without getting an error
  221. return {};
  222. }
  223. };
  224. /**
  225. * @method FS.File.prototype.update
  226. * @public
  227. * @param {modifier} modifier
  228. * @param {object} [options]
  229. * @param {function} [callback]
  230. *
  231. * Updates the fileRecord.
  232. */
  233. FS.File.prototype.update = function(modifier, options, callback) {
  234. var self = this;
  235. FS.debug && console.log('UPDATE: ' + JSON.stringify(modifier));
  236. // Make sure we have options and callback
  237. if (!callback && typeof options === 'function') {
  238. callback = options;
  239. options = {};
  240. }
  241. callback = callback || FS.Utility.defaultCallback;
  242. if (!self.isMounted()) {
  243. callback(new Error("Cannot update a file that is not associated with a collection"));
  244. return;
  245. }
  246. // Call collection update - File record
  247. return self.collection.files.update({_id: self._id}, modifier, options, function(err, count) {
  248. // Update the fileRecord if it was changed and on the client
  249. // The server-side methods will pull the fileRecord if needed
  250. if (count > 0 && Meteor.isClient)
  251. self.getFileRecord();
  252. // Call callback
  253. callback(err, count);
  254. });
  255. };
  256. /**
  257. * @method FS.File.prototype._saveChanges
  258. * @private
  259. * @param {String} [what] "_original" to save original info, or a store name to save info for that store, or saves everything
  260. *
  261. * Updates the fileRecord from values currently set on the FS.File instance.
  262. */
  263. FS.File.prototype._saveChanges = function(what) {
  264. var self = this;
  265. if (!self.isMounted()) {
  266. return;
  267. }
  268. FS.debug && console.log("FS.File._saveChanges:", what || "all");
  269. var mod = {$set: {}};
  270. if (what === "_original") {
  271. mod.$set.original = self.original;
  272. } else if (typeof what === "string") {
  273. var info = self.copies[what];
  274. if (info) {
  275. mod.$set["copies." + what] = info;
  276. }
  277. } else {
  278. mod.$set.original = self.original;
  279. mod.$set.copies = self.copies;
  280. }
  281. self.update(mod);
  282. };
  283. /**
  284. * @method FS.File.prototype.remove
  285. * @public
  286. * @param {Function} [callback]
  287. * @returns {number} Count
  288. *
  289. * Remove the current file from its FS.Collection
  290. */
  291. FS.File.prototype.remove = function(callback) {
  292. var self = this;
  293. FS.debug && console.log('REMOVE: ' + self._id);
  294. callback = callback || FS.Utility.defaultCallback;
  295. if (!self.isMounted()) {
  296. callback(new Error("Cannot remove a file that is not associated with a collection"));
  297. return;
  298. }
  299. return self.collection.files.remove({_id: self._id}, function(err, res) {
  300. if (!err) {
  301. delete self._id;
  302. delete self.collection;
  303. delete self.collectionName;
  304. }
  305. callback(err, res);
  306. });
  307. };
  308. /**
  309. * @method FS.File.prototype.moveTo
  310. * @param {FS.Collection} targetCollection
  311. * @private // Marked private until implemented
  312. * @todo Needs to be implemented
  313. *
  314. * Move the file from current collection to another collection
  315. *
  316. * > Note: Not yet implemented
  317. */
  318. /**
  319. * @method FS.File.prototype.getExtension Returns the lowercase file extension
  320. * @public
  321. * @deprecated Use the `extension` getter/setter method instead.
  322. * @param {Object} [options]
  323. * @param {String} [options.store] - Store name. Default is the original extension.
  324. * @returns {string} The extension eg.: `jpg` or if not found then an empty string ''
  325. */
  326. FS.File.prototype.getExtension = function(options) {
  327. var self = this;
  328. return self.extension(options);
  329. };
  330. function checkContentType(fsFile, storeName, startOfType) {
  331. var type;
  332. if (storeName && fsFile.hasStored(storeName)) {
  333. type = fsFile.type({store: storeName});
  334. } else {
  335. type = fsFile.type();
  336. }
  337. if (typeof type === "string") {
  338. return type.indexOf(startOfType) === 0;
  339. }
  340. return false;
  341. }
  342. /**
  343. * @method FS.File.prototype.isImage Is it an image file?
  344. * @public
  345. * @param {object} [options]
  346. * @param {string} [options.store] The store we're interested in
  347. *
  348. * Returns true if the copy of this file in the specified store has an image
  349. * content type. If the file object is unmounted or doesn't have a copy for
  350. * the specified store, or if you don't specify a store, this method checks
  351. * the content type of the original file.
  352. */
  353. FS.File.prototype.isImage = function(options) {
  354. return checkContentType(this, (options || {}).store, 'image/');
  355. };
  356. /**
  357. * @method FS.File.prototype.isVideo Is it a video file?
  358. * @public
  359. * @param {object} [options]
  360. * @param {string} [options.store] The store we're interested in
  361. *
  362. * Returns true if the copy of this file in the specified store has a video
  363. * content type. If the file object is unmounted or doesn't have a copy for
  364. * the specified store, or if you don't specify a store, this method checks
  365. * the content type of the original file.
  366. */
  367. FS.File.prototype.isVideo = function(options) {
  368. return checkContentType(this, (options || {}).store, 'video/');
  369. };
  370. /**
  371. * @method FS.File.prototype.isAudio Is it an audio file?
  372. * @public
  373. * @param {object} [options]
  374. * @param {string} [options.store] The store we're interested in
  375. *
  376. * Returns true if the copy of this file in the specified store has an audio
  377. * content type. If the file object is unmounted or doesn't have a copy for
  378. * the specified store, or if you don't specify a store, this method checks
  379. * the content type of the original file.
  380. */
  381. FS.File.prototype.isAudio = function(options) {
  382. return checkContentType(this, (options || {}).store, 'audio/');
  383. };
  384. /**
  385. * @method FS.File.prototype.formattedSize
  386. * @public
  387. * @param {Object} options
  388. * @param {String} [options.store=none,display original file size] Which file do you want to get the size of?
  389. * @param {String} [options.formatString='0.00 b'] The `numeral` format string to use.
  390. * @return {String} The file size formatted as a human readable string and reactively updated.
  391. *
  392. * * You must add the `numeral` package to your app before you can use this method.
  393. * * If info is not found or a size can't be determined, it will show 0.
  394. */
  395. FS.File.prototype.formattedSize = function fsFileFormattedSize(options) {
  396. var self = this;
  397. if (typeof numeral !== "function")
  398. throw new Error("You must add the numeral package if you call FS.File.formattedSize");
  399. options = options || {};
  400. options = options.hash || options;
  401. var size = self.size(options) || 0;
  402. return numeral(size).format(options.formatString || '0.00 b');
  403. };
  404. /**
  405. * @method FS.File.prototype.isUploaded Is this file completely uploaded?
  406. * @public
  407. * @returns {boolean} True if the number of uploaded bytes is equal to the file size.
  408. */
  409. FS.File.prototype.isUploaded = function() {
  410. var self = this;
  411. // Make sure we use the updated file record
  412. self.getFileRecord();
  413. return !!self.uploadedAt;
  414. };
  415. /**
  416. * @method FS.File.prototype.hasStored
  417. * @public
  418. * @param {string} storeName Name of the store
  419. * @param {boolean} [optimistic=false] In case that the file record is not found, read below
  420. * @returns {boolean} Is a version of this file stored in the given store?
  421. *
  422. * > Note: If the file is not published to the client or simply not found:
  423. * this method cannot know for sure if it exists or not. The `optimistic`
  424. * param is the boolean value to return. Are we `optimistic` that the copy
  425. * could exist. This is the case in `FS.File.url` we are optimistic that the
  426. * copy supplied by the user exists.
  427. */
  428. FS.File.prototype.hasStored = function(storeName, optimistic) {
  429. var self = this;
  430. // Make sure we use the updated file record
  431. self.getFileRecord();
  432. // If we havent the published data then
  433. if (FS.Utility.isEmpty(self.copies)) {
  434. return !!optimistic;
  435. }
  436. if (typeof storeName === "string") {
  437. // Return true only if the `key` property is present, which is not set until
  438. // storage is complete.
  439. return !!(self.copies && self.copies[storeName] && self.copies[storeName].key);
  440. }
  441. return false;
  442. };
  443. // Backwards compatibility
  444. FS.File.prototype.hasCopy = FS.File.prototype.hasStored;
  445. /**
  446. * @method FS.File.prototype.getCopyInfo
  447. * @public
  448. * @deprecated Use individual methods with `store` option instead.
  449. * @param {string} storeName Name of the store for which to get copy info.
  450. * @returns {Object} The file details, e.g., name, size, key, etc., specific to the copy saved in this store.
  451. */
  452. FS.File.prototype.getCopyInfo = function(storeName) {
  453. var self = this;
  454. // Make sure we use the updated file record
  455. self.getFileRecord();
  456. return (self.copies && self.copies[storeName]) || null;
  457. };
  458. /**
  459. * @method FS.File.prototype._getInfo
  460. * @private
  461. * @param {String} [storeName] Name of the store for which to get file info. Omit for original file details.
  462. * @param {Object} [options]
  463. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first?
  464. * @returns {Object} The file details, e.g., name, size, key, etc. If not found, returns an empty object.
  465. */
  466. FS.File.prototype._getInfo = function(storeName, options) {
  467. var self = this;
  468. options = options || {};
  469. if (options.updateFileRecordFirst) {
  470. // Make sure we use the updated file record
  471. self.getFileRecord();
  472. }
  473. if (storeName) {
  474. return (self.copies && self.copies[storeName]) || {};
  475. } else {
  476. return self.original || {};
  477. }
  478. };
  479. /**
  480. * @method FS.File.prototype._setInfo
  481. * @private
  482. * @param {String} storeName - Name of the store for which to set file info. Non-string will set original file details.
  483. * @param {String} property - Property to set
  484. * @param {String} value - New value for property
  485. * @param {Boolean} save - Should the new value be saved to the DB, too, or just set in the FS.File properties?
  486. * @returns {undefined}
  487. */
  488. FS.File.prototype._setInfo = function(storeName, property, value, save) {
  489. var self = this;
  490. if (typeof storeName === "string") {
  491. self.copies = self.copies || {};
  492. self.copies[storeName] = self.copies[storeName] || {};
  493. self.copies[storeName][property] = value;
  494. save && self._saveChanges(storeName);
  495. } else {
  496. self.original = self.original || {};
  497. self.original[property] = value;
  498. save && self._saveChanges("_original");
  499. }
  500. };
  501. /**
  502. * @method FS.File.prototype.name
  503. * @public
  504. * @param {String|null} [value] - If setting the name, specify the new name as the first argument. Otherwise the options argument should be first.
  505. * @param {Object} [options]
  506. * @param {Object} [options.store=none,original] - Get or set the name of the version of the file that was saved in this store. Default is the original file name.
  507. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
  508. * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
  509. * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file name.
  510. */
  511. FS.File.prototype.name = function(value, options) {
  512. var self = this;
  513. if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
  514. // GET
  515. options = value || {};
  516. options = options.hash || options; // allow use as UI helper
  517. return self._getInfo(options.store, options).name;
  518. } else {
  519. // SET
  520. options = options || {};
  521. return self._setInfo(options.store, 'name', value, typeof options.save === "boolean" ? options.save : true);
  522. }
  523. };
  524. /**
  525. * @method FS.File.prototype.extension
  526. * @public
  527. * @param {String|null} [value] - If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first.
  528. * @param {Object} [options]
  529. * @param {Object} [options.store=none,original] - Get or set the extension of the version of the file that was saved in this store. Default is the original file extension.
  530. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
  531. * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
  532. * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one.
  533. */
  534. FS.File.prototype.extension = function(value, options) {
  535. var self = this;
  536. if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
  537. // GET
  538. options = value || {};
  539. return FS.Utility.getFileExtension(self.name(options) || '');
  540. } else {
  541. // SET
  542. options = options || {};
  543. var newName = FS.Utility.setFileExtension(self.name(options) || '', value);
  544. return self._setInfo(options.store, 'name', newName, typeof options.save === "boolean" ? options.save : true);
  545. }
  546. };
  547. /**
  548. * @method FS.File.prototype.size
  549. * @public
  550. * @param {Number} [value] - If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first.
  551. * @param {Object} [options]
  552. * @param {Object} [options.store=none,original] - Get or set the size of the version of the file that was saved in this store. Default is the original file size.
  553. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
  554. * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
  555. * @returns {Number|undefined} If setting, returns `undefined`. If getting, returns the file size.
  556. */
  557. FS.File.prototype.size = function(value, options) {
  558. var self = this;
  559. if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
  560. // GET
  561. options = value || {};
  562. options = options.hash || options; // allow use as UI helper
  563. return self._getInfo(options.store, options).size;
  564. } else {
  565. // SET
  566. options = options || {};
  567. return self._setInfo(options.store, 'size', value, typeof options.save === "boolean" ? options.save : true);
  568. }
  569. };
  570. /**
  571. * @method FS.File.prototype.type
  572. * @public
  573. * @param {String} [value] - If setting the type, specify the new type as the first argument. Otherwise the options argument should be first.
  574. * @param {Object} [options]
  575. * @param {Object} [options.store=none,original] - Get or set the type of the version of the file that was saved in this store. Default is the original file type.
  576. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
  577. * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
  578. * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file type.
  579. */
  580. FS.File.prototype.type = function(value, options) {
  581. var self = this;
  582. if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) {
  583. // GET
  584. options = value || {};
  585. options = options.hash || options; // allow use as UI helper
  586. return self._getInfo(options.store, options).type;
  587. } else {
  588. // SET
  589. options = options || {};
  590. return self._setInfo(options.store, 'type', value, typeof options.save === "boolean" ? options.save : true);
  591. }
  592. };
  593. /**
  594. * @method FS.File.prototype.updatedAt
  595. * @public
  596. * @param {String} [value] - If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first.
  597. * @param {Object} [options]
  598. * @param {Object} [options.store=none,original] - Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date.
  599. * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only.
  600. * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only.
  601. * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file's last updated date.
  602. */
  603. FS.File.prototype.updatedAt = function(value, options) {
  604. var self = this;
  605. if (!options && ((typeof value === "object" && value !== null && !(value instanceof Date)) || typeof value === "undefined")) {
  606. // GET
  607. options = value || {};
  608. options = options.hash || options; // allow use as UI helper
  609. return self._getInfo(options.store, options).updatedAt;
  610. } else {
  611. // SET
  612. options = options || {};
  613. return self._setInfo(options.store, 'updatedAt', value, typeof options.save === "boolean" ? options.save : true);
  614. }
  615. };
  616. /**
  617. * @method FS.File.onStoredCallback
  618. * @summary Calls callback when the file is fully stored to the specify storeName
  619. * @public
  620. * @param {String} [storeName] - The name of the file store we want to get called when stored.
  621. * @param {function} [callback]
  622. */
  623. FS.File.prototype.onStoredCallback = function (storeName, callback) {
  624. // Check file is not already stored
  625. if (this.hasStored(storeName)) {
  626. callback();
  627. return;
  628. }
  629. if (Meteor.isServer) {
  630. // Listen to file stored events
  631. // TODO Require thinking whether it is better to use observer for case of using multiple application instances, Ask for same image url while upload is being done.
  632. this.on('stored', function (newStoreName) {
  633. // If stored is completed to the specified store call callback
  634. if (storeName === newStoreName) {
  635. // Remove the specified file stored listener
  636. this.removeListener('stored', arguments.callee);
  637. callback();
  638. }
  639. }.bind(this)
  640. );
  641. } else {
  642. var fileId = this._id,
  643. collectionName = this.collectionName;
  644. // Wait for file to be fully uploaded
  645. Tracker.autorun(function (c) {
  646. Meteor.call('_cfs_returnWhenStored', collectionName, fileId, storeName, function (error, result) {
  647. if (result && result === true) {
  648. c.stop();
  649. callback();
  650. } else {
  651. Meteor.setTimeout(function () {
  652. c.invalidate();
  653. }, 100);
  654. }
  655. });
  656. });
  657. }
  658. };
  659. /**
  660. * @method FS.File.onStored
  661. * @summary Function that returns when the file is fully stored to the specify storeName
  662. * @public
  663. * @param {String} storeName - The name of the file store we want to get called when stored.
  664. *
  665. * Function that returns when the file is fully stored to the specify storeName.
  666. *
  667. * For example needed if wanted to save the direct link to a file on s3 when fully uploaded.
  668. */
  669. FS.File.prototype.onStored = function (arguments) {
  670. var onStoredSync = Meteor.wrapAsync(this.onStoredCallback);
  671. return onStoredSync.call(this, arguments);
  672. };
  673. function isBasicObject(obj) {
  674. return (obj === Object(obj) && Object.getPrototypeOf(obj) === Object.prototype);
  675. }
  676. // getPrototypeOf polyfill
  677. if (typeof Object.getPrototypeOf !== "function") {
  678. if (typeof "".__proto__ === "object") {
  679. Object.getPrototypeOf = function(object) {
  680. return object.__proto__;
  681. };
  682. } else {
  683. Object.getPrototypeOf = function(object) {
  684. // May break if the constructor has been tampered with
  685. return object.constructor.prototype;
  686. };
  687. }
  688. }