| "use strict"; |
| |
| (function() |
| { |
| if(typeof XMLHttpRequest === "undefined") |
| { |
| v86util.load_file = load_file_nodejs; |
| } |
| else |
| { |
| v86util.load_file = load_file; |
| } |
| |
| v86util.AsyncXHRBuffer = AsyncXHRBuffer; |
| v86util.AsyncFileBuffer = AsyncFileBuffer; |
| v86util.SyncFileBuffer = SyncFileBuffer; |
| |
| /** |
| * @param {string} filename |
| * @param {Object} options |
| */ |
| function load_file(filename, options) |
| { |
| var http = new XMLHttpRequest(); |
| |
| http.open(options.method || "get", filename, true); |
| |
| if(!options.as_text) |
| { |
| http.responseType = "arraybuffer"; |
| } |
| |
| if(options.headers) |
| { |
| var header_names = Object.keys(options.headers); |
| |
| for(var i = 0; i < header_names.length; i++) |
| { |
| var name = header_names[i]; |
| |
| http.setRequestHeader(name, options.headers[name]); |
| } |
| } |
| |
| http.onload = function(e) |
| { |
| if(http.readyState === 4) |
| { |
| if(http.status !== 200 && http.status !== 206) |
| { |
| console.log("Loading the image `" + filename + "` failed"); |
| } |
| else if(http.response) |
| { |
| options.done && options.done(http.response, http); |
| } |
| } |
| }; |
| |
| if(options.progress) |
| { |
| http.onprogress = function(e) |
| { |
| options.progress(e); |
| }; |
| } |
| |
| http.send(null); |
| } |
| |
| function load_file_nodejs(filename, options) |
| { |
| var o = { |
| encoding: options.as_text ? "utf-8" : null, |
| }; |
| |
| require("fs")["readFile"](filename, o, function(err, data) |
| { |
| if(err) |
| { |
| console.log("Could not read file:", filename); |
| } |
| else |
| { |
| var result = data; |
| |
| if(!options.as_text) |
| { |
| result = new Uint8Array(result).buffer; |
| } |
| |
| options.done(result); |
| } |
| }); |
| } |
| |
| /** |
| * Asynchronous access to ArrayBuffer, loading blocks lazily as needed, |
| * using the `Range: bytes=...` header |
| * |
| * @constructor |
| * @param {string} filename Name of the file to download |
| * @param {number|undefined} size |
| */ |
| function AsyncXHRBuffer(filename, size) |
| { |
| this.filename = filename; |
| |
| /** @const */ |
| this.block_size = 256; |
| this.byteLength = size; |
| |
| this.loaded_blocks = {}; |
| |
| this.onload = undefined; |
| this.onprogress = undefined; |
| } |
| |
| AsyncXHRBuffer.prototype.load = function() |
| { |
| if(this.byteLength !== undefined) |
| { |
| this.onload && this.onload({}); |
| return; |
| } |
| |
| // Determine the size using a request |
| |
| load_file(this.filename, { |
| done: function done(buffer, http) |
| { |
| var header = http.getResponseHeader("Content-Range") || ""; |
| var match = header.match(/\/(\d+)\s*$/); |
| |
| if(match) |
| { |
| this.byteLength = +match[1] |
| this.onload && this.onload({}); |
| } |
| else |
| { |
| console.assert(false, |
| "Cannot use: " + this.filename + ". " + |
| "`Range: bytes=...` header not supported (Got `" + header + "`)"); |
| } |
| }.bind(this), |
| headers: { |
| Range: "bytes=0-0", |
| |
| // Added by Chromium, but can cause the whole file to be sent |
| // Settings this to empty also causes problems and Chromium |
| // doesn't seem to create this header any more |
| //"If-Range": "", |
| } |
| }); |
| } |
| |
| /** |
| * @param {number} offset |
| * @param {number} len |
| * @param {function(!Uint8Array)} fn |
| */ |
| AsyncXHRBuffer.prototype.get_from_cache = function(offset, len, fn) |
| { |
| var number_of_blocks = len / this.block_size; |
| var block_index = offset / this.block_size; |
| |
| for(var i = 0; i < number_of_blocks; i++) |
| { |
| var block = this.loaded_blocks[block_index + i]; |
| |
| if(!block) |
| { |
| return; |
| } |
| } |
| |
| console.log("From cache", number_of_blocks, len); |
| |
| if(number_of_blocks === 1) |
| { |
| return this.loaded_blocks[block_index]; |
| } |
| else |
| { |
| var result = new Uint8Array(len); |
| for(var i = 0; i < number_of_blocks; i++) |
| { |
| result.set(this.loaded_blocks[block_index + i], i * this.block_size); |
| } |
| return result; |
| } |
| }; |
| |
| /** |
| * @param {number} offset |
| * @param {number} len |
| * @param {function(!Uint8Array)} fn |
| */ |
| AsyncXHRBuffer.prototype.get = function(offset, len, fn) |
| { |
| console.assert(offset % this.block_size === 0); |
| console.assert(len % this.block_size === 0); |
| console.assert(len); |
| |
| var block = this.get_from_cache(offset, len, fn); |
| if(block) |
| { |
| fn(block); |
| return; |
| } |
| |
| var range_start = offset; |
| var range_end = offset + len - 1; |
| |
| load_file(this.filename, { |
| done: function done(buffer) |
| { |
| var block = new Uint8Array(buffer); |
| this.handle_read(offset, len, block); |
| fn(block); |
| }.bind(this), |
| headers: { |
| Range: "bytes=" + range_start + "-" + range_end, |
| } |
| }); |
| } |
| |
| /** |
| * Relies on this.byteLength, this.loaded_blocks and this.block_size |
| * |
| * @this {AsyncFileBuffer|AsyncXHRBuffer} |
| * |
| * @param {number} start |
| * @param {!Uint8Array} data |
| * @param {function()} fn |
| */ |
| AsyncXHRBuffer.prototype.set = function(start, data, fn) |
| { |
| console.assert(start + data.byteLength <= this.byteLength); |
| |
| var len = data.length; |
| |
| console.assert(start % this.block_size === 0); |
| console.assert(len % this.block_size === 0); |
| console.assert(len); |
| |
| var start_block = start / this.block_size; |
| var block_count = len / this.block_size; |
| |
| for(var i = 0; i < block_count; i++) |
| { |
| var block = this.loaded_blocks[start_block + i]; |
| |
| if(block === undefined) |
| { |
| block = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size); |
| } |
| |
| var data_slice = data.subarray(i * this.block_size, (i + 1) * this.block_size); |
| block.set(data_slice); |
| |
| console.assert(block.byteLength === data_slice.length); |
| } |
| |
| fn(); |
| } |
| |
| /** |
| * @this {AsyncFileBuffer|AsyncXHRBuffer} |
| * @param {number} offset |
| * @param {number} len |
| * @param {!Uint8Array} block |
| */ |
| AsyncXHRBuffer.prototype.handle_read = function(offset, len, block) |
| { |
| // Used by AsyncXHRBuffer and AsyncFileBuffer |
| // Overwrites blocks from the original source that have been written since |
| |
| var start_block = offset / this.block_size; |
| var block_count = len / this.block_size; |
| |
| for(var i = 0; i < block_count; i++) |
| { |
| var written_block = this.loaded_blocks[start_block + i]; |
| |
| if(written_block) |
| { |
| block.set(written_block, i * this.block_size); |
| } |
| //else |
| //{ |
| // var cached = this.loaded_blocks[start_block + i] = new Uint8Array(this.block_size); |
| // cached.set(block.subarray(i * this.block_size, (i + 1) * this.block_size)); |
| //} |
| } |
| }; |
| |
| AsyncXHRBuffer.prototype.get_buffer = function(fn) |
| { |
| // We must download all parts, unlikely a good idea for big files |
| fn(); |
| }; |
| |
| /** |
| * Synchronous access to File, loading blocks from the input type=file |
| * The whole file is loaded into memory during initialisation |
| * |
| * @constructor |
| */ |
| function SyncFileBuffer(file) |
| { |
| this.file = file; |
| this.byteLength = file.size; |
| |
| if(file.size > (1 << 30)) |
| { |
| console.warn("SyncFileBuffer: Allocating buffer of " + (file.size >> 20) + " MB ..."); |
| } |
| |
| this.buffer = new ArrayBuffer(file.size); |
| this.onload = undefined; |
| this.onprogress = undefined; |
| } |
| |
| SyncFileBuffer.prototype.load = function() |
| { |
| this.load_next(0); |
| } |
| |
| /** |
| * @param {number} start |
| */ |
| SyncFileBuffer.prototype.load_next = function(start) |
| { |
| /** @const */ |
| var PART_SIZE = 4 << 20; |
| |
| var filereader = new FileReader(); |
| |
| filereader.onload = function(e) |
| { |
| var buffer = new Uint8Array(e.target.result); |
| new Uint8Array(this.buffer, start).set(buffer); |
| this.load_next(start + PART_SIZE); |
| }.bind(this); |
| |
| if(this.onprogress) |
| { |
| this.onprogress({ |
| loaded: start, |
| total: this.byteLength, |
| lengthComputable: true, |
| }); |
| } |
| |
| if(start < this.byteLength) |
| { |
| var end = Math.min(start + PART_SIZE, this.byteLength); |
| var slice = this.file.slice(start, end); |
| filereader.readAsArrayBuffer(slice); |
| } |
| else |
| { |
| this.file = undefined; |
| this.onload && this.onload({ buffer: this.buffer }); |
| } |
| } |
| |
| /** |
| * @param {number} start |
| * @param {number} len |
| * @param {function(!Uint8Array)} fn |
| */ |
| SyncFileBuffer.prototype.get = function(start, len, fn) |
| { |
| console.assert(start + len <= this.byteLength); |
| fn(new Uint8Array(this.buffer, start, len)); |
| }; |
| |
| /** |
| * @param {number} offset |
| * @param {!Uint8Array} slice |
| * @param {function()} fn |
| */ |
| SyncFileBuffer.prototype.set = function(offset, slice, fn) |
| { |
| console.assert(offset + slice.byteLength <= this.byteLength); |
| |
| new Uint8Array(this.buffer, offset, slice.byteLength).set(slice); |
| fn(); |
| }; |
| |
| SyncFileBuffer.prototype.get_buffer = function(fn) |
| { |
| fn(this.buffer); |
| }; |
| |
| /** |
| * Asynchronous access to File, loading blocks from the input type=file |
| * |
| * @constructor |
| */ |
| function AsyncFileBuffer(file) |
| { |
| this.file = file; |
| this.byteLength = file.size; |
| |
| /** @const */ |
| this.block_size = 256 |
| this.loaded_blocks = {}; |
| |
| this.onload = undefined; |
| this.onprogress = undefined; |
| } |
| |
| AsyncFileBuffer.prototype.load = function() |
| { |
| this.onload && this.onload({}); |
| }; |
| |
| /** |
| * @param {number} offset |
| * @param {number} len |
| * @param {function(!Uint8Array)} fn |
| */ |
| AsyncFileBuffer.prototype.get = function(offset, len, fn) |
| { |
| console.assert(offset % this.block_size === 0); |
| console.assert(len % this.block_size === 0); |
| console.assert(len); |
| |
| var block = this.get_from_cache(offset, len, fn); |
| if(block) |
| { |
| fn(block); |
| return; |
| } |
| |
| var fr = new FileReader(); |
| |
| fr.onload = function(e) |
| { |
| var buffer = e.target.result; |
| var block = new Uint8Array(buffer); |
| |
| this.handle_read(offset, len, block); |
| fn(block); |
| }.bind(this); |
| |
| fr.readAsArrayBuffer(this.file.slice(offset, offset + len)); |
| } |
| AsyncFileBuffer.prototype.get_from_cache = AsyncXHRBuffer.prototype.get_from_cache; |
| AsyncFileBuffer.prototype.set = AsyncXHRBuffer.prototype.set; |
| AsyncFileBuffer.prototype.handle_read = AsyncXHRBuffer.prototype.handle_read; |
| |
| AsyncFileBuffer.prototype.get_buffer = function(fn) |
| { |
| // We must load all parts, unlikely a good idea for big files |
| fn(); |
| }; |
| |
| AsyncFileBuffer.prototype.get_as_file = function(name) |
| { |
| var parts = []; |
| var existing_blocks = Object.keys(this.loaded_blocks) |
| .map(Number) |
| .sort(function(x, y) { return x - y; }); |
| |
| var current_offset = 0; |
| |
| for(var i = 0; i < existing_blocks.length; i++) |
| { |
| var block_index = existing_blocks[i]; |
| var block = this.loaded_blocks[block_index]; |
| var start = block_index * this.block_size; |
| console.assert(start >= current_offset); |
| |
| if(start !== current_offset) |
| { |
| parts.push(this.file.slice(current_offset, start)); |
| current_offset = start; |
| } |
| |
| parts.push(block); |
| current_offset += block.length; |
| } |
| |
| if(current_offset !== this.file.size) |
| { |
| parts.push(this.file.slice(current_offset)); |
| } |
| |
| var file = new File(parts, name); |
| console.assert(file.size === this.file.size); |
| |
| return file; |
| }; |
| |
| })(); |