chunked_stream.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. var sharedUtil = require('../shared/util.js');
  17. var MissingDataException = sharedUtil.MissingDataException;
  18. var arrayByteLength = sharedUtil.arrayByteLength;
  19. var arraysToBytes = sharedUtil.arraysToBytes;
  20. var assert = sharedUtil.assert;
  21. var createPromiseCapability = sharedUtil.createPromiseCapability;
  22. var isInt = sharedUtil.isInt;
  23. var isEmptyObj = sharedUtil.isEmptyObj;
  24. var ChunkedStream = function ChunkedStreamClosure() {
  25. function ChunkedStream(length, chunkSize, manager) {
  26. this.bytes = new Uint8Array(length);
  27. this.start = 0;
  28. this.pos = 0;
  29. this.end = length;
  30. this.chunkSize = chunkSize;
  31. this.loadedChunks = [];
  32. this.numChunksLoaded = 0;
  33. this.numChunks = Math.ceil(length / chunkSize);
  34. this.manager = manager;
  35. this.progressiveDataLength = 0;
  36. this.lastSuccessfulEnsureByteChunk = -1;
  37. }
  38. ChunkedStream.prototype = {
  39. getMissingChunks: function ChunkedStream_getMissingChunks() {
  40. var chunks = [];
  41. for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
  42. if (!this.loadedChunks[chunk]) {
  43. chunks.push(chunk);
  44. }
  45. }
  46. return chunks;
  47. },
  48. getBaseStreams: function ChunkedStream_getBaseStreams() {
  49. return [this];
  50. },
  51. allChunksLoaded: function ChunkedStream_allChunksLoaded() {
  52. return this.numChunksLoaded === this.numChunks;
  53. },
  54. onReceiveData: function ChunkedStream_onReceiveData(begin, chunk) {
  55. var end = begin + chunk.byteLength;
  56. assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
  57. var length = this.bytes.length;
  58. assert(end % this.chunkSize === 0 || end === length, 'Bad end offset: ' + end);
  59. this.bytes.set(new Uint8Array(chunk), begin);
  60. var chunkSize = this.chunkSize;
  61. var beginChunk = Math.floor(begin / chunkSize);
  62. var endChunk = Math.floor((end - 1) / chunkSize) + 1;
  63. var curChunk;
  64. for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
  65. if (!this.loadedChunks[curChunk]) {
  66. this.loadedChunks[curChunk] = true;
  67. ++this.numChunksLoaded;
  68. }
  69. }
  70. },
  71. onReceiveProgressiveData: function ChunkedStream_onReceiveProgressiveData(data) {
  72. var position = this.progressiveDataLength;
  73. var beginChunk = Math.floor(position / this.chunkSize);
  74. this.bytes.set(new Uint8Array(data), position);
  75. position += data.byteLength;
  76. this.progressiveDataLength = position;
  77. var endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
  78. var curChunk;
  79. for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
  80. if (!this.loadedChunks[curChunk]) {
  81. this.loadedChunks[curChunk] = true;
  82. ++this.numChunksLoaded;
  83. }
  84. }
  85. },
  86. ensureByte: function ChunkedStream_ensureByte(pos) {
  87. var chunk = Math.floor(pos / this.chunkSize);
  88. if (chunk === this.lastSuccessfulEnsureByteChunk) {
  89. return;
  90. }
  91. if (!this.loadedChunks[chunk]) {
  92. throw new MissingDataException(pos, pos + 1);
  93. }
  94. this.lastSuccessfulEnsureByteChunk = chunk;
  95. },
  96. ensureRange: function ChunkedStream_ensureRange(begin, end) {
  97. if (begin >= end) {
  98. return;
  99. }
  100. if (end <= this.progressiveDataLength) {
  101. return;
  102. }
  103. var chunkSize = this.chunkSize;
  104. var beginChunk = Math.floor(begin / chunkSize);
  105. var endChunk = Math.floor((end - 1) / chunkSize) + 1;
  106. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  107. if (!this.loadedChunks[chunk]) {
  108. throw new MissingDataException(begin, end);
  109. }
  110. }
  111. },
  112. nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
  113. var chunk,
  114. numChunks = this.numChunks;
  115. for (var i = 0; i < numChunks; ++i) {
  116. chunk = (beginChunk + i) % numChunks;
  117. if (!this.loadedChunks[chunk]) {
  118. return chunk;
  119. }
  120. }
  121. return null;
  122. },
  123. hasChunk: function ChunkedStream_hasChunk(chunk) {
  124. return !!this.loadedChunks[chunk];
  125. },
  126. get length() {
  127. return this.end - this.start;
  128. },
  129. get isEmpty() {
  130. return this.length === 0;
  131. },
  132. getByte: function ChunkedStream_getByte() {
  133. var pos = this.pos;
  134. if (pos >= this.end) {
  135. return -1;
  136. }
  137. this.ensureByte(pos);
  138. return this.bytes[this.pos++];
  139. },
  140. getUint16: function ChunkedStream_getUint16() {
  141. var b0 = this.getByte();
  142. var b1 = this.getByte();
  143. if (b0 === -1 || b1 === -1) {
  144. return -1;
  145. }
  146. return (b0 << 8) + b1;
  147. },
  148. getInt32: function ChunkedStream_getInt32() {
  149. var b0 = this.getByte();
  150. var b1 = this.getByte();
  151. var b2 = this.getByte();
  152. var b3 = this.getByte();
  153. return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
  154. },
  155. getBytes: function ChunkedStream_getBytes(length) {
  156. var bytes = this.bytes;
  157. var pos = this.pos;
  158. var strEnd = this.end;
  159. if (!length) {
  160. this.ensureRange(pos, strEnd);
  161. return bytes.subarray(pos, strEnd);
  162. }
  163. var end = pos + length;
  164. if (end > strEnd) {
  165. end = strEnd;
  166. }
  167. this.ensureRange(pos, end);
  168. this.pos = end;
  169. return bytes.subarray(pos, end);
  170. },
  171. peekByte: function ChunkedStream_peekByte() {
  172. var peekedByte = this.getByte();
  173. this.pos--;
  174. return peekedByte;
  175. },
  176. peekBytes: function ChunkedStream_peekBytes(length) {
  177. var bytes = this.getBytes(length);
  178. this.pos -= bytes.length;
  179. return bytes;
  180. },
  181. getByteRange: function ChunkedStream_getBytes(begin, end) {
  182. this.ensureRange(begin, end);
  183. return this.bytes.subarray(begin, end);
  184. },
  185. skip: function ChunkedStream_skip(n) {
  186. if (!n) {
  187. n = 1;
  188. }
  189. this.pos += n;
  190. },
  191. reset: function ChunkedStream_reset() {
  192. this.pos = this.start;
  193. },
  194. moveStart: function ChunkedStream_moveStart() {
  195. this.start = this.pos;
  196. },
  197. makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
  198. this.ensureRange(start, start + length);
  199. function ChunkedStreamSubstream() {}
  200. ChunkedStreamSubstream.prototype = Object.create(this);
  201. ChunkedStreamSubstream.prototype.getMissingChunks = function () {
  202. var chunkSize = this.chunkSize;
  203. var beginChunk = Math.floor(this.start / chunkSize);
  204. var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
  205. var missingChunks = [];
  206. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  207. if (!this.loadedChunks[chunk]) {
  208. missingChunks.push(chunk);
  209. }
  210. }
  211. return missingChunks;
  212. };
  213. var subStream = new ChunkedStreamSubstream();
  214. subStream.pos = subStream.start = start;
  215. subStream.end = start + length || this.end;
  216. subStream.dict = dict;
  217. return subStream;
  218. }
  219. };
  220. return ChunkedStream;
  221. }();
  222. var ChunkedStreamManager = function ChunkedStreamManagerClosure() {
  223. function ChunkedStreamManager(pdfNetworkStream, args) {
  224. var chunkSize = args.rangeChunkSize;
  225. var length = args.length;
  226. this.stream = new ChunkedStream(length, chunkSize, this);
  227. this.length = length;
  228. this.chunkSize = chunkSize;
  229. this.pdfNetworkStream = pdfNetworkStream;
  230. this.url = args.url;
  231. this.disableAutoFetch = args.disableAutoFetch;
  232. this.msgHandler = args.msgHandler;
  233. this.currRequestId = 0;
  234. this.chunksNeededByRequest = Object.create(null);
  235. this.requestsByChunk = Object.create(null);
  236. this.promisesByRequest = Object.create(null);
  237. this.progressiveDataLength = 0;
  238. this.aborted = false;
  239. this._loadedStreamCapability = createPromiseCapability();
  240. }
  241. ChunkedStreamManager.prototype = {
  242. onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
  243. return this._loadedStreamCapability.promise;
  244. },
  245. sendRequest: function ChunkedStreamManager_sendRequest(begin, end) {
  246. var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
  247. if (!rangeReader.isStreamingSupported) {
  248. rangeReader.onProgress = this.onProgress.bind(this);
  249. }
  250. var chunks = [],
  251. loaded = 0;
  252. var manager = this;
  253. var promise = new Promise(function (resolve, reject) {
  254. var readChunk = function (chunk) {
  255. try {
  256. if (!chunk.done) {
  257. var data = chunk.value;
  258. chunks.push(data);
  259. loaded += arrayByteLength(data);
  260. if (rangeReader.isStreamingSupported) {
  261. manager.onProgress({ loaded: loaded });
  262. }
  263. rangeReader.read().then(readChunk, reject);
  264. return;
  265. }
  266. var chunkData = arraysToBytes(chunks);
  267. chunks = null;
  268. resolve(chunkData);
  269. } catch (e) {
  270. reject(e);
  271. }
  272. };
  273. rangeReader.read().then(readChunk, reject);
  274. });
  275. promise.then(function (data) {
  276. if (this.aborted) {
  277. return;
  278. }
  279. this.onReceiveData({
  280. chunk: data,
  281. begin: begin
  282. });
  283. }.bind(this));
  284. },
  285. requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
  286. var missingChunks = this.stream.getMissingChunks();
  287. this._requestChunks(missingChunks);
  288. return this._loadedStreamCapability.promise;
  289. },
  290. _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
  291. var requestId = this.currRequestId++;
  292. var i, ii;
  293. var chunksNeeded = Object.create(null);
  294. this.chunksNeededByRequest[requestId] = chunksNeeded;
  295. for (i = 0, ii = chunks.length; i < ii; i++) {
  296. if (!this.stream.hasChunk(chunks[i])) {
  297. chunksNeeded[chunks[i]] = true;
  298. }
  299. }
  300. if (isEmptyObj(chunksNeeded)) {
  301. return Promise.resolve();
  302. }
  303. var capability = createPromiseCapability();
  304. this.promisesByRequest[requestId] = capability;
  305. var chunksToRequest = [];
  306. for (var chunk in chunksNeeded) {
  307. chunk = chunk | 0;
  308. if (!(chunk in this.requestsByChunk)) {
  309. this.requestsByChunk[chunk] = [];
  310. chunksToRequest.push(chunk);
  311. }
  312. this.requestsByChunk[chunk].push(requestId);
  313. }
  314. if (!chunksToRequest.length) {
  315. return capability.promise;
  316. }
  317. var groupedChunksToRequest = this.groupChunks(chunksToRequest);
  318. for (i = 0; i < groupedChunksToRequest.length; ++i) {
  319. var groupedChunk = groupedChunksToRequest[i];
  320. var begin = groupedChunk.beginChunk * this.chunkSize;
  321. var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
  322. this.sendRequest(begin, end);
  323. }
  324. return capability.promise;
  325. },
  326. getStream: function ChunkedStreamManager_getStream() {
  327. return this.stream;
  328. },
  329. requestRange: function ChunkedStreamManager_requestRange(begin, end) {
  330. end = Math.min(end, this.length);
  331. var beginChunk = this.getBeginChunk(begin);
  332. var endChunk = this.getEndChunk(end);
  333. var chunks = [];
  334. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  335. chunks.push(chunk);
  336. }
  337. return this._requestChunks(chunks);
  338. },
  339. requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
  340. ranges = ranges || [];
  341. var chunksToRequest = [];
  342. for (var i = 0; i < ranges.length; i++) {
  343. var beginChunk = this.getBeginChunk(ranges[i].begin);
  344. var endChunk = this.getEndChunk(ranges[i].end);
  345. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  346. if (chunksToRequest.indexOf(chunk) < 0) {
  347. chunksToRequest.push(chunk);
  348. }
  349. }
  350. }
  351. chunksToRequest.sort(function (a, b) {
  352. return a - b;
  353. });
  354. return this._requestChunks(chunksToRequest);
  355. },
  356. groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
  357. var groupedChunks = [];
  358. var beginChunk = -1;
  359. var prevChunk = -1;
  360. for (var i = 0; i < chunks.length; ++i) {
  361. var chunk = chunks[i];
  362. if (beginChunk < 0) {
  363. beginChunk = chunk;
  364. }
  365. if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
  366. groupedChunks.push({
  367. beginChunk: beginChunk,
  368. endChunk: prevChunk + 1
  369. });
  370. beginChunk = chunk;
  371. }
  372. if (i + 1 === chunks.length) {
  373. groupedChunks.push({
  374. beginChunk: beginChunk,
  375. endChunk: chunk + 1
  376. });
  377. }
  378. prevChunk = chunk;
  379. }
  380. return groupedChunks;
  381. },
  382. onProgress: function ChunkedStreamManager_onProgress(args) {
  383. var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
  384. this.msgHandler.send('DocProgress', {
  385. loaded: bytesLoaded,
  386. total: this.length
  387. });
  388. },
  389. onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
  390. var chunk = args.chunk;
  391. var isProgressive = args.begin === undefined;
  392. var begin = isProgressive ? this.progressiveDataLength : args.begin;
  393. var end = begin + chunk.byteLength;
  394. var beginChunk = Math.floor(begin / this.chunkSize);
  395. var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
  396. if (isProgressive) {
  397. this.stream.onReceiveProgressiveData(chunk);
  398. this.progressiveDataLength = end;
  399. } else {
  400. this.stream.onReceiveData(begin, chunk);
  401. }
  402. if (this.stream.allChunksLoaded()) {
  403. this._loadedStreamCapability.resolve(this.stream);
  404. }
  405. var loadedRequests = [];
  406. var i, requestId;
  407. for (chunk = beginChunk; chunk < endChunk; ++chunk) {
  408. var requestIds = this.requestsByChunk[chunk] || [];
  409. delete this.requestsByChunk[chunk];
  410. for (i = 0; i < requestIds.length; ++i) {
  411. requestId = requestIds[i];
  412. var chunksNeeded = this.chunksNeededByRequest[requestId];
  413. if (chunk in chunksNeeded) {
  414. delete chunksNeeded[chunk];
  415. }
  416. if (!isEmptyObj(chunksNeeded)) {
  417. continue;
  418. }
  419. loadedRequests.push(requestId);
  420. }
  421. }
  422. if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
  423. var nextEmptyChunk;
  424. if (this.stream.numChunksLoaded === 1) {
  425. var lastChunk = this.stream.numChunks - 1;
  426. if (!this.stream.hasChunk(lastChunk)) {
  427. nextEmptyChunk = lastChunk;
  428. }
  429. } else {
  430. nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
  431. }
  432. if (isInt(nextEmptyChunk)) {
  433. this._requestChunks([nextEmptyChunk]);
  434. }
  435. }
  436. for (i = 0; i < loadedRequests.length; ++i) {
  437. requestId = loadedRequests[i];
  438. var capability = this.promisesByRequest[requestId];
  439. delete this.promisesByRequest[requestId];
  440. capability.resolve();
  441. }
  442. this.msgHandler.send('DocProgress', {
  443. loaded: this.stream.numChunksLoaded * this.chunkSize,
  444. total: this.length
  445. });
  446. },
  447. onError: function ChunkedStreamManager_onError(err) {
  448. this._loadedStreamCapability.reject(err);
  449. },
  450. getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
  451. var chunk = Math.floor(begin / this.chunkSize);
  452. return chunk;
  453. },
  454. getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
  455. var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
  456. return chunk;
  457. },
  458. abort: function ChunkedStreamManager_abort() {
  459. this.aborted = true;
  460. if (this.pdfNetworkStream) {
  461. this.pdfNetworkStream.cancelAllRequests('abort');
  462. }
  463. for (var requestId in this.promisesByRequest) {
  464. var capability = this.promisesByRequest[requestId];
  465. capability.reject(new Error('Request was aborted'));
  466. }
  467. }
  468. };
  469. return ChunkedStreamManager;
  470. }();
  471. exports.ChunkedStream = ChunkedStream;
  472. exports.ChunkedStreamManager = ChunkedStreamManager;