| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| var exec = require('cordova/exec'); |
| var modulemapper = require('cordova/modulemapper'); |
| var utils = require('cordova/utils'); |
| var FileError = require('./FileError'); |
| var ProgressEvent = require('./ProgressEvent'); |
| var origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader'); |
| |
| /** |
| * This class reads the mobile device file system. |
| * |
| * For Android: |
| * The root directory is the root of the file system. |
| * To read from the SD card, the file name is "sdcard/my_file.txt" |
| * @constructor |
| */ |
| var FileReader = function () { |
| this._readyState = 0; |
| this._error = null; |
| this._result = null; |
| this._progress = null; |
| this._localURL = ''; |
| this._realReader = origFileReader ? new origFileReader() : {}; // eslint-disable-line new-cap |
| }; |
| |
| /** |
| * Defines the maximum size to read at a time via the native API. The default value is a compromise between |
| * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files. |
| * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause |
| * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.) |
| */ |
| FileReader.READ_CHUNK_SIZE = 256 * 1024; |
| |
| // States |
| FileReader.EMPTY = 0; |
| FileReader.LOADING = 1; |
| FileReader.DONE = 2; |
| |
| utils.defineGetter(FileReader.prototype, 'readyState', function () { |
| return this._localURL ? this._readyState : this._realReader.readyState; |
| }); |
| |
| utils.defineGetter(FileReader.prototype, 'error', function () { |
| return this._localURL ? this._error : this._realReader.error; |
| }); |
| |
| utils.defineGetter(FileReader.prototype, 'result', function () { |
| return this._localURL ? this._result : this._realReader.result; |
| }); |
| |
| function defineEvent (eventName) { |
| utils.defineGetterSetter(FileReader.prototype, eventName, function () { |
| return this._realReader[eventName] || null; |
| }, function (value) { |
| this._realReader[eventName] = value; |
| }); |
| } |
| |
| // When the read starts. |
| defineEvent('onloadstart'); |
| |
| // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) |
| defineEvent('onprogress'); |
| |
| // When the read has successfully completed. |
| defineEvent('onload'); |
| |
| // When the read has failed (see errors). |
| defineEvent('onerror'); |
| |
| // When the request has completed (either in success or failure). |
| defineEvent('onloadend'); |
| |
| // When the read has been aborted. For instance, by invoking the abort() method. |
| defineEvent('onabort'); |
| |
| function initRead (reader, file) { |
| // Already loading something |
| if (reader.readyState === FileReader.LOADING) { |
| throw new FileError(FileError.INVALID_STATE_ERR); |
| } |
| |
| reader._result = null; |
| reader._error = null; |
| reader._progress = 0; |
| reader._readyState = FileReader.LOADING; |
| |
| if (typeof file.localURL === 'string') { |
| reader._localURL = file.localURL; |
| } else { |
| reader._localURL = ''; |
| return true; |
| } |
| |
| if (reader.onloadstart) { |
| reader.onloadstart(new ProgressEvent('loadstart', { target: reader })); |
| } |
| } |
| |
| /** |
| * Callback used by the following read* functions to handle incremental or final success. |
| * Must be bound to the FileReader's this along with all but the last parameter, |
| * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate) |
| * @param readType The name of the read function to call. |
| * @param encoding Text encoding, or null if this is not a text type read. |
| * @param offset Starting offset of the read. |
| * @param totalSize Total number of bytes or chars to read. |
| * @param accumulate A function that takes the callback result and accumulates it in this._result. |
| * @param r Callback result returned by the last read exec() call, or null to begin reading. |
| */ |
| function readSuccessCallback (readType, encoding, offset, totalSize, accumulate, r) { |
| if (this._readyState === FileReader.DONE) { |
| return; |
| } |
| |
| var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE; |
| if (readType === 'readAsDataURL') { |
| // Windows proxy does not support reading file slices as Data URLs |
| // so read the whole file at once. |
| CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize |
| : ( |
| // Calculate new chunk size for data URLs to be multiply of 3 |
| // Otherwise concatenated base64 chunks won't be valid base64 data |
| FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3 |
| ); |
| } |
| |
| if (typeof r !== 'undefined') { |
| accumulate(r); |
| this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize); |
| |
| if (typeof this.onprogress === 'function') { |
| this.onprogress(new ProgressEvent('progress', { loaded: this._progress, total: totalSize })); |
| } |
| } |
| |
| if (typeof r === 'undefined' || this._progress < totalSize) { |
| var execArgs = [ |
| this._localURL, |
| offset + this._progress, |
| offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)]; |
| if (encoding) { |
| execArgs.splice(1, 0, encoding); |
| } |
| exec( |
| readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate), |
| readFailureCallback.bind(this), |
| 'File', readType, execArgs); |
| } else { |
| this._readyState = FileReader.DONE; |
| |
| if (typeof this.onload === 'function') { |
| this.onload(new ProgressEvent('load', { target: this })); |
| } |
| |
| if (typeof this.onloadend === 'function') { |
| this.onloadend(new ProgressEvent('loadend', { target: this })); |
| } |
| } |
| } |
| |
| /** |
| * Callback used by the following read* functions to handle errors. |
| * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this) |
| */ |
| function readFailureCallback (e) { |
| if (this._readyState === FileReader.DONE) { |
| return; |
| } |
| |
| this._readyState = FileReader.DONE; |
| this._result = null; |
| this._error = new FileError(e); |
| |
| if (typeof this.onerror === 'function') { |
| this.onerror(new ProgressEvent('error', { target: this })); |
| } |
| |
| if (typeof this.onloadend === 'function') { |
| this.onloadend(new ProgressEvent('loadend', { target: this })); |
| } |
| } |
| |
| /** |
| * Abort reading file. |
| */ |
| FileReader.prototype.abort = function () { |
| if (origFileReader && !this._localURL) { |
| return this._realReader.abort(); |
| } |
| this._result = null; |
| |
| if (this._readyState === FileReader.DONE || this._readyState === FileReader.EMPTY) { |
| return; |
| } |
| |
| this._readyState = FileReader.DONE; |
| |
| // If abort callback |
| if (typeof this.onabort === 'function') { |
| this.onabort(new ProgressEvent('abort', { target: this })); |
| } |
| // If load end callback |
| if (typeof this.onloadend === 'function') { |
| this.onloadend(new ProgressEvent('loadend', { target: this })); |
| } |
| }; |
| |
| /** |
| * Read text file. |
| * |
| * @param file {File} File object containing file properties |
| * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) |
| */ |
| FileReader.prototype.readAsText = function (file, encoding) { |
| if (initRead(this, file)) { |
| return this._realReader.readAsText(file, encoding); |
| } |
| |
| // Default encoding is UTF-8 |
| var enc = encoding || 'UTF-8'; |
| |
| var totalSize = file.end - file.start; |
| readSuccessCallback.bind(this)('readAsText', enc, file.start, totalSize, function (r) { |
| if (this._progress === 0) { |
| this._result = ''; |
| } |
| this._result += r; |
| }.bind(this)); |
| }; |
| |
| /** |
| * Read file and return data as a base64 encoded data url. |
| * A data url is of the form: |
| * data:[<mediatype>][;base64],<data> |
| * |
| * @param file {File} File object containing file properties |
| */ |
| FileReader.prototype.readAsDataURL = function (file) { |
| if (initRead(this, file)) { |
| return this._realReader.readAsDataURL(file); |
| } |
| |
| var totalSize = file.end - file.start; |
| readSuccessCallback.bind(this)('readAsDataURL', null, file.start, totalSize, function (r) { |
| var commaIndex = r.indexOf(','); |
| if (this._progress === 0) { |
| this._result = r; |
| } else { |
| this._result += r.substring(commaIndex + 1); |
| } |
| }.bind(this)); |
| }; |
| |
| /** |
| * Read file and return data as a binary data. |
| * |
| * @param file {File} File object containing file properties |
| */ |
| FileReader.prototype.readAsBinaryString = function (file) { |
| if (initRead(this, file)) { |
| return this._realReader.readAsBinaryString(file); |
| } |
| |
| var totalSize = file.end - file.start; |
| readSuccessCallback.bind(this)('readAsBinaryString', null, file.start, totalSize, function (r) { |
| if (this._progress === 0) { |
| this._result = ''; |
| } |
| this._result += r; |
| }.bind(this)); |
| }; |
| |
| /** |
| * Read file and return data as a binary data. |
| * |
| * @param file {File} File object containing file properties |
| */ |
| FileReader.prototype.readAsArrayBuffer = function (file) { |
| if (initRead(this, file)) { |
| return this._realReader.readAsArrayBuffer(file); |
| } |
| |
| var totalSize = file.end - file.start; |
| readSuccessCallback.bind(this)('readAsArrayBuffer', null, file.start, totalSize, function (r) { |
| var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result)); |
| resultArray.set(new Uint8Array(r), this._progress); |
| this._result = resultArray.buffer; |
| }.bind(this)); |
| }; |
| |
| module.exports = FileReader; |