如何使用Javascript下载,压缩和保存多个文件并取得进展?

gua*_*ari 5 javascript zip download save-as google-chrome-extension

我正在创建一个Chrome扩展程序,需要从网站下载多个文件(图像和/或视频).这些文件可能有很大的大小,所以我想向用户显示下载进度.经过一些研究,我发现目前可能的解决方案可能是:

  1. 使用XMLHttpRequests下载所有文件.
  2. 下载后,使用JavaScript库将所有文件压缩到一个存档中(例如JSZip.js,zip.js).
  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)
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码生成一个包含小损坏文件的可下载存档.文件名同步也存在问题:blob对象不包含文件名,因此如果.fileURLs[0]需要更多时间下载而不是fileURLs[1]名称变错(倒置)..

注意:我知道Chrome有一个下载API,但它在开发频道中,所以不幸的是它现在不是解决方案,我想避免使用NPAPI来完成这么简单的任务.

Bla*_*ard 9

我的解决方案使用Axios,FileSaver.jsJSZip

import JSZip from "jszip";
import axios from "axios";
import { saveAs } from "file-saver";

const zip = new JSZip();

const fileArr = [
    {
        name: "file1.jpg",
        url: "https://url.com/file1.jpg",
    },
    {
        name: "file2.docx",
        url: "https://url.com/file2.docx",
    },
    {
        name: "file3.pdf",
        url: "https://url.com/file3.pdf",
    },
];

const download = (item) => {
    //download single file as blob and add it to zip archive
    return axios.get(item.url, { responseType: "blob" }).then((resp) => {
        zip.file(item.name, resp.data);
    });
};

//call this function to download all files as ZIP archive
const downloadAll = () => {
    const arrOfFiles = fileArr.map((item) => download(item)); //create array of promises
    Promise.all(arrOfFiles)
        .then(() => {
            //when all promises resolved - save zip file
            zip.generateAsync({ type: "blob" }).then(function (blob) {
                saveAs(blob, "hello.zip");
            });
        })
        .catch((err) => {
            console.log(err);
        });
};
Run Code Online (Sandbox Code Playgroud)


gua*_*ari 7

我被提醒了这个问题..因为它还没有答案,我写了一个可能的解决方案,以防它对其他人有用:

  • 如上所述,第一个问题是将blob url传递给jszip(它不支持blob但它也不会抛出任何错误来通知它并且它成功生成了已损坏文件的存档):要纠正这个问题,只需传递一个base64字符串数据而不是其blob对象url;
  • 第二个问题是文件名同步:这里最简单的解决方法是一次下载一个文件而不是使用parallels xhr请求.

因此,修改后的上层代码可以是:

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) {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

最后请注意,如果您下载少量文件(小于10个文件的整体大小不到1MB),这个解决方案效果很好,在其他情况下,JSZip会在生成存档时崩溃浏览器选项卡,所以它使用分离的线程进行压缩(WebWorker,如zip.js)是一个更好的选择.

如果之后生成了归档文件,浏览器仍会继续崩溃大文件而不报告任何错误,尝试触发saveAs窗口而不传递二进制数据,但是通过传递blob引用(a.href = URL.createObjectURL(zippedBlobData);其中zippedBlobData是blob对象引用的生成的档案数据);