fbr
Version:
FileBufferReader is a JavaScript library reads file and returns chunkified array-buffers. The resulting buffers can be shared using WebRTC data channels or socket.io. Share files same as Skype do!
1,237 lines (1,034 loc) • 42.2 kB
JavaScript
// 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
// --------------------------------------------------
;
(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;
})();