7

Web サイトから複数のファイル (画像やビデオ) をダウンロードする必要がある Chrome 拡張機能を作成しています。これらのファイルは巨大なサイズになる可能性があるため、ダウンロードの進行状況をユーザーに表示したいと考えています。いくつかの調査の結果、現在考えられる解決策は次のとおりであることがわかりました。

  1. XMLHttpRequests ですべてのファイルをダウンロードします。
  2. ダウンロードしたら、すべてのファイルを JavaScript ライブラリ (例: JSZip.js、zip.js) で 1 つのアーカイブに圧縮します。
  3. 名前を付けて保存ダイアログで zip を保存するようにユーザーに促します。

パッセージ 2) で行き詰まっています。ダウンロードしたファイルを圧縮するにはどうすればよいですか?

理解するために、コードサンプルを次に示します。

var fileURLs = ['http://www.test.com/img.jpg',...];
var zip = new JSZip();

var count = 0;
for (var i = 0; i < fileURLs.length; i++){
    var xhr = new XMLHttpRequest();
    xhr.onprogress = calculateAndUpdateProgress;
    xhr.open('GET', fileURLs[i], true);
    xhr.responseType = "blob";
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
               var blob_url = URL.createObjectURL(response);
            // add downloaded file to zip:
            var fileName = fileURLs[count].substring(fileURLs[count].lastIndexOf('/')+1);
            zip.file(fileName, blob_url); // <- here's one problem

            count++;
            if (count == fileURLs.length){
                // all download are completed, create the zip
                var content = zip.generate();

                // then trigger the download link:
                var zipName = 'download.zip';
                var a = document.createElement('a'); 
                a.href = "data:application/zip;base64," + content;
                a.download = zipName;
                a.click();
            }
        }
    };
    xhr.send();
}

function calculateAndUpdateProgress(evt) {
    if (evt.lengthComputable) {
        // get download progress by performing some average 
        // calculations with evt.loaded, evt.total and the number
        // of file to download / already downloaded
        ...
        // then update the GUI elements (eg. page-action icon and popup if showed)
        ...
    }
}

上のコードは、小さな破損ファイルを含むダウンロード可能なアーカイブを生成します。ファイル名の同期にも問題があります。blob オブジェクトにはファイル名が含まれていないため、たとえば. 名前が間違っている (反転) fileURLs[0]よりも、ダウンロードに時間がかかります。fileURLs[1]

注: Chrome にはダウンロード API があることは知っていますが、それは開発チャンネルにあるため、残念ながら現在は解決策ではありません。このような単純なタスクに NPAPI を使用することは避けたいと思います。

4

3 に答える 3

12

この質問を思い出しました..まだ答えがないので、他の人に役立つ場合に備えて可能な解決策を書きます:

  • 前述のように、最初の問題は blob url を jszip に渡すことです (blob をサポートしていませんが、それを通知するエラーもスローせず、破損したファイルのアーカイブを正常に生成します): これを修正するには、単純に base64 文字列を渡しますblob オブジェクトの URL ではなく、データの
  • 2 番目の問題は、ファイル名の同期に関するものです。ここでの最も簡単な回避策は、並列 xhr リクエストを使用する代わりに、一度に 1 つのファイルをダウンロードすることです。

したがって、変更された上位コードは次のようになります。

var fileURLs = ['http://www.test.com/img.jpg',...];
var zip = new JSZip();
var count = 0;

downloadFile(fileURLs[count], onDownloadComplete);


function downloadFile(url, onSuccess) {
    var xhr = new XMLHttpRequest();
    xhr.onprogress = calculateAndUpdateProgress;
    xhr.open('GET', url, true);
    xhr.responseType = "blob";
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (onSuccess) onSuccess(xhr.response);
}

function onDownloadComplete(blobData){
    if (count < fileURLs.length) {
        blobToBase64(blobData, function(binaryData){
                // add downloaded file to zip:
                var fileName = fileURLs[count].substring(fileURLs[count].lastIndexOf('/')+1);
                zip.file(fileName, binaryData, {base64: true});
                if (count < fileURLs.length -1){
                    count++;
                    downloadFile(fileURLs[count], onDownloadCompleted);
                }
                else {
                    // all files have been downloaded, create the zip
                    var content = zip.generate();

                    // then trigger the download link:        
                    var zipName = 'download.zip';
                    var a = document.createElement('a'); 
                    a.href = "data:application/zip;base64," + content;
                    a.download = zipName;
                    a.click();
                }
            });
    }
}

function blobToBase64(blob, callback) {
    var reader = new FileReader();
    reader.onload = function() {
        var dataUrl = reader.result;
        var base64 = dataUrl.split(',')[1];
        callback(base64);
    };
    reader.readAsDataURL(blob);
}

function calculateAndUpdateProgress(evt) {
    if (evt.lengthComputable) {
        ...
    }
}

最後の注意として、このソリューションは、ファイルをほとんどダウンロードしない場合 (10 個未満のファイルで全体のサイズが約 1MB 未満)、非常にうまく機能します。圧縮には別のスレッドを使用する方が良いでしょう (zip.js のような WebWorker)。

a.href = URL.createObjectURL(zippedBlobData);アーカイブが生成された後も、ブラウザが依然として大きなファイルでクラッシュし続け、エラーが報告されない場合は、バイナリ データを渡さずに、blob 参照を渡すことによって、saveAs ウィンドウをトリガーしてみてください (zippedBlobDataは、生成されたアーカイブ データ);

于 2013-07-17T10:33:44.597 に答える