From 487a9770476b1a7896de667c4d3b28fab439f966 Mon Sep 17 00:00:00 2001 From: Qvazar Date: Mon, 14 Sep 2015 18:34:34 +0200 Subject: [PATCH] Added buffer to file. Added installation and better usage instructions. --- .gitignore | 1 - README.md | 53 ++++----- build/dev/ProgressivePromise.js | 65 +++++++++++ build/dev/untar-worker.js | 189 ++++++++++++++++++++++++++++++++ build/dev/untar.js | 91 +++++++++++++++ build/dist/untar.js | 1 + package.json | 3 - src/untar.js | 23 ++-- 8 files changed, 387 insertions(+), 39 deletions(-) create mode 100644 build/dev/ProgressivePromise.js create mode 100644 build/dev/untar-worker.js create mode 100644 build/dev/untar.js create mode 100644 build/dist/untar.js diff --git a/.gitignore b/.gitignore index e3fbd98..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -build node_modules diff --git a/README.md b/README.md index 985e2ff..d532b18 100644 --- a/README.md +++ b/README.md @@ -11,38 +11,40 @@ Useful when packing all your application images/sound/json/etc. data in a standa As of September 2015 this includes Chrome>=20, Firefox>=13, IE>=10, Opera>=12.10 and Safari>=8. [Web Worker transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) are used when available, increasing speed greatly. This is supported in Chrome>=21, Firefox>=18, Opera>=15 and Safari. +## Installation + bower install --save js-untar + ## Documentation -Load the module with RequireJS or similar. The module is a function that returns a modified Promise with a progress callback. +Supports AMD, CommonJS or simply load with a script tag, which will provide a global untar function. +The module is a function that returns a modified Promise with a progress callback. This callback is executed every time a file is extracted. The standard Promise.then method is also called when extraction is done, with all extracted files as argument. The extraction is done in a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) to allow the main UI thread to continue. ### Example: - define(["untar"], function(untar) { - // Load the source ArrayBuffer from a XMLHttpRequest (or any other way you may need). - var sourceBuffer = [...]; - - untar(sourceBuffer) - .progress(function(extractedFile) { - ... - }) - .then(function(extractedFiles) { - ... - }); - // or - untar(sourceBuffer).then( - function(extractedFiles) { // onSuccess - ... - }, - function(err) { // onError - ... - }, - function(extractedFile) { // onProgress - ... - } - ); + // Load the source ArrayBuffer from a XMLHttpRequest (or any other way you may need). + var sourceBuffer = [...]; + + untar(sourceBuffer) + .progress(function(extractedFile) { + ... // Do something with a single extracted file. + }) + .then(function(extractedFiles) { + ... // Do something with all extracted files. }); + // or + untar(sourceBuffer).then( + function(extractedFiles) { // onSuccess + ... // Do something with all extracted files. + }, + function(err) { // onError + ... // Handle the error. + }, + function(extractedFile) { // onProgress + ... // Do something with a single extracted file. + } + ); ### File object The returned file object(s) has the following properties. Most of these are explained in the [Tar wikipedia entry](https://en.wikipedia.org/wiki/Tar_(computing)#File_format). @@ -57,7 +59,8 @@ The returned file object(s) has the following properties. Most of these are expl * type * linkname * ustarFormat -* blob A [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object with the contens of the file. +* buffer An ArrayBuffer with the contents of the file. +* blob A [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object with the contents of the file. * getObjectUrl() A unique [ObjectUrl](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) to the data can be retrieved with this method for easy usage of extracted data in <img> tags etc. document.getElementById("targetImageElement").src = file.getObjectUrl(); diff --git a/build/dev/ProgressivePromise.js b/build/dev/ProgressivePromise.js new file mode 100644 index 0000000..fd81047 --- /dev/null +++ b/build/dev/ProgressivePromise.js @@ -0,0 +1,65 @@ +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.ProgressivePromise = factory(); + } +}(this, function() { +"use strict"; +/* globals window: false, Promise: false */ + +/** +Returns a Promise decorated with a progress() event. +*/ +function ProgressivePromise(fn) { + if (typeof Promise !== "function") { + throw new Error("Promise implementation not available in this environment."); + } + + var progressCallbacks = []; + var progressHistory = []; + + function doProgress(value) { + for (var i = 0, l = progressCallbacks.length; i < l; ++i) { + progressCallbacks[i](value); + } + + progressHistory.push(value); + } + + var promise = new Promise(function(resolve, reject) { + fn(resolve, reject, doProgress); + }); + + promise.progress = function(cb) { + if (typeof cb !== "function") { + throw new Error("cb is not a function."); + } + + // Report the previous progress history + for (var i = 0, l = progressHistory.length; i < l; ++i) { + cb(progressHistory[i]); + } + + progressCallbacks.push(cb); + return promise; + }; + + var origThen = promise.then; + + promise.then = function(onSuccess, onFail, onProgress) { + origThen.call(promise, onSuccess, onFail); + + if (onProgress !== undefined) { + promise.progress(onProgress); + } + + return promise; + }; + + return promise; +} +return ProgressivePromise; +})); diff --git a/build/dev/untar-worker.js b/build/dev/untar-worker.js new file mode 100644 index 0000000..4d08511 --- /dev/null +++ b/build/dev/untar-worker.js @@ -0,0 +1,189 @@ +"use strict"; +/* globals postMessage: false, DataView: false, self: false, window: false, ArrayBuffer: false, Uint8Array: false */ + +function UntarWorker() { + +} + +UntarWorker.prototype = { + onmessage: function(msg) { + try { + if (msg.data.type === "extract") { + this.untarBuffer(msg.data.buffer); + } else { + throw new Error("Unknown message type: " + msg.data.type); + } + } catch (err) { + this.postError(err); + } + }, + + postError: function(err) { + //console.info("postError(" + err.message + ")" + " " + JSON.stringify(err)); + this.postMessage({ type: "error", data: { message: err.message } }); + }, + + postLog: function(level, msg) { + console.info("postLog"); + this.postMessage({ type: "log", data: { level: level, msg: msg }}); + }, + + untarBuffer: function(arrayBuffer) { + try { + var tarFileStream = new UntarFileStream(arrayBuffer); + while (tarFileStream.hasNext()) { + var file = tarFileStream.next(); + + this.postMessage({ type: "extract", data: file }, [file.buffer]); + } + + this.postMessage({ type: "complete" }); + } catch (err) { + this.postError(err); + } + }, + + postMessage: function(msg, transfers) { + console.info("postMessage(" + msg + ", " + JSON.stringify(transfers) + ")"); + self.postMessage(msg, transfers); + } +}; + +if (typeof self !== "undefined") { + // We're running in a worker thread + var worker = new UntarWorker(); + self.onmessage = function(msg) { worker.onmessage(msg); }; +} + +function TarFile() { + +} + +function UntarStream(arrayBuffer) { + this._bufferView = new DataView(arrayBuffer); + this._position = 0; +} + +UntarStream.prototype = { + readString: function(charCount) { + //console.log("readString: position " + this.position() + ", " + charCount + " chars"); + var charSize = 1; + var byteCount = charCount * charSize; + + var charCodes = []; + + for (var i = 0; i < charCount; ++i) { + var charCode = this._bufferView.getUint8(this.position() + (i * charSize), true); + if (charCode !== 0) { + charCodes.push(charCode); + } else { + break; + } + } + + this.seek(byteCount); + + return String.fromCharCode.apply(null, charCodes); + }, + + readBuffer: function(byteCount) { + var buf; + + if (typeof ArrayBuffer.prototype.slice === "function") { + buf = this._bufferView.buffer.slice(this.position(), this.position() + byteCount); + } else { + buf = new ArrayBuffer(byteCount); + var target = new Uint8Array(buf); + var src = new Uint8Array(this._bufferView.buffer, this.position(), byteCount); + target.set(src); + } + + this.seek(byteCount); + return buf; + }, + + seek: function(byteCount) { + this._position += byteCount; + }, + + peekUint32: function() { + return this._bufferView.getUint32(this.position(), true); + }, + + position: function(newpos) { + if (newpos === undefined) { + return this._position; + } else { + this._position = newpos; + } + }, + + size: function() { + return this._bufferView.byteLength; + } +}; + +function UntarFileStream(arrayBuffer) { + this._stream = new UntarStream(arrayBuffer); +} + +UntarFileStream.prototype = { + hasNext: function() { + // A tar file ends with 4 zero bytes + return this._stream.position() + 4 < this._stream.size() && this._stream.peekUint32() !== 0; + }, + + next: function() { + var stream = this._stream; + var file = new TarFile(); + + var headerBeginPos = stream.position(); + var dataBeginPos = headerBeginPos + 512; + + // Read header + file.name = stream.readString(100); + file.mode = stream.readString(8); + file.uid = stream.readString(8); + file.gid = stream.readString(8); + file.size = parseInt(stream.readString(12), 8); + file.modificationTime = parseInt(stream.readString(12), 8); + file.checksum = stream.readString(8); + file.type = stream.readString(1); + file.linkname = stream.readString(1); + file.ustarFormat = stream.readString(6); + + if (file.ustarFormat === "ustar") { + file.version = stream.readString(2); + file.uname = stream.readString(32); + file.gname = stream.readString(32); + file.devmajor = stream.readString(8); + file.devminor = stream.readString(8); + file.namePrefix = stream.readString(155); + + if (file.namePrefix.length > 0) { + file.name = file.namePrefix + file.name; + } + } + + stream.position(dataBeginPos); + + // Normal file is either "\0" or 0. + if (file.type === "0" || file.type === "\0") { + file.buffer = stream.readBuffer(file.size); + } else if (file.type == 5) { + // Directory - should we do anything with this? Nope! + } else { + // We only care about real files, not symlinks. + } + + if (file.buffer === undefined) { + file.buffer = new ArrayBuffer(0); + } + + // File data is padded to reach a 512 byte boundary; skip the padded bytes. + var dataEndPos = dataBeginPos + (file.size > 0 ? file.size + (512 - file.size % 512) : 0); + stream.position(dataEndPos); + + return file; + } +}; \ No newline at end of file diff --git a/build/dev/untar.js b/build/dev/untar.js new file mode 100644 index 0000000..602fe1a --- /dev/null +++ b/build/dev/untar.js @@ -0,0 +1,91 @@ +;(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['ProgressivePromise'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('ProgressivePromise')); + } else { + root.untar = factory(root.ProgressivePromise); + } +}(this, function(ProgressivePromise) { +"use strict"; +/* globals window: false, Blob: false, Promise: false, console: false, Worker: false, ProgressivePromise: false */ + +var workerScriptUri; // Included at compile time + +var URL = window.URL || window.webkitURL; + +/** +Returns a ProgressivePromise. +*/ +function untar(arrayBuffer) { + if (!(arrayBuffer instanceof ArrayBuffer)) { + throw new TypeError("arrayBuffer is not an instance of ArrayBuffer."); + } + + if (!window.Worker) { + throw new Error("Worker implementation not available in this environment."); + } + + return new ProgressivePromise(function(resolve, reject, progress) { + var worker = new Worker(workerScriptUri); + + var files = []; + + worker.onerror = function(err) { + reject(err); + }; + + worker.onmessage = function(message) { + message = message.data; + + switch (message.type) { + case "log": + console[message.data.level]("Worker: " + message.data.msg); + break; + case "extract": + var file = decorateExtractedFile(message.data); + files.push(file); + progress(file); + break; + case "complete": + resolve(files); + break; + case "error": + //console.log("error message"); + reject(new Error(message.data.message)); + break; + default: + reject(new Error("Unknown message from worker: " + message.type)); + break; + } + }; + + //console.info("Sending arraybuffer to worker for extraction."); + worker.postMessage({ type: "extract", buffer: arrayBuffer }, [arrayBuffer]); + }); +} + +function decorateExtractedFile(file) { + var blob; + var blobUrl; + Object.defineProperties(file, { + blob: { + get: function() { + return blob || (blob = new Blob([this.buffer])); + } + }, + getObjectUrl: { + value: function() { + return blobUrl || (blubUrl = URL.createObjectURL(blob)); + } + } + }); + + return file; +} + +workerScriptUri = '/base/build/dev/untar-worker.js'; +return untar; +})); + +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlcyI6WyJ1bnRhci5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKiBnbG9iYWxzIHdpbmRvdzogZmFsc2UsIEJsb2I6IGZhbHNlLCBQcm9taXNlOiBmYWxzZSwgY29uc29sZTogZmFsc2UsIFdvcmtlcjogZmFsc2UsIFByb2dyZXNzaXZlUHJvbWlzZTogZmFsc2UgKi9cblxudmFyIHdvcmtlclNjcmlwdFVyaTsgLy8gSW5jbHVkZWQgYXQgY29tcGlsZSB0aW1lXG5cbnZhciBVUkwgPSB3aW5kb3cuVVJMIHx8IHdpbmRvdy53ZWJraXRVUkw7XG5cbi8qKlxuUmV0dXJucyBhIFByb2dyZXNzaXZlUHJvbWlzZS5cbiovXG5mdW5jdGlvbiB1bnRhcihhcnJheUJ1ZmZlcikge1xuXHRpZiAoIShhcnJheUJ1ZmZlciBpbnN0YW5jZW9mIEFycmF5QnVmZmVyKSkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoXCJhcnJheUJ1ZmZlciBpcyBub3QgYW4gaW5zdGFuY2Ugb2YgQXJyYXlCdWZmZXIuXCIpO1xuXHR9XG5cblx0aWYgKCF3aW5kb3cuV29ya2VyKSB7XG5cdFx0dGhyb3cgbmV3IEVycm9yKFwiV29ya2VyIGltcGxlbWVudGF0aW9uIG5vdCBhdmFpbGFibGUgaW4gdGhpcyBlbnZpcm9ubWVudC5cIik7XG5cdH1cblxuXHRyZXR1cm4gbmV3IFByb2dyZXNzaXZlUHJvbWlzZShmdW5jdGlvbihyZXNvbHZlLCByZWplY3QsIHByb2dyZXNzKSB7XG5cdFx0dmFyIHdvcmtlciA9IG5ldyBXb3JrZXIod29ya2VyU2NyaXB0VXJpKTtcblxuXHRcdHZhciBmaWxlcyA9IFtdO1xuXG5cdFx0d29ya2VyLm9uZXJyb3IgPSBmdW5jdGlvbihlcnIpIHtcblx0XHRcdHJlamVjdChlcnIpO1xuXHRcdH07XG5cblx0XHR3b3JrZXIub25tZXNzYWdlID0gZnVuY3Rpb24obWVzc2FnZSkge1xuXHRcdFx0bWVzc2FnZSA9IG1lc3NhZ2UuZGF0YTtcblxuXHRcdFx0c3dpdGNoIChtZXNzYWdlLnR5cGUpIHtcblx0XHRcdFx0Y2FzZSBcImxvZ1wiOlxuXHRcdFx0XHRcdGNvbnNvbGVbbWVzc2FnZS5kYXRhLmxldmVsXShcIldvcmtlcjogXCIgKyBtZXNzYWdlLmRhdGEubXNnKTtcblx0XHRcdFx0XHRicmVhaztcblx0XHRcdFx0Y2FzZSBcImV4dHJhY3RcIjpcblx0XHRcdFx0XHR2YXIgZmlsZSA9IGRlY29yYXRlRXh0cmFjdGVkRmlsZShtZXNzYWdlLmRhdGEpO1xuXHRcdFx0XHRcdGZpbGVzLnB1c2goZmlsZSk7XG5cdFx0XHRcdFx0cHJvZ3Jlc3MoZmlsZSk7XG5cdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdGNhc2UgXCJjb21wbGV0ZVwiOlxuXHRcdFx0XHRcdHJlc29sdmUoZmlsZXMpO1xuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHRjYXNlIFwiZXJyb3JcIjpcblx0XHRcdFx0XHQvL2NvbnNvbGUubG9nKFwiZXJyb3IgbWVzc2FnZVwiKTtcblx0XHRcdFx0XHRyZWplY3QobmV3IEVycm9yKG1lc3NhZ2UuZGF0YS5tZXNzYWdlKSk7XG5cdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdGRlZmF1bHQ6XG5cdFx0XHRcdFx0cmVqZWN0KG5ldyBFcnJvcihcIlVua25vd24gbWVzc2FnZSBmcm9tIHdvcmtlcjogXCIgKyBtZXNzYWdlLnR5cGUpKTtcblx0XHRcdFx0XHRicmVhaztcblx0XHRcdH1cblx0XHR9O1xuXG5cdFx0Ly9jb25zb2xlLmluZm8oXCJTZW5kaW5nIGFycmF5YnVmZmVyIHRvIHdvcmtlciBmb3IgZXh0cmFjdGlvbi5cIik7XG5cdFx0d29ya2VyLnBvc3RNZXNzYWdlKHsgdHlwZTogXCJleHRyYWN0XCIsIGJ1ZmZlcjogYXJyYXlCdWZmZXIgfSwgW2FycmF5QnVmZmVyXSk7XG5cdH0pO1xufVxuXG5mdW5jdGlvbiBkZWNvcmF0ZUV4dHJhY3RlZEZpbGUoZmlsZSkge1xuXHR2YXIgYmxvYjtcblx0dmFyIGJsb2JVcmw7XG5cdE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKGZpbGUsIHtcblx0XHRibG9iOiB7XG5cdFx0XHRnZXQ6IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRyZXR1cm4gYmxvYiB8fCAoYmxvYiA9IG5ldyBCbG9iKFt0aGlzLmJ1ZmZlcl0pKTtcblx0XHRcdH1cblx0XHR9LFxuXHRcdGdldE9iamVjdFVybDoge1xuXHRcdFx0dmFsdWU6IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRyZXR1cm4gYmxvYlVybCB8fCAoYmx1YlVybCA9IFVSTC5jcmVhdGVPYmplY3RVUkwoYmxvYikpO1xuXHRcdFx0fVxuXHRcdH1cblx0fSk7XG5cblx0cmV0dXJuIGZpbGU7XG59XG4iXSwiZmlsZSI6InVudGFyLmpzIiwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= \ No newline at end of file diff --git a/build/dist/untar.js b/build/dist/untar.js new file mode 100644 index 0000000..66e1bc3 --- /dev/null +++ b/build/dist/untar.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.untar=t()}(this,function(){"use strict";function e(e){function t(e){for(var t=0,i=r.length;i>t;++t)r[t](e);n.push(e)}if("function"!=typeof Promise)throw new Error("Promise implementation not available in this environment.");var r=[],n=[],i=new Promise(function(r,n){e(r,n,t)});i.progress=function(e){if("function"!=typeof e)throw new Error("cb is not a function.");for(var t=0,o=n.length;o>t;++t)e(n[t]);return r.push(e),i};var o=i.then;return i.then=function(e,t,r){return o.call(i,e,t),void 0!==r&&i.progress(r),i},i}function t(t){if(!(t instanceof ArrayBuffer))throw new TypeError("arrayBuffer is not an instance of ArrayBuffer.");if(!window.Worker)throw new Error("Worker implementation not available in this environment.");return new e(function(e,i,o){var a=new Worker(n),s=[];a.onerror=function(e){i(e)},a.onmessage=function(t){switch(t=t.data,t.type){case"log":console[t.data.level]("Worker: "+t.data.msg);break;case"extract":var n=r(t.data);s.push(n),o(n);break;case"complete":e(s);break;case"error":i(new Error(t.data.message));break;default:i(new Error("Unknown message from worker: "+t.type))}},a.postMessage({type:"extract",buffer:t},[t])})}function r(e){var t,r;return Object.defineProperties(e,{blob:{get:function(){return t||(t=new Blob([this.buffer]))}},getObjectUrl:{value:function(){return r||(blubUrl=i.createObjectURL(t))}}}),e}var n,i=window.URL||window.webkitURL;return n=i.createObjectURL(new Blob(['"use strict";function UntarWorker(){}function TarFile(){}function UntarStream(e){this._bufferView=new DataView(e),this._position=0}function UntarFileStream(e){this._stream=new UntarStream(e)}if(UntarWorker.prototype={onmessage:function(e){try{if("extract"!==e.data.type)throw new Error("Unknown message type: "+e.data.type);this.untarBuffer(e.data.buffer)}catch(t){this.postError(t)}},postError:function(e){this.postMessage({type:"error",data:{message:e.message}})},postLog:function(e,t){console.info("postLog"),this.postMessage({type:"log",data:{level:e,msg:t}})},untarBuffer:function(e){try{for(var t=new UntarFileStream(e);t.hasNext();){var r=t.next();this.postMessage({type:"extract",data:r},[r.buffer])}this.postMessage({type:"complete"})}catch(i){this.postError(i)}},postMessage:function(e,t){console.info("postMessage("+e+", "+JSON.stringify(t)+")"),self.postMessage(e,t)}},"undefined"!=typeof self){var worker=new UntarWorker;self.onmessage=function(e){worker.onmessage(e)}}UntarStream.prototype={readString:function(e){for(var t=1,r=e*t,i=[],n=0;e>n;++n){var s=this._bufferView.getUint8(this.position()+n*t,!0);if(0===s)break;i.push(s)}return this.seek(r),String.fromCharCode.apply(null,i)},readBuffer:function(e){var t;if("function"==typeof ArrayBuffer.prototype.slice)t=this._bufferView.buffer.slice(this.position(),this.position()+e);else{t=new ArrayBuffer(e);var r=new Uint8Array(t),i=new Uint8Array(this._bufferView.buffer,this.position(),e);r.set(i)}return this.seek(e),t},seek:function(e){this._position+=e},peekUint32:function(){return this._bufferView.getUint32(this.position(),!0)},position:function(e){return void 0===e?this._position:void(this._position=e)},size:function(){return this._bufferView.byteLength}},UntarFileStream.prototype={hasNext:function(){return this._stream.position()+40&&(t.name=t.namePrefix+t.name)),e.position(i),"0"===t.type||"\x00"===t.type?t.buffer=e.readBuffer(t.size):5==t.type,void 0===t.buffer&&(t.buffer=new ArrayBuffer(0));var n=i+(t.size>0?t.size+(512-t.size%512):0);return e.position(n),t}};'])),t}); \ No newline at end of file diff --git a/package.json b/package.json index 5a31c0d..a72548a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ }, "homepage": "https://github.com/Qvazar/js-untar", "devDependencies": { - "es6-promise": "^3.0.2", "gulp": "^3.9.0", "gulp-add-src": "^0.2.0", "gulp-concat": "^2.6.0", @@ -42,9 +41,7 @@ "karma-firefox-launcher": "^0.1.6", "karma-ie-launcher": "^0.2.0", "karma-jasmine": "^0.3.6", - "karma-phantomjs-launcher": "^0.2.1", "karma-requirejs": "^0.2.2", - "phantomjs": "^1.9.18", "requirejs": "^2.1.20" } } diff --git a/src/untar.js b/src/untar.js index d0d9f97..1647b8a 100644 --- a/src/untar.js +++ b/src/untar.js @@ -56,17 +56,20 @@ function untar(arrayBuffer) { } function decorateExtractedFile(file) { - file.blob = new Blob([file.buffer]); - delete file.buffer; - + var blob; var blobUrl; - file.getObjectUrl = function() { - if (!blobUrl) { - blobUrl = URL.createObjectURL(file.blob); + Object.defineProperties(file, { + blob: { + get: function() { + return blob || (blob = new Blob([this.buffer])); + } + }, + getObjectUrl: { + value: function() { + return blobUrl || (blubUrl = URL.createObjectURL(blob)); + } } - - return blobUrl; - }; + }); return file; -} \ No newline at end of file +}