data-man-api.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /* global DataMan:true, Buffer */
  2. var fs = Npm.require("fs");
  3. var Readable = Npm.require('stream').Readable;
  4. /**
  5. * @method DataMan
  6. * @public
  7. * @constructor
  8. * @param {Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
  9. * @param {String} [type] The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL
  10. * @param {Object} [options] Currently used only to pass options for the GET request when `data` is a URL.
  11. */
  12. DataMan = function DataMan(data, type, options) {
  13. var self = this, buffer;
  14. if (!data) {
  15. throw new Error("DataMan constructor requires a data argument");
  16. }
  17. // The end result of all this is that we will have this.source set to a correct
  18. // data type handler. We are simply detecting what the data arg is.
  19. //
  20. // Unless we already have in-memory data, we don't load anything into memory
  21. // and instead rely on obtaining a read stream when the time comes.
  22. if (typeof Buffer !== "undefined" && data instanceof Buffer) {
  23. if (!type) {
  24. throw new Error("DataMan constructor requires a type argument when passed a Buffer");
  25. }
  26. self.source = new DataMan.Buffer(data, type);
  27. } else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
  28. if (typeof Buffer === "undefined") {
  29. throw new Error("Buffer support required to handle an ArrayBuffer");
  30. }
  31. if (!type) {
  32. throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer");
  33. }
  34. buffer = new Buffer(new Uint8Array(data));
  35. self.source = new DataMan.Buffer(buffer, type);
  36. } else if (EJSON.isBinary(data)) {
  37. if (typeof Buffer === "undefined") {
  38. throw new Error("Buffer support required to handle an ArrayBuffer");
  39. }
  40. if (!type) {
  41. throw new Error("DataMan constructor requires a type argument when passed a Uint8Array");
  42. }
  43. buffer = new Buffer(data);
  44. self.source = new DataMan.Buffer(buffer, type);
  45. } else if (typeof Readable !== "undefined" && data instanceof Readable) {
  46. if (!type) {
  47. throw new Error("DataMan constructor requires a type argument when passed a stream.Readable");
  48. }
  49. self.source = new DataMan.ReadStream(data, type);
  50. } else if (typeof data === "string") {
  51. if (data.slice(0, 5) === "data:") {
  52. self.source = new DataMan.DataURI(data);
  53. } else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
  54. if (!type) {
  55. throw new Error("DataMan constructor requires a type argument when passed a URL");
  56. }
  57. self.source = new DataMan.URL(data, type, options);
  58. } else {
  59. // assume it's a filepath
  60. self.source = new DataMan.FilePath(data, type);
  61. }
  62. } else {
  63. throw new Error("DataMan constructor received data that it doesn't support");
  64. }
  65. };
  66. /**
  67. * @method DataMan.prototype.getBuffer
  68. * @public
  69. * @param {function} [callback] callback(err, buffer)
  70. * @returns {Buffer|undefined}
  71. *
  72. * Returns a Buffer representing this data, or passes the Buffer to a callback.
  73. */
  74. DataMan.prototype.getBuffer = function dataManGetBuffer(callback) {
  75. var self = this;
  76. return callback ? self.source.getBuffer(callback) : Meteor.wrapAsync(bind(self.source.getBuffer, self.source))();
  77. };
  78. function _saveToFile(readStream, filePath, callback) {
  79. var writeStream = fs.createWriteStream(filePath);
  80. writeStream.on('close', Meteor.bindEnvironment(function () {
  81. callback();
  82. }, function (error) { callback(error); }));
  83. writeStream.on('error', Meteor.bindEnvironment(function (error) {
  84. callback(error);
  85. }, function (error) { callback(error); }));
  86. readStream.pipe(writeStream);
  87. }
  88. /**
  89. * @method DataMan.prototype.saveToFile
  90. * @public
  91. * @param {String} filePath
  92. * @param {Function} callback
  93. * @returns {undefined}
  94. *
  95. * Saves this data to a filepath on the local filesystem.
  96. */
  97. DataMan.prototype.saveToFile = function dataManSaveToFile(filePath, callback) {
  98. var readStream = this.createReadStream();
  99. return callback ? _saveToFile(readStream, filePath, callback) : Meteor.wrapAsync(_saveToFile)(readStream, filePath);
  100. };
  101. /**
  102. * @method DataMan.prototype.getDataUri
  103. * @public
  104. * @param {function} [callback] callback(err, dataUri)
  105. *
  106. * If no callback, returns the data URI.
  107. */
  108. DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
  109. var self = this;
  110. return callback ? self.source.getDataUri(callback) : Meteor.wrapAsync(bind(self.source.getDataUri, self.source))();
  111. };
  112. /**
  113. * @method DataMan.prototype.createReadStream
  114. * @public
  115. *
  116. * Returns a read stream for the data.
  117. */
  118. DataMan.prototype.createReadStream = function dataManCreateReadStream() {
  119. return this.source.createReadStream();
  120. };
  121. /**
  122. * @method DataMan.prototype.size
  123. * @public
  124. * @param {function} [callback] callback(err, size)
  125. *
  126. * If no callback, returns the size in bytes of the data.
  127. */
  128. DataMan.prototype.size = function dataManSize(callback) {
  129. var self = this;
  130. return callback ? self.source.size(callback) : Meteor.wrapAsync(bind(self.source.size, self.source))();
  131. };
  132. /**
  133. * @method DataMan.prototype.type
  134. * @public
  135. *
  136. * Returns the type of the data.
  137. */
  138. DataMan.prototype.type = function dataManType() {
  139. return this.source.type();
  140. };
  141. /*
  142. * "bind" shim; from underscorejs, but we avoid a dependency
  143. */
  144. var slice = Array.prototype.slice;
  145. var nativeBind = Function.prototype.bind;
  146. var ctor = function(){};
  147. function isFunction(obj) {
  148. return Object.prototype.toString.call(obj) == '[object Function]';
  149. }
  150. function bind(func, context) {
  151. var args, bound;
  152. if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
  153. if (!isFunction(func)) throw new TypeError;
  154. args = slice.call(arguments, 2);
  155. return bound = function() {
  156. if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
  157. ctor.prototype = func.prototype;
  158. var self = new ctor;
  159. ctor.prototype = null;
  160. var result = func.apply(self, args.concat(slice.call(arguments)));
  161. if (Object(result) === result) return result;
  162. return self;
  163. };
  164. }