2
0

chunked_stream.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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 _this = this;
  247. var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
  248. if (!rangeReader.isStreamingSupported) {
  249. rangeReader.onProgress = this.onProgress.bind(this);
  250. }
  251. var chunks = [],
  252. loaded = 0;
  253. var manager = this;
  254. var promise = new Promise(function (resolve, reject) {
  255. var readChunk = function readChunk(chunk) {
  256. try {
  257. if (!chunk.done) {
  258. var data = chunk.value;
  259. chunks.push(data);
  260. loaded += arrayByteLength(data);
  261. if (rangeReader.isStreamingSupported) {
  262. manager.onProgress({ loaded: loaded });
  263. }
  264. rangeReader.read().then(readChunk, reject);
  265. return;
  266. }
  267. var chunkData = arraysToBytes(chunks);
  268. chunks = null;
  269. resolve(chunkData);
  270. } catch (e) {
  271. reject(e);
  272. }
  273. };
  274. rangeReader.read().then(readChunk, reject);
  275. });
  276. promise.then(function (data) {
  277. if (_this.aborted) {
  278. return;
  279. }
  280. _this.onReceiveData({
  281. chunk: data,
  282. begin: begin
  283. });
  284. });
  285. },
  286. requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
  287. var missingChunks = this.stream.getMissingChunks();
  288. this._requestChunks(missingChunks);
  289. return this._loadedStreamCapability.promise;
  290. },
  291. _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
  292. var requestId = this.currRequestId++;
  293. var i, ii;
  294. var chunksNeeded = Object.create(null);
  295. this.chunksNeededByRequest[requestId] = chunksNeeded;
  296. for (i = 0, ii = chunks.length; i < ii; i++) {
  297. if (!this.stream.hasChunk(chunks[i])) {
  298. chunksNeeded[chunks[i]] = true;
  299. }
  300. }
  301. if (isEmptyObj(chunksNeeded)) {
  302. return Promise.resolve();
  303. }
  304. var capability = createPromiseCapability();
  305. this.promisesByRequest[requestId] = capability;
  306. var chunksToRequest = [];
  307. for (var chunk in chunksNeeded) {
  308. chunk = chunk | 0;
  309. if (!(chunk in this.requestsByChunk)) {
  310. this.requestsByChunk[chunk] = [];
  311. chunksToRequest.push(chunk);
  312. }
  313. this.requestsByChunk[chunk].push(requestId);
  314. }
  315. if (!chunksToRequest.length) {
  316. return capability.promise;
  317. }
  318. var groupedChunksToRequest = this.groupChunks(chunksToRequest);
  319. for (i = 0; i < groupedChunksToRequest.length; ++i) {
  320. var groupedChunk = groupedChunksToRequest[i];
  321. var begin = groupedChunk.beginChunk * this.chunkSize;
  322. var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
  323. this.sendRequest(begin, end);
  324. }
  325. return capability.promise;
  326. },
  327. getStream: function ChunkedStreamManager_getStream() {
  328. return this.stream;
  329. },
  330. requestRange: function ChunkedStreamManager_requestRange(begin, end) {
  331. end = Math.min(end, this.length);
  332. var beginChunk = this.getBeginChunk(begin);
  333. var endChunk = this.getEndChunk(end);
  334. var chunks = [];
  335. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  336. chunks.push(chunk);
  337. }
  338. return this._requestChunks(chunks);
  339. },
  340. requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
  341. ranges = ranges || [];
  342. var chunksToRequest = [];
  343. for (var i = 0; i < ranges.length; i++) {
  344. var beginChunk = this.getBeginChunk(ranges[i].begin);
  345. var endChunk = this.getEndChunk(ranges[i].end);
  346. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  347. if (chunksToRequest.indexOf(chunk) < 0) {
  348. chunksToRequest.push(chunk);
  349. }
  350. }
  351. }
  352. chunksToRequest.sort(function (a, b) {
  353. return a - b;
  354. });
  355. return this._requestChunks(chunksToRequest);
  356. },
  357. groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
  358. var groupedChunks = [];
  359. var beginChunk = -1;
  360. var prevChunk = -1;
  361. for (var i = 0; i < chunks.length; ++i) {
  362. var chunk = chunks[i];
  363. if (beginChunk < 0) {
  364. beginChunk = chunk;
  365. }
  366. if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
  367. groupedChunks.push({
  368. beginChunk: beginChunk,
  369. endChunk: prevChunk + 1
  370. });
  371. beginChunk = chunk;
  372. }
  373. if (i + 1 === chunks.length) {
  374. groupedChunks.push({
  375. beginChunk: beginChunk,
  376. endChunk: chunk + 1
  377. });
  378. }
  379. prevChunk = chunk;
  380. }
  381. return groupedChunks;
  382. },
  383. onProgress: function ChunkedStreamManager_onProgress(args) {
  384. var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
  385. this.msgHandler.send('DocProgress', {
  386. loaded: bytesLoaded,
  387. total: this.length
  388. });
  389. },
  390. onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
  391. var chunk = args.chunk;
  392. var isProgressive = args.begin === undefined;
  393. var begin = isProgressive ? this.progressiveDataLength : args.begin;
  394. var end = begin + chunk.byteLength;
  395. var beginChunk = Math.floor(begin / this.chunkSize);
  396. var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
  397. if (isProgressive) {
  398. this.stream.onReceiveProgressiveData(chunk);
  399. this.progressiveDataLength = end;
  400. } else {
  401. this.stream.onReceiveData(begin, chunk);
  402. }
  403. if (this.stream.allChunksLoaded()) {
  404. this._loadedStreamCapability.resolve(this.stream);
  405. }
  406. var loadedRequests = [];
  407. var i, requestId;
  408. for (chunk = beginChunk; chunk < endChunk; ++chunk) {
  409. var requestIds = this.requestsByChunk[chunk] || [];
  410. delete this.requestsByChunk[chunk];
  411. for (i = 0; i < requestIds.length; ++i) {
  412. requestId = requestIds[i];
  413. var chunksNeeded = this.chunksNeededByRequest[requestId];
  414. if (chunk in chunksNeeded) {
  415. delete chunksNeeded[chunk];
  416. }
  417. if (!isEmptyObj(chunksNeeded)) {
  418. continue;
  419. }
  420. loadedRequests.push(requestId);
  421. }
  422. }
  423. if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
  424. var nextEmptyChunk;
  425. if (this.stream.numChunksLoaded === 1) {
  426. var lastChunk = this.stream.numChunks - 1;
  427. if (!this.stream.hasChunk(lastChunk)) {
  428. nextEmptyChunk = lastChunk;
  429. }
  430. } else {
  431. nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
  432. }
  433. if (isInt(nextEmptyChunk)) {
  434. this._requestChunks([nextEmptyChunk]);
  435. }
  436. }
  437. for (i = 0; i < loadedRequests.length; ++i) {
  438. requestId = loadedRequests[i];
  439. var capability = this.promisesByRequest[requestId];
  440. delete this.promisesByRequest[requestId];
  441. capability.resolve();
  442. }
  443. this.msgHandler.send('DocProgress', {
  444. loaded: this.stream.numChunksLoaded * this.chunkSize,
  445. total: this.length
  446. });
  447. },
  448. onError: function ChunkedStreamManager_onError(err) {
  449. this._loadedStreamCapability.reject(err);
  450. },
  451. getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
  452. var chunk = Math.floor(begin / this.chunkSize);
  453. return chunk;
  454. },
  455. getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
  456. var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
  457. return chunk;
  458. },
  459. abort: function ChunkedStreamManager_abort() {
  460. this.aborted = true;
  461. if (this.pdfNetworkStream) {
  462. this.pdfNetworkStream.cancelAllRequests('abort');
  463. }
  464. for (var requestId in this.promisesByRequest) {
  465. var capability = this.promisesByRequest[requestId];
  466. capability.reject(new Error('Request was aborted'));
  467. }
  468. }
  469. };
  470. return ChunkedStreamManager;
  471. }();
  472. exports.ChunkedStream = ChunkedStream;
  473. exports.ChunkedStreamManager = ChunkedStreamManager;