· 8 years ago · May 28, 2018, 12:46 AM
1/*
2 Zip.js
3 Copyright (c) 2013 Gildas Lormeau. All rights reserved.
4 Please read copyright notice, list of conditions and the disclaimer https://raw.githubusercontent.com/gildas-lormeau/zip.js/master/WebContent/zip.js
5 */
6if (!window.vkopt) window.vkopt={};
7(function(obj) {
8 "use strict";
9
10 var ERR_WRITE = "Error while writing zip file.";
11 var ERR_READ_DATA = "Error while reading file data.";
12 var ERR_DUPLICATED_NAME = "File already exists.";
13 var CHUNK_SIZE = 512 * 1024;
14
15 var appendABViewSupported;
16 try {
17 appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
18 } catch (e) {
19 }
20
21 function Crc32() {
22 this.crc = -1;
23 }
24 Crc32.prototype.append = function append(data) {
25 var crc = this.crc | 0, table = this.table;
26 for (var offset = 0, len = data.length | 0; offset < len; offset++)
27 crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
28 this.crc = crc;
29 };
30 Crc32.prototype.get = function get() {
31 return ~this.crc;
32 };
33 Crc32.prototype.table = (function() {
34 var i, j, t, table = []; // Uint32Array is actually slower than []
35 for (i = 0; i < 256; i++) {
36 t = i;
37 for (j = 0; j < 8; j++)
38 if (t & 1)
39 t = (t >>> 1) ^ 0xEDB88320;
40 else
41 t = t >>> 1;
42 table[i] = t;
43 }
44 return table;
45 })();
46
47 // "no-op" codec
48 function NOOP() {}
49 NOOP.prototype.append = function append(bytes, onprogress) {
50 return bytes;
51 };
52 NOOP.prototype.flush = function flush() {};
53
54 function getDataHelper(byteLength, bytes) {
55 var dataBuffer, dataArray;
56 dataBuffer = new ArrayBuffer(byteLength);
57 dataArray = new Uint8Array(dataBuffer);
58 if (bytes)
59 dataArray.set(bytes, 0);
60 return {
61 buffer : dataBuffer,
62 array : dataArray,
63 view : new DataView(dataBuffer)
64 };
65 }
66
67 function blobSlice(blob, index, length) {
68 if (index < 0 || length < 0 || index + length > blob.size)
69 throw new RangeError('offset:' + index + ', length:' + length + ', size:' + blob.size);
70 if (blob.slice)
71 return blob.slice(index, index + length);
72 else if (blob.webkitSlice)
73 return blob.webkitSlice(index, index + length);
74 else if (blob.mozSlice)
75 return blob.mozSlice(index, index + length);
76 else if (blob.msSlice)
77 return blob.msSlice(index, index + length);
78 }
79 // Readers
80 function Reader() {
81 }
82 function BlobReader(blob) {
83 var that = this;
84
85 function init(callback) {
86 that.size = blob.size;
87 callback();
88 }
89
90 function readUint8Array(index, length, callback, onerror) {
91 var reader = new FileReader();
92 reader.onload = function(e) {
93 callback(new Uint8Array(e.target.result));
94 };
95 reader.onerror = onerror;
96 try {
97 reader.readAsArrayBuffer(blobSlice(blob, index, length));
98 } catch (e) {
99 onerror(e);
100 }
101 }
102
103 that.size = 0;
104 that.init = init;
105 that.readUint8Array = readUint8Array;
106 }
107 BlobReader.prototype = new Reader();
108 BlobReader.prototype.constructor = BlobReader;
109
110 // Writers
111
112 function Writer() {
113 }
114 Writer.prototype.getData = function(callback) {
115 callback(this.data);
116 };
117
118 function BlobWriter(contentType) {
119 var blob, that = this;
120
121 function init(callback) {
122 blob = new Blob([], {
123 type : contentType
124 });
125 callback();
126 }
127
128 function writeUint8Array(array, callback) {
129 blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
130 type : contentType
131 });
132 callback();
133 }
134
135 function getData(callback) {
136 callback(blob);
137 }
138
139 that.init = init;
140 that.writeUint8Array = writeUint8Array;
141 that.getData = getData;
142 }
143 BlobWriter.prototype = new Writer();
144 BlobWriter.prototype.constructor = BlobWriter;
145
146 function FileWriter(fileEntry, contentType) {
147 var writer, that = this;
148
149 function init(callback, onerror) {
150 fileEntry.createWriter(function(fileWriter) {
151 writer = fileWriter;
152 callback();
153 }, onerror);
154 }
155
156 function writeUint8Array(array, callback, onerror) {
157 var blob = new Blob([ appendABViewSupported ? array : array.buffer ], {
158 type : contentType
159 });
160 writer.onwrite = function() {
161 writer.onwrite = null;
162 callback();
163 };
164 writer.onerror = onerror;
165 writer.write(blob);
166 }
167
168 function getData(callback) {
169 fileEntry.file(callback);
170 }
171
172 that.init = init;
173 that.writeUint8Array = writeUint8Array;
174 that.getData = getData;
175 }
176 FileWriter.prototype = new Writer();
177 FileWriter.prototype.constructor = FileWriter;
178
179 function MutableFileWriter(fileHandle, contentType) {
180 var lockedFile, that = this;
181
182 function init(callback) {
183 //lockedFile = fileHandle.open('readwrite');
184 callback();
185 }
186
187 function writeUint8Array(array, callback) {
188 var blob = new Blob([ appendABViewSupported ? array : array.buffer ], {
189 type : contentType
190 });
191 var lockedFile = fileHandle.open('readwrite');
192 var writing = lockedFile.append(blob)
193 writing.onsuccess = function(){
194 callback();
195 }
196 writing.onerror = function(){
197 console.log('Something goes wrong in the writing process: ' + this.error);
198 }
199 }
200
201 function getData(callback) {
202 fileHandle.getFile().onsuccess = function () {
203 callback(this.result);
204 }
205 }
206
207 that.init = init;
208 that.writeUint8Array = writeUint8Array;
209 that.getData = getData;
210 }
211 MutableFileWriter.prototype = new Writer();
212 MutableFileWriter.prototype.constructor = MutableFileWriter;
213
214 function launchProcess(process, reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror) {
215 var chunkIndex = 0, index, outputSize = 0,
216 crcInput = crcType === 'input',
217 crcOutput = crcType === 'output',
218 crc = new Crc32();
219 function step() {
220 var outputData;
221 index = chunkIndex * CHUNK_SIZE;
222 if (index < size)
223 reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
224 var outputData;
225 try {
226 outputData = process.append(inputData, function(loaded) {
227 if (onprogress)
228 onprogress(index + loaded, size);
229 });
230 } catch (e) {
231 onreaderror(e);
232 return;
233 }
234 if (outputData) {
235 outputSize += outputData.length;
236 writer.writeUint8Array(outputData, function() {
237 chunkIndex++;
238 setTimeout(step, 1);
239 }, onwriteerror);
240 if (crcOutput)
241 crc.append(outputData);
242 } else {
243 chunkIndex++;
244 setTimeout(step, 1);
245 }
246 if (crcInput)
247 crc.append(inputData);
248 if (onprogress)
249 onprogress(index, size);
250 }, onreaderror);
251 else {
252 try {
253 outputData = process.flush();
254 } catch (e) {
255 onreaderror(e);
256 return;
257 }
258 if (outputData) {
259 if (crcOutput)
260 crc.append(outputData);
261 outputSize += outputData.length;
262 writer.writeUint8Array(outputData, function() {
263 onend(outputSize, crc.get());
264 }, onwriteerror);
265 } else
266 onend(outputSize, crc.get());
267 }
268 }
269
270 step();
271 }
272
273 function copy(worker, sn, reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
274 var crcType = 'input';
275 launchProcess(new NOOP(), reader, writer, offset, size, crcType, onprogress, onend, onreaderror, onwriteerror);
276 }
277
278 // ZipWriter
279
280 function encodeUTF8(string) {
281 return unescape(encodeURIComponent(string));
282 }
283
284 function getBytes(str) {
285 var i, array = [];
286 for (i = 0; i < str.length; i++)
287 array.push(str.charCodeAt(i));
288 return array;
289 }
290
291 function createZipWriter(writer, callback, onerror) {
292 var files = {}, filenames = [], datalength = 0;
293 var deflateSN = 0;
294
295 function onwriteerror(err) {
296 onerror(err || ERR_WRITE);
297 }
298
299 function onreaderror(err) {
300 onerror(err || ERR_READ_DATA);
301 }
302
303 var zipWriter = {
304 add : function(name, reader, onend, onprogress, options) {
305 var header, filename, date;
306 var worker = this._worker;
307
308 function writeHeader(callback) {
309 var data;
310 date = options.lastModDate || new Date();
311 header = getDataHelper(26);
312 files[name] = {
313 headerArray : header.array,
314 directory : options.directory,
315 filename : filename,
316 offset : datalength,
317 comment : getBytes(encodeUTF8(options.comment || ""))
318 };
319 header.view.setUint32(0, 0x14000808);
320 if (options.version)
321 header.view.setUint8(0, options.version);
322 header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
323 header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
324 header.view.setUint16(22, filename.length, true);
325 data = getDataHelper(30 + filename.length);
326 data.view.setUint32(0, 0x504b0304);
327 data.array.set(header.array, 4);
328 data.array.set(filename, 30);
329 datalength += data.array.length;
330 writer.writeUint8Array(data.array, callback, onwriteerror);
331 }
332
333 function writeFooter(compressedLength, crc32) {
334 var footer = getDataHelper(16);
335 datalength += compressedLength || 0;
336 footer.view.setUint32(0, 0x504b0708);
337 if (typeof crc32 != "undefined") {
338 header.view.setUint32(10, crc32, true);
339 footer.view.setUint32(4, crc32, true);
340 }
341 if (reader) {
342 footer.view.setUint32(8, compressedLength, true);
343 header.view.setUint32(14, compressedLength, true);
344 footer.view.setUint32(12, reader.size, true);
345 header.view.setUint32(18, reader.size, true);
346 }
347 writer.writeUint8Array(footer.array, function() {
348 datalength += 16;
349 onend();
350 }, onwriteerror);
351 }
352
353 function writeFile() {
354 options = options || {};
355 name = name.trim();
356 if (options.directory && name.charAt(name.length - 1) != "/")
357 name += "/";
358 if (files.hasOwnProperty(name)) {
359 onerror(ERR_DUPLICATED_NAME);
360 return;
361 }
362 filename = getBytes(encodeUTF8(name));
363 filenames.push(name);
364 writeHeader(function() {
365 if (reader)
366 copy(worker, deflateSN++, reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
367 else
368 writeFooter();
369 }, onwriteerror);
370 }
371
372 if (reader)
373 reader.init(writeFile, onreaderror);
374 else
375 writeFile();
376 },
377 close : function(callback) {
378 if (this._worker) {
379 this._worker.terminate();
380 this._worker = null;
381 }
382
383 var data, length = 0, index = 0, indexFilename, file;
384 for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
385 file = files[filenames[indexFilename]];
386 length += 46 + file.filename.length + file.comment.length;
387 }
388 data = getDataHelper(length + 22);
389 for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
390 file = files[filenames[indexFilename]];
391 data.view.setUint32(index, 0x504b0102);
392 data.view.setUint16(index + 4, 0x1400);
393 data.array.set(file.headerArray, index + 6);
394 data.view.setUint16(index + 32, file.comment.length, true);
395 if (file.directory)
396 data.view.setUint8(index + 38, 0x10);
397 data.view.setUint32(index + 42, file.offset, true);
398 data.array.set(file.filename, index + 46);
399 data.array.set(file.comment, index + 46 + file.filename.length);
400 index += 46 + file.filename.length + file.comment.length;
401 }
402 data.view.setUint32(index, 0x504b0506);
403 data.view.setUint16(index + 8, filenames.length, true);
404 data.view.setUint16(index + 10, filenames.length, true);
405 data.view.setUint32(index + 12, length, true);
406 data.view.setUint32(index + 16, datalength, true);
407 writer.writeUint8Array(data.array, function() {
408 writer.getData(callback);
409 }, onwriteerror);
410 },
411 _worker: null
412 };
413
414 callback(zipWriter);
415 }
416
417 function resolveURLs(urls) {
418 var a = document.createElement('a');
419 return urls.map(function(url) {
420 a.href = url;
421 return a.href;
422 });
423 }
424
425 function onerror_default(error) {
426 console.error(error);
427 }
428 obj.zipjs = {
429 Writer : Writer,
430 BlobWriter : BlobWriter,
431 FileWriter : FileWriter,
432 MutableFileWriter: MutableFileWriter,
433 BlobReader : BlobReader,
434 createWriter : function(writer, callback, onerror) {
435 onerror = onerror || onerror_default;
436 writer.init(function() {
437 createZipWriter(writer, callback, onerror);
438 }, onerror);
439 }
440 };
441
442})(vkopt);
443
444
445
446
447(function(obj) {
448
449 var requestFileSystem = obj.webkitRequestFileSystem || obj.mozRequestFileSystem || obj.requestFileSystem;
450 var url_create = (window.URL || window.webkitURL || window.mozURL || window).createObjectURL;
451 var url_revoke = (window.URL || window.webkitURL || window.mozURL || window).revokeObjectURL;
452
453 function onerror(message) {
454 alert(message);
455 }
456
457
458 var model = function(file_name){
459 var
460 zipFileEntry, zipWriter, writer,
461 creationMethod = 'Blob',
462 download_url = null;
463
464 if (typeof requestFileSystem != "undefined")
465 creationMethod = 'File';
466 else
467 if (typeof IDBMutableFile != "undefined")
468 creationMethod = 'MutableFile';
469
470 function createTempFile(file_name, callback) {
471 var tmpFilename = file_name || "tmp.zip";
472 requestFileSystem(TEMPORARY, 4 * 1024 * 1024 * 1024, function(filesystem) {
473 function create() {
474 filesystem.root.getFile(tmpFilename, {
475 create : true
476 }, function(zipFile) {
477 callback(zipFile);
478 });
479 }
480
481 filesystem.root.getFile(tmpFilename, null, function(entry) {
482 entry.remove(create, create);
483 }, create);
484 });
485 }
486
487 function createMutableFile(file_name, contentType, cb){
488 var idb = indexedDB.open("vkoptFileStorageDB",{version: 1, storage:'persistent'});
489 idb.onsuccess = function(){
490 var db = this.result;
491 var buildHandle = db.createMutableFile(file_name, contentType);
492 buildHandle.onsuccess = function(){
493 cb && cb(this.result); // fileHandle
494 };
495 };
496 }
497
498 //function onprogress(current, total) {}
499 var add_locked = false;
500 var addFile = function (name, data, ondone, onprogress){
501 if (add_locked){
502 var args = Array.prototype.slice.call(arguments);
503 setTimeout(function(){addFile.apply(this, args)}, Math.round(Math.random()*100)+10);
504 return;
505 }
506 add_locked = true;
507 var add = function(){
508 zipWriter.add(name, new vkopt.zipjs.BlobReader(data), function() {
509 add_locked = false;
510 ondone();
511 }, onprogress);
512 }
513 function createZipWriter() {
514 vkopt.zipjs.createWriter(writer, function(writer) {
515 zipWriter = writer;
516 add();
517 }, onerror);
518 }
519
520 if (zipWriter)
521 add();
522 else switch(creationMethod){
523 case 'Blob': {
524 writer = new vkopt.zipjs.BlobWriter();
525 createZipWriter();
526 break;
527 };
528 case 'File': {
529 createTempFile(file_name, function(fileEntry) {
530 zipFileEntry = fileEntry;
531 writer = new vkopt.zipjs.FileWriter(zipFileEntry);
532 createZipWriter();
533 });
534 break;
535 };
536 case 'MutableFile': {
537 createMutableFile(file_name, "application/zip", function(fileHandle) {
538 writer = new vkopt.zipjs.MutableFileWriter(fileHandle, "application/zip");
539 createZipWriter();
540 })
541 break;
542 }
543 }
544 };
545 var addFiles = function (files, onadd, onprogress, onend) {
546 var addIndex = 0;
547
548 function nextFile() {
549 var file = files[addIndex];
550 onadd(file);
551 addFile(file.name, file, function(){
552 addIndex++;
553 if (addIndex < files.length)
554 nextFile();
555 else
556 onend();
557 }, onprogress)
558 }
559
560 nextFile();
561 };
562 var getBlobURL = function(callback) {
563 zipWriter.close(function(blob) {
564 var blobURL = creationMethod == "File" ? zipFileEntry.toURL() : url_create(blob);
565 callback(blobURL);
566 zipWriter = null;
567 });
568 };
569 var getBlob = function(callback) {
570 zipWriter.close(callback);
571 };
572 return {
573 addFile: addFile,
574 addFiles : addFiles,
575 getBlobURL : getBlobURL,
576 getBlob : getBlob,
577 download: function(){
578 var dl = function(){
579 if (creationMethod == 'MutableFile'){
580 var fr = document.createElement('iframe');
581 fr.frameBorder = 0;
582 fr.width = 1;
583 fr.height = 1;
584 document.body.appendChild(fr);
585 var doc = fr.contentDocument;
586 var form = doc.createElement('form');
587 form.action = download_url;
588 doc.body.appendChild(form);
589 form.submit();
590 setTimeout(function(){
591 fr.parentNode.removeChild(fr);
592 }, 100);
593 } else {
594 var dlnk = document.createElement('a');
595 dlnk.href = download_url;
596 dlnk.download = file_name;
597 (window.utilsNode || document.body).appendChild(dlnk)
598 dlnk.click();
599 vkopt.log('Download zip file: ', file_name);
600 setTimeout(function(){
601 re(dlnk);
602 //url_revoke(dlnk);
603 },200);
604 }
605 }
606 if (typeof navigator.msSaveBlob == "function") {
607 getBlob(function(blob) {
608 navigator.msSaveBlob(blob, file_name);
609 });
610 } else {
611 if (!download_url){
612 getBlobURL(function(blobURL){
613 download_url = blobURL;
614 vkopt.log(file_name + ' download url: ' + download_url);
615 dl();
616 })
617 } else dl();
618 }
619 }
620 };
621 };
622 obj.zip = model;
623})(vkopt);
624
625
626
627var download_photo_list = function(list, name, on_progress){
628 var modes = [
629 'direct download',
630 'direct download',
631 'download with domain replace',
632 'download with domain replace',
633 'download through ext bg'
634 ];
635 var max_threads = 5;
636 var max_zip_size = 400 * 1024 * 1024; // 400Mb
637 var cur = 0;
638 var dl_cnt = 0;
639 var zip_cnt = 0;
640 var size_sum = 0;
641 var url, file_name;
642
643 //var phz = vkopt.zip(name || 'photos.zip');
644
645 var xhrs = [];
646 for (var i = 0; i < max_threads; i++){
647 var xhr = new XMLHttpRequest();
648 xhr.responseType = 'blob';
649 xhrs.push(xhr);
650 }
651
652 var make_next_zip = function(cb){
653 var zipname = (name || 'photos.zip').replace(/(\.zip)?$/i, '_p' + zip_cnt + '.zip');
654 size_sum = 0;
655 phz = vkopt.zip(zipname);
656 phz.addFile('0_info.txt', new Blob(['Archive #'+zip_cnt+'\r\nOriginal file name: ' + zipname + '\r\nDownloaded from vk.com by vkopt extension ( http://vkopt.net/ )'],{type:'plain/text'}), function(){
657 cb && cb();
658 });
659 zip_cnt++;
660 }
661
662 var dl = function(idx, mode, callback){
663 var error_fired = false;
664 var xhr = xhrs.pop();
665 if (!xhr) {
666 vkopt.log('Get existing XHR object error. Create new');
667 xhr = new XMLHttpRequest();
668 xhr.responseType = 'blob';
669 }
670 mode = mode || 0;
671 url = list[idx];
672 file_name = url.split('?')[0].split('/').pop();
673
674 var onload = function(){
675 if (xhr.status == 200){
676 xhrs.push(xhr);
677 callback(null, xhr.response, idx)
678 } else
679 onerror();
680
681 };
682 var onerror = function(){
683 if (error_fired) return;
684 error_fired = true;
685 vkopt.log('Failed ' + modes[mode] + ': ' + file_name);
686 xhrs.push(xhr);
687 dl(idx, mode+1, callback)
688 };
689 xhr.onload = onload;
690 xhr.onerror = onerror;
691 //xhr.onprogress = function(e){console.log(e.loaded, e.total)}
692 switch(mode){
693 case 0:
694 case 1: //retry
695 {
696 xhr.open('GET', url, true);
697 xhr.send();
698 break;
699 };
700 case 2:
701 case 3: // retry
702 {
703 if (/pp\.userapi\.com/.test(url)){ // пробудем подÑтавить в ÑÑылку домен раздачи, на котором еÑть CORS хидеры.
704 xhrs.push(xhr);
705 dl(idx, mode+1, callback);
706 } else if (/[^\/\/]+\.userapi\.com/.test(url)){
707 url = url.replace(/:\/\/[^\/\/]+\.userapi\.com/,'://pp.userapi.com')
708 xhr.open('GET', url, true);
709 xhr.send();
710 } else
711 onerror();
712 break;
713 };
714 case 4: {
715 vk_aj.ajax({url: url, method: 'GET', responseType: 'arraybuffer'}, function (response) {
716 if (response && response.status == 200){
717 xhrs.push(xhr);
718 callback(null, new Blob([new Uint8Array(response.raw)],{type:'image/jpeg'}), idx);
719 } else
720 onerror();
721 });
722 break;
723 };
724 default: {
725 xhrs.push(xhr);
726 callback('Failed while downloading "'+file_name+'"', null, idx);
727 }
728 }
729 }
730 if (list.length < 1){
731 alert('Photos not found');
732 return;
733 }
734 var thread_count = 0;
735 var run_threads = function(){
736 make_next_zip(function(){
737 for (var i = 0; i < max_threads; i++)
738 (cur < list.length) && step();
739 });
740 }
741 var step = function(){
742 thread_count++;
743 dl(cur++, 0,function(err, result, num){
744 size_sum += result.size || result.length || 0;
745 phz.addFile(num + '_' + file_name, result, function(){
746 dl_cnt++;
747 thread_count--;
748 on_progress(dl_cnt, list.length);
749 if (size_sum >= max_zip_size) {
750 check_ready();
751 } else if (cur < list.length){
752 step();
753 } else {
754 check_ready();
755 }
756 });
757 });
758 }
759 var check_ready = function(){
760 if (thread_count == 0){
761 phz.download();
762 if (cur < list.length){
763 run_threads();
764 }
765 } else
766 vkopt.log('Wait download ending...');
767 }
768
769 run_threads();
770}
771
772
773function run(){
774 var ainfo = location.href.match(/album(-?\d+)_(\d+)/);
775 var p = geByClass1('photos_album_intro_info');
776 var wrp = se('<div id="vk_ph_progress" style="padding:10px 40px"></div>');
777 if (p)
778 p.appendChild(wrp);
779 else
780 return;
781 vkApis.photos_hd(ainfo[1], ainfo[2], function(r){
782 vkopt.log('Links collected. Downloading...');
783 download_photo_list(r, ainfo[0]+'.zip', function(progr, total){
784 wrp.innerHTML = vkProgressBar(progr, total, 700, 'Downloading and Archiving %');
785 });
786 }, function(progr, total){
787 wrp.innerHTML = vkProgressBar(progr, total, 700, 'Collecting links %');
788 });
789}
790//*
791if (navigator.storage && navigator.storage.persist)
792 navigator.storage.persist().then(function(granted){
793 run();
794 });
795//*/