arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-file/www/FileReader.js
plugins/cordova-plugin-file/www/FileReader.jsRaw
  1/*
  2 *
  3 * Licensed to the Apache Software Foundation (ASF) under one
  4 * or more contributor license agreements.  See the NOTICE file
  5 * distributed with this work for additional information
  6 * regarding copyright ownership.  The ASF licenses this file
  7 * to you under the Apache License, Version 2.0 (the
  8 * "License"); you may not use this file except in compliance
  9 * with the License.  You may obtain a copy of the License at
 10 *
 11 *   http://www.apache.org/licenses/LICENSE-2.0
 12 *
 13 * Unless required by applicable law or agreed to in writing,
 14 * software distributed under the License is distributed on an
 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 16 * KIND, either express or implied.  See the License for the
 17 * specific language governing permissions and limitations
 18 * under the License.
 19 *
 20*/
 21
 22var exec = require('cordova/exec'),
 23    modulemapper = require('cordova/modulemapper'),
 24    utils = require('cordova/utils'),
 25    FileError = require('./FileError'),
 26    ProgressEvent = require('./ProgressEvent'),
 27    origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
 28
 29/**
 30 * This class reads the mobile device file system.
 31 *
 32 * For Android:
 33 *      The root directory is the root of the file system.
 34 *      To read from the SD card, the file name is "sdcard/my_file.txt"
 35 * @constructor
 36 */
 37var FileReader = function() {
 38    this._readyState = 0;
 39    this._error = null;
 40    this._result = null;
 41    this._progress = null;
 42    this._localURL = '';
 43    this._realReader = origFileReader ? new origFileReader() : {};
 44};
 45
 46/**
 47 * Defines the maximum size to read at a time via the native API. The default value is a compromise between
 48 * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
 49 * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
 50 * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
 51 */
 52FileReader.READ_CHUNK_SIZE = 256*1024;
 53
 54// States
 55FileReader.EMPTY = 0;
 56FileReader.LOADING = 1;
 57FileReader.DONE = 2;
 58
 59utils.defineGetter(FileReader.prototype, 'readyState', function() {
 60    return this._localURL ? this._readyState : this._realReader.readyState;
 61});
 62
 63utils.defineGetter(FileReader.prototype, 'error', function() {
 64    return this._localURL ? this._error: this._realReader.error;
 65});
 66
 67utils.defineGetter(FileReader.prototype, 'result', function() {
 68    return this._localURL ? this._result: this._realReader.result;
 69});
 70
 71function defineEvent(eventName) {
 72    utils.defineGetterSetter(FileReader.prototype, eventName, function() {
 73        return this._realReader[eventName] || null;
 74    }, function(value) {
 75        this._realReader[eventName] = value;
 76    });
 77}
 78defineEvent('onloadstart');    // When the read starts.
 79defineEvent('onprogress');     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
 80defineEvent('onload');         // When the read has successfully completed.
 81defineEvent('onerror');        // When the read has failed (see errors).
 82defineEvent('onloadend');      // When the request has completed (either in success or failure).
 83defineEvent('onabort');        // When the read has been aborted. For instance, by invoking the abort() method.
 84
 85function initRead(reader, file) {
 86    // Already loading something
 87    if (reader.readyState == FileReader.LOADING) {
 88      throw new FileError(FileError.INVALID_STATE_ERR);
 89    }
 90
 91    reader._result = null;
 92    reader._error = null;
 93    reader._progress = 0;
 94    reader._readyState = FileReader.LOADING;
 95
 96    if (typeof file.localURL == 'string') {
 97        reader._localURL = file.localURL;
 98    } else {
 99        reader._localURL = '';
100        return true;
101    }
102
103    if (reader.onloadstart) {
104        reader.onloadstart(new ProgressEvent("loadstart", {target:reader}));
105    }
106}
107
108/**
109 * Callback used by the following read* functions to handle incremental or final success.
110 * Must be bound to the FileReader's this along with all but the last parameter,
111 * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
112 * @param readType The name of the read function to call.
113 * @param encoding Text encoding, or null if this is not a text type read.
114 * @param offset Starting offset of the read.
115 * @param totalSize Total number of bytes or chars to read.
116 * @param accumulate A function that takes the callback result and accumulates it in this._result.
117 * @param r Callback result returned by the last read exec() call, or null to begin reading.
118 */
119function readSuccessCallback(readType, encoding, offset, totalSize, accumulate, r) {
120    if (this._readyState === FileReader.DONE) {
121        return;
122    }
123
124    var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE;
125    if (readType === 'readAsDataURL') {
126        // Windows proxy does not support reading file slices as Data URLs
127        // so read the whole file at once.
128        CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize :
129            // Calculate new chunk size for data URLs to be multiply of 3
130            // Otherwise concatenated base64 chunks won't be valid base64 data
131            FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3;
132    }
133
134    if (typeof r !== "undefined") {
135        accumulate(r);
136        this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize);
137
138        if (typeof this.onprogress === "function") {
139            this.onprogress(new ProgressEvent("progress", {loaded:this._progress, total:totalSize}));
140        }
141    }
142
143    if (typeof r === "undefined" || this._progress < totalSize) {
144        var execArgs = [
145            this._localURL,
146            offset + this._progress,
147            offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)];
148        if (encoding) {
149            execArgs.splice(1, 0, encoding);
150        }
151        exec(
152            readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
153            readFailureCallback.bind(this),
154            "File", readType, execArgs);
155    } else {
156        this._readyState = FileReader.DONE;
157
158        if (typeof this.onload === "function") {
159            this.onload(new ProgressEvent("load", {target:this}));
160        }
161
162        if (typeof this.onloadend === "function") {
163            this.onloadend(new ProgressEvent("loadend", {target:this}));
164        }
165    }
166}
167
168/**
169 * Callback used by the following read* functions to handle errors.
170 * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
171 */
172function readFailureCallback(e) {
173    if (this._readyState === FileReader.DONE) {
174        return;
175    }
176
177    this._readyState = FileReader.DONE;
178    this._result = null;
179    this._error = new FileError(e);
180
181    if (typeof this.onerror === "function") {
182        this.onerror(new ProgressEvent("error", {target:this}));
183    }
184
185    if (typeof this.onloadend === "function") {
186        this.onloadend(new ProgressEvent("loadend", {target:this}));
187    }
188}
189
190/**
191 * Abort reading file.
192 */
193FileReader.prototype.abort = function() {
194    if (origFileReader && !this._localURL) {
195        return this._realReader.abort();
196    }
197    this._result = null;
198
199    if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) {
200      return;
201    }
202
203    this._readyState = FileReader.DONE;
204
205    // If abort callback
206    if (typeof this.onabort === 'function') {
207        this.onabort(new ProgressEvent('abort', {target:this}));
208    }
209    // If load end callback
210    if (typeof this.onloadend === 'function') {
211        this.onloadend(new ProgressEvent('loadend', {target:this}));
212    }
213};
214
215/**
216 * Read text file.
217 *
218 * @param file          {File} File object containing file properties
219 * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
220 */
221FileReader.prototype.readAsText = function(file, encoding) {
222    if (initRead(this, file)) {
223        return this._realReader.readAsText(file, encoding);
224    }
225
226    // Default encoding is UTF-8
227    var enc = encoding ? encoding : "UTF-8";
228
229    var totalSize = file.end - file.start;
230    readSuccessCallback.bind(this)("readAsText", enc, file.start, totalSize, function(r) {
231        if (this._progress === 0) {
232            this._result = "";
233        }
234        this._result += r;
235    }.bind(this));
236};
237
238
239/**
240 * Read file and return data as a base64 encoded data url.
241 * A data url is of the form:
242 *      data:[<mediatype>][;base64],<data>
243 *
244 * @param file          {File} File object containing file properties
245 */
246FileReader.prototype.readAsDataURL = function(file) {
247    if (initRead(this, file)) {
248        return this._realReader.readAsDataURL(file);
249    }
250
251    var totalSize = file.end - file.start;
252    readSuccessCallback.bind(this)("readAsDataURL", null, file.start, totalSize, function(r) {
253        var commaIndex = r.indexOf(',');
254        if (this._progress === 0) {
255            this._result = r;
256        } else {
257            this._result += r.substring(commaIndex + 1);
258        }
259    }.bind(this));
260};
261
262/**
263 * Read file and return data as a binary data.
264 *
265 * @param file          {File} File object containing file properties
266 */
267FileReader.prototype.readAsBinaryString = function(file) {
268    if (initRead(this, file)) {
269        return this._realReader.readAsBinaryString(file);
270    }
271
272    var totalSize = file.end - file.start;
273    readSuccessCallback.bind(this)("readAsBinaryString", null, file.start, totalSize, function(r) {
274        if (this._progress === 0) {
275            this._result = "";
276        }
277        this._result += r;
278    }.bind(this));
279};
280
281/**
282 * Read file and return data as a binary data.
283 *
284 * @param file          {File} File object containing file properties
285 */
286FileReader.prototype.readAsArrayBuffer = function(file) {
287    if (initRead(this, file)) {
288        return this._realReader.readAsArrayBuffer(file);
289    }
290
291    var totalSize = file.end - file.start;
292    readSuccessCallback.bind(this)("readAsArrayBuffer", null, file.start, totalSize, function(r) {
293        var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
294        resultArray.set(new Uint8Array(r), this._progress);
295        this._result = resultArray.buffer;
296    }.bind(this));
297};
298
299module.exports = FileReader;