chunked_stream.js 16 KB

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