chunked_stream.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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, numChunks = this.numChunks;
  114. for (var i = 0; i < numChunks; ++i) {
  115. chunk = (beginChunk + i) % numChunks;
  116. if (!this.loadedChunks[chunk]) {
  117. return chunk;
  118. }
  119. }
  120. return null;
  121. },
  122. hasChunk: function ChunkedStream_hasChunk(chunk) {
  123. return !!this.loadedChunks[chunk];
  124. },
  125. get length() {
  126. return this.end - this.start;
  127. },
  128. get isEmpty() {
  129. return this.length === 0;
  130. },
  131. getByte: function ChunkedStream_getByte() {
  132. var pos = this.pos;
  133. if (pos >= this.end) {
  134. return -1;
  135. }
  136. this.ensureByte(pos);
  137. return this.bytes[this.pos++];
  138. },
  139. getUint16: function ChunkedStream_getUint16() {
  140. var b0 = this.getByte();
  141. var b1 = this.getByte();
  142. if (b0 === -1 || b1 === -1) {
  143. return -1;
  144. }
  145. return (b0 << 8) + b1;
  146. },
  147. getInt32: function ChunkedStream_getInt32() {
  148. var b0 = this.getByte();
  149. var b1 = this.getByte();
  150. var b2 = this.getByte();
  151. var b3 = this.getByte();
  152. return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
  153. },
  154. getBytes: function ChunkedStream_getBytes(length) {
  155. var bytes = this.bytes;
  156. var pos = this.pos;
  157. var strEnd = this.end;
  158. if (!length) {
  159. this.ensureRange(pos, strEnd);
  160. return bytes.subarray(pos, strEnd);
  161. }
  162. var end = pos + length;
  163. if (end > strEnd) {
  164. end = strEnd;
  165. }
  166. this.ensureRange(pos, end);
  167. this.pos = end;
  168. return bytes.subarray(pos, end);
  169. },
  170. peekByte: function ChunkedStream_peekByte() {
  171. var peekedByte = this.getByte();
  172. this.pos--;
  173. return peekedByte;
  174. },
  175. peekBytes: function ChunkedStream_peekBytes(length) {
  176. var bytes = this.getBytes(length);
  177. this.pos -= bytes.length;
  178. return bytes;
  179. },
  180. getByteRange: function ChunkedStream_getBytes(begin, end) {
  181. this.ensureRange(begin, end);
  182. return this.bytes.subarray(begin, end);
  183. },
  184. skip: function ChunkedStream_skip(n) {
  185. if (!n) {
  186. n = 1;
  187. }
  188. this.pos += n;
  189. },
  190. reset: function ChunkedStream_reset() {
  191. this.pos = this.start;
  192. },
  193. moveStart: function ChunkedStream_moveStart() {
  194. this.start = this.pos;
  195. },
  196. makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
  197. this.ensureRange(start, start + length);
  198. function ChunkedStreamSubstream() {
  199. }
  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 = [], loaded = 0;
  251. var manager = this;
  252. var promise = new Promise(function (resolve, reject) {
  253. var readChunk = function (chunk) {
  254. try {
  255. if (!chunk.done) {
  256. var data = chunk.value;
  257. chunks.push(data);
  258. loaded += arrayByteLength(data);
  259. if (rangeReader.isStreamingSupported) {
  260. manager.onProgress({ loaded: loaded });
  261. }
  262. rangeReader.read().then(readChunk, reject);
  263. return;
  264. }
  265. var chunkData = arraysToBytes(chunks);
  266. chunks = null;
  267. resolve(chunkData);
  268. } catch (e) {
  269. reject(e);
  270. }
  271. };
  272. rangeReader.read().then(readChunk, reject);
  273. });
  274. promise.then(function (data) {
  275. if (this.aborted) {
  276. return;
  277. }
  278. this.onReceiveData({
  279. chunk: data,
  280. begin: begin
  281. });
  282. }.bind(this));
  283. },
  284. requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
  285. var missingChunks = this.stream.getMissingChunks();
  286. this._requestChunks(missingChunks);
  287. return this._loadedStreamCapability.promise;
  288. },
  289. _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
  290. var requestId = this.currRequestId++;
  291. var i, ii;
  292. var chunksNeeded = Object.create(null);
  293. this.chunksNeededByRequest[requestId] = chunksNeeded;
  294. for (i = 0, ii = chunks.length; i < ii; i++) {
  295. if (!this.stream.hasChunk(chunks[i])) {
  296. chunksNeeded[chunks[i]] = true;
  297. }
  298. }
  299. if (isEmptyObj(chunksNeeded)) {
  300. return Promise.resolve();
  301. }
  302. var capability = createPromiseCapability();
  303. this.promisesByRequest[requestId] = capability;
  304. var chunksToRequest = [];
  305. for (var chunk in chunksNeeded) {
  306. chunk = chunk | 0;
  307. if (!(chunk in this.requestsByChunk)) {
  308. this.requestsByChunk[chunk] = [];
  309. chunksToRequest.push(chunk);
  310. }
  311. this.requestsByChunk[chunk].push(requestId);
  312. }
  313. if (!chunksToRequest.length) {
  314. return capability.promise;
  315. }
  316. var groupedChunksToRequest = this.groupChunks(chunksToRequest);
  317. for (i = 0; i < groupedChunksToRequest.length; ++i) {
  318. var groupedChunk = groupedChunksToRequest[i];
  319. var begin = groupedChunk.beginChunk * this.chunkSize;
  320. var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
  321. this.sendRequest(begin, end);
  322. }
  323. return capability.promise;
  324. },
  325. getStream: function ChunkedStreamManager_getStream() {
  326. return this.stream;
  327. },
  328. requestRange: function ChunkedStreamManager_requestRange(begin, end) {
  329. end = Math.min(end, this.length);
  330. var beginChunk = this.getBeginChunk(begin);
  331. var endChunk = this.getEndChunk(end);
  332. var chunks = [];
  333. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  334. chunks.push(chunk);
  335. }
  336. return this._requestChunks(chunks);
  337. },
  338. requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
  339. ranges = ranges || [];
  340. var chunksToRequest = [];
  341. for (var i = 0; i < ranges.length; i++) {
  342. var beginChunk = this.getBeginChunk(ranges[i].begin);
  343. var endChunk = this.getEndChunk(ranges[i].end);
  344. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  345. if (chunksToRequest.indexOf(chunk) < 0) {
  346. chunksToRequest.push(chunk);
  347. }
  348. }
  349. }
  350. chunksToRequest.sort(function (a, b) {
  351. return a - b;
  352. });
  353. return this._requestChunks(chunksToRequest);
  354. },
  355. groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
  356. var groupedChunks = [];
  357. var beginChunk = -1;
  358. var prevChunk = -1;
  359. for (var i = 0; i < chunks.length; ++i) {
  360. var chunk = chunks[i];
  361. if (beginChunk < 0) {
  362. beginChunk = chunk;
  363. }
  364. if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
  365. groupedChunks.push({
  366. beginChunk: beginChunk,
  367. endChunk: prevChunk + 1
  368. });
  369. beginChunk = chunk;
  370. }
  371. if (i + 1 === chunks.length) {
  372. groupedChunks.push({
  373. beginChunk: beginChunk,
  374. endChunk: chunk + 1
  375. });
  376. }
  377. prevChunk = chunk;
  378. }
  379. return groupedChunks;
  380. },
  381. onProgress: function ChunkedStreamManager_onProgress(args) {
  382. var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
  383. this.msgHandler.send('DocProgress', {
  384. loaded: bytesLoaded,
  385. total: this.length
  386. });
  387. },
  388. onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
  389. var chunk = args.chunk;
  390. var isProgressive = args.begin === undefined;
  391. var begin = isProgressive ? this.progressiveDataLength : args.begin;
  392. var end = begin + chunk.byteLength;
  393. var beginChunk = Math.floor(begin / this.chunkSize);
  394. var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
  395. if (isProgressive) {
  396. this.stream.onReceiveProgressiveData(chunk);
  397. this.progressiveDataLength = end;
  398. } else {
  399. this.stream.onReceiveData(begin, chunk);
  400. }
  401. if (this.stream.allChunksLoaded()) {
  402. this._loadedStreamCapability.resolve(this.stream);
  403. }
  404. var loadedRequests = [];
  405. var i, requestId;
  406. for (chunk = beginChunk; chunk < endChunk; ++chunk) {
  407. var requestIds = this.requestsByChunk[chunk] || [];
  408. delete this.requestsByChunk[chunk];
  409. for (i = 0; i < requestIds.length; ++i) {
  410. requestId = requestIds[i];
  411. var chunksNeeded = this.chunksNeededByRequest[requestId];
  412. if (chunk in chunksNeeded) {
  413. delete chunksNeeded[chunk];
  414. }
  415. if (!isEmptyObj(chunksNeeded)) {
  416. continue;
  417. }
  418. loadedRequests.push(requestId);
  419. }
  420. }
  421. if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
  422. var nextEmptyChunk;
  423. if (this.stream.numChunksLoaded === 1) {
  424. var lastChunk = this.stream.numChunks - 1;
  425. if (!this.stream.hasChunk(lastChunk)) {
  426. nextEmptyChunk = lastChunk;
  427. }
  428. } else {
  429. nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
  430. }
  431. if (isInt(nextEmptyChunk)) {
  432. this._requestChunks([nextEmptyChunk]);
  433. }
  434. }
  435. for (i = 0; i < loadedRequests.length; ++i) {
  436. requestId = loadedRequests[i];
  437. var capability = this.promisesByRequest[requestId];
  438. delete this.promisesByRequest[requestId];
  439. capability.resolve();
  440. }
  441. this.msgHandler.send('DocProgress', {
  442. loaded: this.stream.numChunksLoaded * this.chunkSize,
  443. total: this.length
  444. });
  445. },
  446. onError: function ChunkedStreamManager_onError(err) {
  447. this._loadedStreamCapability.reject(err);
  448. },
  449. getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
  450. var chunk = Math.floor(begin / this.chunkSize);
  451. return chunk;
  452. },
  453. getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
  454. var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
  455. return chunk;
  456. },
  457. abort: function ChunkedStreamManager_abort() {
  458. this.aborted = true;
  459. if (this.pdfNetworkStream) {
  460. this.pdfNetworkStream.cancelAllRequests('abort');
  461. }
  462. for (var requestId in this.promisesByRequest) {
  463. var capability = this.promisesByRequest[requestId];
  464. capability.reject(new Error('Request was aborted'));
  465. }
  466. }
  467. };
  468. return ChunkedStreamManager;
  469. }();
  470. exports.ChunkedStream = ChunkedStream;
  471. exports.ChunkedStreamManager = ChunkedStreamManager;