jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
3.97k stars 413 forks source link

How to display total size when downloading files in batch? #269

Closed shiliuxiaoler closed 2 years ago

shiliuxiaoler commented 2 years ago

I want to display the total size of the compressed package file during the process of downloading large files, but the totalSize of all files has not been obtained when createWriteStream is created. How to update the size of createWriteStream after fetching to get all Content-Length? I've tried many ways to no avail, I hope to get help, thank you so much.

function multiDownload(urls, callback) {
    let date = Date.parse(new Date())
    let totalfileSize = 0
    const fileStream = StreamSaver.createWriteStream('Batchdownload_' + date + '.zip')
    const readableZipStream = new ZipStream({
        start(ctrl) {
        },
        async pull(ctr1) {
            for (let i = 0; i < urls.length; i++) {
                const url = urls[i].url
                const res = await fetch(url)
                console.log(res.headers.get('Content-Length'),'文件大小',typeof(res.headers.get('Content-Length')))
                totalfileSize += parseInt(res.headers.get('Content-Length'))
                if (res.status === 200 && res.ok) {
                    const stream = () => res.body
                    ctr1.enqueue({ urls[i].filename, stream })
                } 
            }
            ctr1.close()
        }
    })
    if (window.WritableStream && readableZipStream.pipeTo) {
        return readableZipStream.pipeTo(fileStream).then(() => console.log('done writing'))
    }
    const writer = fileStream.getWriter()
    const reader = readableZipStream.getReader()
    const pump = () => reader.read().then(res => {
        res.done ? writer.close() : writer.write(res.value).then(pump)
    })
    pump()
}
jimmywarting commented 2 years ago

figuring out the final total size of multiple url from content-length and extra zip headers can be tuff...

  1. you would first have to figure out how many files there is
  2. then you would preferable make a early pre fetch HEAD request to get information of how large each file is by looking at the content-length
  3. then you also need to figure out weather or not it's compressed or not: cuz content-length only reflects the compressed size (not the actual size),
    // example
    res = await fetch('https://httpbin.org/gzip')
    blob = await res.blob()
    console.log(res.headers.get('content-encoding')) // gzip
    console.log(res.headers.get('content-length')) // 482
    console.log(blob.size) // 880
  4. then you must account for each zip entry of how many extra bytes each one adds (something like fixed_size * n_files + all_file_names_in_utf8array.bytelength + end_of_central_zip_size

predicting the total file size in advance is hard. it's something i would recommend against. Instead of would add a html UI showing the progress of each file that is being downloaded. and how many files there are left.

something like:

- file_a.txt - 100% - done
- file_b.txt - 100% - done
- file_c.txt - 80% - downloading
- file_d.txt - 0% - pending
- file_e.txt - 0% - pending

you can't update the content-size after the download has started unfortunately