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

如何使用Javascript下载、压缩和保存多个文件并取得进展?,javascript,google-chrome-extension,zip,download,save-as,Javascript,Google Chrome Extension,Zip,Download,Save As,我正在创建一个需要从网站下载多个文件(图像和/或视频)的Chrome扩展。这些文件可能很大,所以我想向用户显示下载进度。经过一些研究,我发现目前一个可能的解决方案可能是: 使用XMLHttpRequests下载所有文件 下载后,使用JavaScript库(例如JSZip.js、zip.js)将所有文件压缩到一个归档文件中 提示用户使用“另存为”对话框保存zip 我被困在第二段),我如何才能压缩下载的文件 为了理解,下面是一个代码示例: var fileURLs = ['http://www.te

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

  • 使用XMLHttpRequests下载所有文件
  • 下载后,使用JavaScript库(例如JSZip.js、zip.js)将所有文件压缩到一个归档文件中
  • 提示用户使用“另存为”对话框保存zip
  • 我被困在第二段),我如何才能压缩下载的文件

    为了理解,下面是一个代码示例:

    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)
            ...
        }
    }
    
    var fileurl=['http://www.test.com/img.jpg',...];
    var zip=newjszip();
    var计数=0;
    对于(var i=0;izip.file(fileName,blob_url);//我想起了这个问题。由于它还没有答案,我写了一个可能的解决方案,以防它对其他人有用:

    • 如前所述,第一个问题是将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) {
            ...
        }
    }
    
    var fileurl=['http://www.test.com/img.jpg',...];
    var zip=newjszip();
    var计数=0;
    下载文件(fileURLs[count],onDownloadComplete);
    函数下载文件(url,onSuccess){
    var xhr=new XMLHttpRequest();
    xhr.onprogress=calculateAndUpdateProgress;
    xhr.open('GET',url,true);
    xhr.responseType=“blob”;
    xhr.onreadystatechange=函数(){
    if(xhr.readyState==4){
    if(onSuccess)onSuccess(xhr.response);
    }
    函数onDownloadComplete(blobData){
    if(count
    最后请注意,如果您下载的文件很少(不到10个文件的总大小小于1MB),此解决方案工作得很好,在其他情况下,JSZip将在生成归档文件时使浏览器选项卡崩溃,因此最好使用单独的线程进行压缩(像zip.js这样的WebWorker)


    如果在生成存档文件之后,浏览器仍然会因大文件而崩溃,并且没有报告任何错误,请尝试在不传递二进制数据的情况下触发“另存为”窗口,但要传递blob引用(
    a.href=URL.createObjectURL(zippedBlobData);
    其中
    zippedBlobData
    是引用生成的存档数据的blob对象);

    基于@guari代码,我在本地对其进行了测试,并将其应用于react应用程序,附加代码供其他人参考

    从“JSZip”导入JSZip;
    从“jszip/vendor/FileSaver.js”导入saveAs;
    // .......
    //下载按钮点击事件
    btnDownloadAudio=记录=>{
    让FileURL=['https://www.test.com/52f6c50.AMR', 'https://www.test.com/061940.AMR'];
    让计数=0;
    让zip=newjszip();
    const query={record,fileurl,count,zip};
    this.downloadFile(查询,this.onDownloadComplete);
    }
    下载文件=(查询,onSuccess)=>{
    const{fileurl,count,}=query;
    var xhr=new XMLHttpRequest();
    xhr.onprogress=this.calculateAndUpdateProgress;
    open('GET',fileurl[count],true);
    xhr.responseType=“blob”;
    xhr.onreadystatechange=函数(e){
    if(xhr.readyState==4){
    if(onSuccess)onSuccess(查询,xhr.response);
    }
    }
    xhr.send();
    }
    onDownloadComplete=(查询,blobData)=>{
    让{record,fileurl,count,zip}=query;
    if(countimport JSZip from 'jszip'
    import JSZipUtils from 'jszip-utils'
    import FileSaver from 'file-saver'
    
    const async downloadZip = (urls) => {
          const urlToPromise = (url) => {
            return new Promise((resolve, reject) => {
              JSZipUtils.getBinaryContent(url, (err, data) => {
                if (err) reject(err)
                else resolve(data)
              })
            })
          }
    
          const getExtension = (binary) => {
            const arr = (new Uint8Array(binary)).subarray(0, 4)
            let hex = ''
            for (var i = 0; i < arr.length; i++) {
              hex += arr[i].toString(16)
            }
            switch (hex) {
              case '89504e47':
                return 'png'
              case '47494638':
                return 'gif'
              case 'ffd8ffe0':
              case 'ffd8ffe1':
              case 'ffd8ffe2':
              case 'ffd8ffe3':
              case 'ffd8ffe8':
                return 'jpg'
              default:
                return ''
            }
          }
    
          this.progress = true
    
          const zip = new JSZip()
          for (const index in urls) {
            const url = urls[index]
            const binary = await urlToPromise(url)
            const extension = getExtension(binary) || url.split('.').pop().split(/#|\?/)[0]
            const filename = `${index}.${extension}`
            zip.file(filename, binary, { binary: true })
          }
          await zip.generateAsync({ type: 'blob' })
            .then((blob) => {
              FileSaver.saveAs(blob, 'download.zip')
            })
    }
    
    downloadZip(['https://example.net/1.jpg', 'https://example.net/some_picture_generator'])