설명 없음

FileReader.js 10KB

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