// Last time updated: 2017-08-27 5:48:35 AM UTC // ________________ // FileBufferReader // Open-Sourced: https://github.com/muaz-khan/FileBufferReader // -------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- 'use strict'; (function() { function FileBufferReader() { var fbr = this; var fbrHelper = new FileBufferReaderHelper(); fbr.chunks = {}; fbr.users = {}; fbr.readAsArrayBuffer = function(file, callback, extra) { var options = { file: file, earlyCallback: function(chunk) { callback(fbrClone(chunk, { currentPosition: -1 })); }, extra: extra || { userid: 0 } }; if (file.extra && Object.keys(file.extra).length) { Object.keys(file.extra).forEach(function(key) { options.extra[key] = file.extra[key]; }); } fbrHelper.readAsArrayBuffer(fbr, options); }; fbr.getNextChunk = function(fileUUID, callback, userid) { var currentPosition; if (typeof fileUUID.currentPosition !== 'undefined') { currentPosition = fileUUID.currentPosition; fileUUID = fileUUID.uuid; } var allFileChunks = fbr.chunks[fileUUID]; if (!allFileChunks) { return; } if (typeof userid !== 'undefined') { if (!fbr.users[userid + '']) { fbr.users[userid + ''] = { fileUUID: fileUUID, userid: userid, currentPosition: -1 }; } if (typeof currentPosition !== 'undefined') { fbr.users[userid + ''].currentPosition = currentPosition; } fbr.users[userid + ''].currentPosition++; currentPosition = fbr.users[userid + ''].currentPosition; } else { if (typeof currentPosition !== 'undefined') { fbr.chunks[fileUUID].currentPosition = currentPosition; } fbr.chunks[fileUUID].currentPosition++; currentPosition = fbr.chunks[fileUUID].currentPosition; } var nextChunk = allFileChunks[currentPosition]; if (!nextChunk) { delete fbr.chunks[fileUUID]; fbr.convertToArrayBuffer({ chunkMissing: true, currentPosition: currentPosition, uuid: fileUUID }, callback); return; } nextChunk = fbrClone(nextChunk); if (typeof userid !== 'undefined') { nextChunk.remoteUserId = userid + ''; } if (!!nextChunk.start) { fbr.onBegin(nextChunk); } if (!!nextChunk.end) { fbr.onEnd(nextChunk); } fbr.onProgress(nextChunk); fbr.convertToArrayBuffer(nextChunk, function(buffer) { if (nextChunk.currentPosition == nextChunk.maxChunks) { callback(buffer, true); return; } callback(buffer, false); }); }; var fbReceiver = new FileBufferReceiver(fbr); fbr.addChunk = function(chunk, callback) { if (!chunk) { return; } fbReceiver.receive(chunk, function(chunk) { fbr.convertToArrayBuffer({ readyForNextChunk: true, currentPosition: chunk.currentPosition, uuid: chunk.uuid }, callback); }); }; fbr.chunkMissing = function(chunk) { delete fbReceiver.chunks[chunk.uuid]; delete fbReceiver.chunksWaiters[chunk.uuid]; }; fbr.onBegin = function() {}; fbr.onEnd = function() {}; fbr.onProgress = function() {}; fbr.convertToObject = FileConverter.ConvertToObject; fbr.convertToArrayBuffer = FileConverter.ConvertToArrayBuffer // for backward compatibility----it is redundant. fbr.setMultipleUsers = function() {}; // extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned function fbrClone(from, to) { if (from == null || typeof from != "object") return from; if (from.constructor != Object && from.constructor != Array) return from; if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function || from.constructor == String || from.constructor == Number || from.constructor == Boolean) return new from.constructor(from); to = to || new from.constructor(); for (var name in from) { to[name] = typeof to[name] == "undefined" ? fbrClone(from[name], null) : to[name]; } return to; } } function FileBufferReaderHelper() { var fbrHelper = this; function processInWebWorker(_function) { var blob = URL.createObjectURL(new Blob([_function.toString(), 'this.onmessage = function (e) {' + _function.name + '(e.data);}' ], { type: 'application/javascript' })); var worker = new Worker(blob); return worker; } fbrHelper.readAsArrayBuffer = function(fbr, options) { var earlyCallback = options.earlyCallback; delete options.earlyCallback; function processChunk(chunk) { if (!fbr.chunks[chunk.uuid]) { fbr.chunks[chunk.uuid] = { currentPosition: -1 }; } options.extra = options.extra || { userid: 0 }; chunk.userid = options.userid || options.extra.userid || 0; chunk.extra = options.extra; fbr.chunks[chunk.uuid][chunk.currentPosition] = chunk; if (chunk.end && earlyCallback) { earlyCallback(chunk.uuid); earlyCallback = null; } // for huge files if ((chunk.maxChunks > 200 && chunk.currentPosition == 200) && earlyCallback) { earlyCallback(chunk.uuid); earlyCallback = null; } } if (false && typeof Worker !== 'undefined') { var webWorker = processInWebWorker(fileReaderWrapper); webWorker.onmessage = function(event) { processChunk(event.data); }; webWorker.postMessage(options); } else { fileReaderWrapper(options, processChunk); } }; function fileReaderWrapper(options, callback) { callback = callback || function(chunk) { postMessage(chunk); }; var file = options.file; if (!file.uuid) { file.uuid = (Math.random() * 100).toString().replace(/\./g, ''); } var chunkSize = options.chunkSize || 15 * 1000; if (options.extra && options.extra.chunkSize) { chunkSize = options.extra.chunkSize; } var sliceId = 0; var cacheSize = chunkSize; var chunksPerSlice = Math.floor(Math.min(100000000, cacheSize) / chunkSize); var sliceSize = chunksPerSlice * chunkSize; var maxChunks = Math.ceil(file.size / chunkSize); file.maxChunks = maxChunks; var numOfChunksInSlice; var currentPosition = 0; var hasEntireFile; var chunks = []; callback({ currentPosition: currentPosition, uuid: file.uuid, maxChunks: maxChunks, size: file.size, name: file.name, type: file.type, lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), start: true }); var blob, reader = new FileReader(); reader.onloadend = function(evt) { if (evt.target.readyState == FileReader.DONE) { addChunks(file.name, evt.target.result, function() { sliceId++; if ((sliceId + 1) * sliceSize < file.size) { blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize); reader.readAsArrayBuffer(blob); } else if (sliceId * sliceSize < file.size) { blob = file.slice(sliceId * sliceSize, file.size); reader.readAsArrayBuffer(blob); } else { file.url = URL.createObjectURL(file); callback({ currentPosition: currentPosition, uuid: file.uuid, maxChunks: maxChunks, size: file.size, name: file.name, lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), url: URL.createObjectURL(file), type: file.type, end: true }); } }); } }; currentPosition += 1; blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize); reader.readAsArrayBuffer(blob); function addChunks(fileName, binarySlice, addChunkCallback) { numOfChunksInSlice = Math.ceil(binarySlice.byteLength / chunkSize); for (var i = 0; i < numOfChunksInSlice; i++) { var start = i * chunkSize; chunks[currentPosition] = binarySlice.slice(start, Math.min(start + chunkSize, binarySlice.byteLength)); callback({ uuid: file.uuid, buffer: chunks[currentPosition], currentPosition: currentPosition, maxChunks: maxChunks, size: file.size, name: file.name, lastModifiedDate: (file.lastModifiedDate || new Date()).toString(), type: file.type }); currentPosition++; } if (currentPosition == maxChunks) { hasEntireFile = true; } addChunkCallback(); } } } function FileSelector() { var selector = this; var noFileSelectedCallback = function() {}; selector.selectSingleFile = function(callback, failure) { if (failure) { noFileSelectedCallback = failure; } selectFile(callback); }; selector.selectMultipleFiles = function(callback, failure) { if (failure) { noFileSelectedCallback = failure; } selectFile(callback, true); }; selector.selectDirectory = function(callback, failure) { if (failure) { noFileSelectedCallback = failure; } selectFile(callback, true, true); }; selector.accept = '*.*'; function selectFile(callback, multiple, directory) { callback = callback || function() {}; var file = document.createElement('input'); file.type = 'file'; if (multiple) { file.multiple = true; } if (directory) { file.webkitdirectory = true; } file.accept = selector.accept; file.onclick = function() { file.clickStarted = true; }; document.body.onfocus = function() { setTimeout(function() { if (!file.clickStarted) return; file.clickStarted = false; if (!file.value) { noFileSelectedCallback(); } }, 500); }; file.onchange = function() { if (multiple) { if (!file.files.length) { console.error('No file selected.'); return; } var arr = []; Array.from(file.files).forEach(function(file) { file.url = file.webkitRelativePath; arr.push(file); }); callback(arr); return; } if (!file.files[0]) { console.error('No file selected.'); return; } callback(file.files[0]); file.parentNode.removeChild(file); }; file.style.display = 'none'; (document.body || document.documentElement).appendChild(file); fireClickEvent(file); } function getValidFileName(fileName) { if (!fileName) { fileName = 'file' + (new Date).toISOString().replace(/:|\.|-/g, '') } var a = fileName; a = a.replace(/^.*[\\\/]([^\\\/]*)$/i, "$1"); a = a.replace(/\s/g, "_"); a = a.replace(/,/g, ''); a = a.toLowerCase(); return a; } function fireClickEvent(element) { if (typeof element.click === 'function') { element.click(); return; } if (typeof element.change === 'function') { element.change(); return; } if (typeof document.createEvent('Event') !== 'undefined') { var event = document.createEvent('Event'); if (typeof event.initEvent === 'function' && typeof element.dispatchEvent === 'function') { event.initEvent('click', true, true); element.dispatchEvent(event); return; } } var event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); element.dispatchEvent(event); } } function FileBufferReceiver(fbr) { var fbReceiver = this; fbReceiver.chunks = {}; fbReceiver.chunksWaiters = {}; function receive(chunk, callback) { if (!chunk.uuid) { fbr.convertToObject(chunk, function(object) { receive(object); }); return; } if (chunk.start && !fbReceiver.chunks[chunk.uuid]) { fbReceiver.chunks[chunk.uuid] = {}; if (fbr.onBegin) fbr.onBegin(chunk); } if (!chunk.end && chunk.buffer) { fbReceiver.chunks[chunk.uuid][chunk.currentPosition] = chunk.buffer; } if (chunk.end) { var chunksObject = fbReceiver.chunks[chunk.uuid]; var chunksArray = []; Object.keys(chunksObject).forEach(function(item, idx) { chunksArray.push(chunksObject[item]); }); var blob = new Blob(chunksArray, { type: chunk.type }); blob = merge(blob, chunk); blob.url = URL.createObjectURL(blob); blob.uuid = chunk.uuid; if (!blob.size) console.error('Something went wrong. Blob Size is 0.'); if (fbr.onEnd) fbr.onEnd(blob); // clear system memory delete fbReceiver.chunks[chunk.uuid]; delete fbReceiver.chunksWaiters[chunk.uuid]; } if (chunk.buffer && fbr.onProgress) fbr.onProgress(chunk); if (!chunk.end) { callback(chunk); fbReceiver.chunksWaiters[chunk.uuid] = function() { function looper() { if (!chunk.buffer) { return; } if (!fbReceiver.chunks[chunk.uuid]) { return; } if (chunk.currentPosition != chunk.maxChunks && !fbReceiver.chunks[chunk.uuid][chunk.currentPosition]) { callback(chunk); setTimeout(looper, 5000); } } setTimeout(looper, 5000); }; fbReceiver.chunksWaiters[chunk.uuid](); } } fbReceiver.receive = receive; } var FileConverter = { ConvertToArrayBuffer: function(object, callback) { binarize.pack(object, function(dataView) { callback(dataView.buffer); }); }, ConvertToObject: function(buffer, callback) { binarize.unpack(buffer, callback); } }; function merge(mergein, mergeto) { if (!mergein) mergein = {}; if (!mergeto) return mergein; for (var item in mergeto) { try { mergein[item] = mergeto[item]; } catch (e) {} } return mergein; } var debug = false; var BIG_ENDIAN = false, LITTLE_ENDIAN = true, TYPE_LENGTH = Uint8Array.BYTES_PER_ELEMENT, LENGTH_LENGTH = Uint16Array.BYTES_PER_ELEMENT, BYTES_LENGTH = Uint32Array.BYTES_PER_ELEMENT; var Types = { NULL: 0, UNDEFINED: 1, STRING: 2, NUMBER: 3, BOOLEAN: 4, ARRAY: 5, OBJECT: 6, INT8ARRAY: 7, INT16ARRAY: 8, INT32ARRAY: 9, UINT8ARRAY: 10, UINT16ARRAY: 11, UINT32ARRAY: 12, FLOAT32ARRAY: 13, FLOAT64ARRAY: 14, ARRAYBUFFER: 15, BLOB: 16, FILE: 16, BUFFER: 17 // Special type for node.js }; if (debug) { var TypeNames = [ 'NULL', 'UNDEFINED', 'STRING', 'NUMBER', 'BOOLEAN', 'ARRAY', 'OBJECT', 'INT8ARRAY', 'INT16ARRAY', 'INT32ARRAY', 'UINT8ARRAY', 'UINT16ARRAY', 'UINT32ARRAY', 'FLOAT32ARRAY', 'FLOAT64ARRAY', 'ARRAYBUFFER', 'BLOB', 'BUFFER' ]; } var Length = [ null, // Types.NULL null, // Types.UNDEFINED 'Uint16', // Types.STRING 'Float64', // Types.NUMBER 'Uint8', // Types.BOOLEAN null, // Types.ARRAY null, // Types.OBJECT 'Int8', // Types.INT8ARRAY 'Int16', // Types.INT16ARRAY 'Int32', // Types.INT32ARRAY 'Uint8', // Types.UINT8ARRAY 'Uint16', // Types.UINT16ARRAY 'Uint32', // Types.UINT32ARRAY 'Float32', // Types.FLOAT32ARRAY 'Float64', // Types.FLOAT64ARRAY 'Uint8', // Types.ARRAYBUFFER 'Uint8', // Types.BLOB, Types.FILE 'Uint8' // Types.BUFFER ]; var binary_dump = function(view, start, length) { var table = [], endianness = BIG_ENDIAN, ROW_LENGTH = 40; table[0] = []; for (var i = 0; i < ROW_LENGTH; i++) { table[0][i] = i < 10 ? '0' + i.toString(10) : i.toString(10); } for (i = 0; i < length; i++) { var code = view.getUint8(start + i, endianness); var index = ~~(i / ROW_LENGTH) + 1; if (typeof table[index] === 'undefined') table[index] = []; table[index][i % ROW_LENGTH] = code < 16 ? '0' + code.toString(16) : code.toString(16); } console.log('%c' + table[0].join(' '), 'font-weight: bold;'); for (i = 1; i < table.length; i++) { console.log(table[i].join(' ')); } }; var find_type = function(obj) { var type = undefined; if (obj === undefined) { type = Types.UNDEFINED; } else if (obj === null) { type = Types.NULL; } else { var const_name = obj.constructor.name; var const_name_reflection = obj.constructor.toString().match(/\w+/g)[1]; if (const_name !== undefined && Types[const_name.toUpperCase()] !== undefined) { // return type by .constructor.name if possible type = Types[const_name.toUpperCase()]; } else if (const_name_reflection !== undefined && Types[const_name_reflection.toUpperCase()] !== undefined) { type = Types[const_name_reflection.toUpperCase()]; } else { // Work around when constructor.name is not defined switch (typeof obj) { case 'string': type = Types.STRING; break; case 'number': type = Types.NUMBER; break; case 'boolean': type = Types.BOOLEAN; break; case 'object': if (obj instanceof Array) { type = Types.ARRAY; } else if (obj instanceof Int8Array) { type = Types.INT8ARRAY; } else if (obj instanceof Int16Array) { type = Types.INT16ARRAY; } else if (obj instanceof Int32Array) { type = Types.INT32ARRAY; } else if (obj instanceof Uint8Array) { type = Types.UINT8ARRAY; } else if (obj instanceof Uint16Array) { type = Types.UINT16ARRAY; } else if (obj instanceof Uint32Array) { type = Types.UINT32ARRAY; } else if (obj instanceof Float32Array) { type = Types.FLOAT32ARRAY; } else if (obj instanceof Float64Array) { type = Types.FLOAT64ARRAY; } else if (obj instanceof ArrayBuffer) { type = Types.ARRAYBUFFER; } else if (obj instanceof Blob) { // including File type = Types.BLOB; } else if (obj instanceof Buffer) { // node.js only type = Types.BUFFER; } else if (obj instanceof Object) { type = Types.OBJECT; } break; default: break; } } } return type; }; var utf16_utf8 = function(string) { return unescape(encodeURIComponent(string)); }; var utf8_utf16 = function(bytes) { return decodeURIComponent(escape(bytes)); }; /** * packs seriarized elements array into a packed ArrayBuffer * @param {Array} serialized Serialized array of elements. * @return {DataView} view of packed binary */ var pack = function(serialized) { var cursor = 0, i = 0, j = 0, endianness = BIG_ENDIAN; var ab = new ArrayBuffer(serialized[0].byte_length + serialized[0].header_size); var view = new DataView(ab); for (i = 0; i < serialized.length; i++) { var start = cursor, header_size = serialized[i].header_size, type = serialized[i].type, length = serialized[i].length, value = serialized[i].value, byte_length = serialized[i].byte_length, type_name = Length[type], unit = type_name === null ? 0 : window[type_name + 'Array'].BYTES_PER_ELEMENT; // Set type if (type === Types.BUFFER) { // on node.js Blob is emulated using Buffer type view.setUint8(cursor, Types.BLOB, endianness); } else { view.setUint8(cursor, type, endianness); } cursor += TYPE_LENGTH; if (debug) { console.info('Packing', type, TypeNames[type]); } // Set length if required if (type === Types.ARRAY || type === Types.OBJECT) { view.setUint16(cursor, length, endianness); cursor += LENGTH_LENGTH; if (debug) { console.info('Content Length', length); } } // Set byte length view.setUint32(cursor, byte_length, endianness); cursor += BYTES_LENGTH; if (debug) { console.info('Header Size', header_size, 'bytes'); console.info('Byte Length', byte_length, 'bytes'); } switch (type) { case Types.NULL: case Types.UNDEFINED: // NULL and UNDEFINED doesn't have any payload break; case Types.STRING: if (debug) { console.info('Actual Content %c"' + value + '"', 'font-weight:bold;'); } for (j = 0; j < length; j++, cursor += unit) { view.setUint16(cursor, value.charCodeAt(j), endianness); } break; case Types.NUMBER: case Types.BOOLEAN: if (debug) { console.info('%c' + value.toString(), 'font-weight:bold;'); } view['set' + type_name](cursor, value, endianness); cursor += unit; break; case Types.INT8ARRAY: case Types.INT16ARRAY: case Types.INT32ARRAY: case Types.UINT8ARRAY: case Types.UINT16ARRAY: case Types.UINT32ARRAY: case Types.FLOAT32ARRAY: case Types.FLOAT64ARRAY: var _view = new Uint8Array(view.buffer, cursor, byte_length); _view.set(new Uint8Array(value.buffer)); cursor += byte_length; break; case Types.ARRAYBUFFER: case Types.BUFFER: var _view = new Uint8Array(view.buffer, cursor, byte_length); _view.set(new Uint8Array(value)); cursor += byte_length; break; case Types.BLOB: case Types.ARRAY: case Types.OBJECT: break; default: throw 'TypeError: Unexpected type found.'; } if (debug) { binary_dump(view, start, cursor - start); } } return view; }; /** * Unpack binary data into an object with value and cursor * @param {DataView} view [description] * @param {Number} cursor [description] * @return {Object} */ var unpack = function(view, cursor) { var i = 0, endianness = BIG_ENDIAN, start = cursor; var type, length, byte_length, value, elem; // Retrieve "type" type = view.getUint8(cursor, endianness); cursor += TYPE_LENGTH; if (debug) { console.info('Unpacking', type, TypeNames[type]); } // Retrieve "length" if (type === Types.ARRAY || type === Types.OBJECT) { length = view.getUint16(cursor, endianness); cursor += LENGTH_LENGTH; if (debug) { console.info('Content Length', length); } } // Retrieve "byte_length" byte_length = view.getUint32(cursor, endianness); cursor += BYTES_LENGTH; if (debug) { console.info('Byte Length', byte_length, 'bytes'); } var type_name = Length[type]; var unit = type_name === null ? 0 : window[type_name + 'Array'].BYTES_PER_ELEMENT; switch (type) { case Types.NULL: case Types.UNDEFINED: if (debug) { binary_dump(view, start, cursor - start); } // NULL and UNDEFINED doesn't have any octet value = null; break; case Types.STRING: length = byte_length / unit; var string = []; for (i = 0; i < length; i++) { var code = view.getUint16(cursor, endianness); cursor += unit; string.push(String.fromCharCode(code)); } value = string.join(''); if (debug) { console.info('Actual Content %c"' + value + '"', 'font-weight:bold;'); binary_dump(view, start, cursor - start); } break; case Types.NUMBER: value = view.getFloat64(cursor, endianness); cursor += unit; if (debug) { console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;'); binary_dump(view, start, cursor - start); } break; case Types.BOOLEAN: value = view.getUint8(cursor, endianness) === 1 ? true : false; cursor += unit; if (debug) { console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;'); binary_dump(view, start, cursor - start); } break; case Types.INT8ARRAY: case Types.INT16ARRAY: case Types.INT32ARRAY: case Types.UINT8ARRAY: case Types.UINT16ARRAY: case Types.UINT32ARRAY: case Types.FLOAT32ARRAY: case Types.FLOAT64ARRAY: case Types.ARRAYBUFFER: elem = view.buffer.slice(cursor, cursor + byte_length); cursor += byte_length; // If ArrayBuffer if (type === Types.ARRAYBUFFER) { value = elem; // If other TypedArray } else { value = new window[type_name + 'Array'](elem); } if (debug) { binary_dump(view, start, cursor - start); } break; case Types.BLOB: if (debug) { binary_dump(view, start, cursor - start); } // If Blob is available (on browser) if (window.Blob) { var mime = unpack(view, cursor); var buffer = unpack(view, mime.cursor); cursor = buffer.cursor; value = new Blob([buffer.value], { type: mime.value }); } else { // node.js implementation goes here elem = view.buffer.slice(cursor, cursor + byte_length); cursor += byte_length; // node.js implementatino uses Buffer to help Blob value = new Buffer(elem); } break; case Types.ARRAY: if (debug) { binary_dump(view, start, cursor - start); } value = []; for (i = 0; i < length; i++) { // Retrieve array element elem = unpack(view, cursor); cursor = elem.cursor; value.push(elem.value); } break; case Types.OBJECT: if (debug) { binary_dump(view, start, cursor - start); } value = {}; for (i = 0; i < length; i++) { // Retrieve object key and value in sequence var key = unpack(view, cursor); var val = unpack(view, key.cursor); cursor = val.cursor; value[key.value] = val.value; } break; default: throw 'TypeError: Type not supported.'; } return { value: value, cursor: cursor }; }; /** * deferred function to process multiple serialization in order * @param {array} array [description] * @param {Function} callback [description] * @return {void} no return value */ var deferredSerialize = function(array, callback) { var length = array.length, results = [], count = 0, byte_length = 0; for (var i = 0; i < array.length; i++) { (function(index) { serialize(array[index], function(result) { // store results in order results[index] = result; // count byte length byte_length += result[0].header_size + result[0].byte_length; // when all results are on table if (++count === length) { // finally concatenate all reuslts into a single array in order var array = []; for (var j = 0; j < results.length; j++) { array = array.concat(results[j]); } callback(array, byte_length); } }); })(i); } }; /** * Serializes object and return byte_length * @param {mixed} obj JavaScript object you want to serialize * @return {Array} Serialized array object */ var serialize = function(obj, callback) { var subarray = [], unit = 1, header_size = TYPE_LENGTH + BYTES_LENGTH, type, byte_length = 0, length = 0, value = obj; type = find_type(obj); unit = Length[type] === undefined || Length[type] === null ? 0 : window[Length[type] + 'Array'].BYTES_PER_ELEMENT; switch (type) { case Types.UNDEFINED: case Types.NULL: break; case Types.NUMBER: case Types.BOOLEAN: byte_length = unit; break; case Types.STRING: length = obj.length; byte_length += length * unit; break; case Types.INT8ARRAY: case Types.INT16ARRAY: case Types.INT32ARRAY: case Types.UINT8ARRAY: case Types.UINT16ARRAY: case Types.UINT32ARRAY: case Types.FLOAT32ARRAY: case Types.FLOAT64ARRAY: length = obj.length; byte_length += length * unit; break; case Types.ARRAY: deferredSerialize(obj, function(subarray, byte_length) { callback([{ type: type, length: obj.length, header_size: header_size + LENGTH_LENGTH, byte_length: byte_length, value: null }].concat(subarray)); }); return; case Types.OBJECT: var deferred = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { deferred.push(key); deferred.push(obj[key]); length++; } } deferredSerialize(deferred, function(subarray, byte_length) { callback([{ type: type, length: length, header_size: header_size + LENGTH_LENGTH, byte_length: byte_length, value: null }].concat(subarray)); }); return; case Types.ARRAYBUFFER: byte_length += obj.byteLength; break; case Types.BLOB: var mime_type = obj.type; var reader = new FileReader(); reader.onload = function(e) { deferredSerialize([mime_type, e.target.result], function(subarray, byte_length) { callback([{ type: type, length: length, header_size: header_size, byte_length: byte_length, value: null }].concat(subarray)); }); }; reader.onerror = function(e) { throw 'FileReader Error: ' + e; }; reader.readAsArrayBuffer(obj); return; case Types.BUFFER: byte_length += obj.length; break; default: throw 'TypeError: Type "' + obj.constructor.name + '" not supported.'; } callback([{ type: type, length: length, header_size: header_size, byte_length: byte_length, value: value }].concat(subarray)); }; /** * Deserialize binary and return JavaScript object * @param ArrayBuffer buffer ArrayBuffer you want to deserialize * @return mixed Retrieved JavaScript object */ var deserialize = function(buffer, callback) { var view = buffer instanceof DataView ? buffer : new DataView(buffer); var result = unpack(view, 0); return result.value; }; if (debug) { window.Test = { BIG_ENDIAN: BIG_ENDIAN, LITTLE_ENDIAN: LITTLE_ENDIAN, Types: Types, pack: pack, unpack: unpack, serialize: serialize, deserialize: deserialize }; } var binarize = { pack: function(obj, callback) { try { if (debug) console.info('%cPacking Start', 'font-weight: bold; color: red;', obj); serialize(obj, function(array) { if (debug) console.info('Serialized Object', array); callback(pack(array)); }); } catch (e) { throw e; } }, unpack: function(buffer, callback) { try { if (debug) console.info('%cUnpacking Start', 'font-weight: bold; color: red;', buffer); var result = deserialize(buffer); if (debug) console.info('Deserialized Object', result); callback(result); } catch (e) { throw e; } } }; window.FileConverter = FileConverter; window.FileSelector = FileSelector; window.FileBufferReader = FileBufferReader; })();