WDwloadFile.mjs

import fs from 'fs'
import { pipeline } from 'stream/promises'
import { Transform } from 'stream'
import get from 'lodash-es/get.js'
import throttle from 'lodash-es/throttle.js'
import isbol from 'wsemi/src/isbol.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import getPathParent from 'wsemi/src/getPathParent.mjs'
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
import axios from 'axios'


/**
 * 下載檔案
 *
 * @param {String} url 輸入檔案網址字串
 * @param {String} fp 輸入儲存mp4檔案路徑字串
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Boolean} [opt.clean=false] 輸入預先清除暫存檔布林值,預設false
 * @param {Function} [opt.funProg=null] 輸入回傳進度函數,傳入參數為prog代表進度百分比、nn代表當前已下載ts檔案數量、na代表全部須下載ts檔案數量,預設null
 * @returns {Promise} 回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息
 * @example
 *
 * import fs from 'fs'
 * import WDwloadFile from './src/WDwloadFile.mjs'
 *
 * async function test() {
 *
 *     //url
 *     let url = `https://cdn.jsdelivr.net/npm/w-demores@1.0.28/res/video/aigen.mp4`
 *
 *     //fp
 *     let fp = './abc.mp4'
 *
 *     //funProg
 *     let funProg = (prog, nn, na) => {
 *         console.log('prog', `${prog.toFixed(2)}%`, nn, na)
 *     }
 *
 *     //WDwloadFile
 *     await WDwloadFile(url, fp, {
 *         clean: true, //單一程序執行時, 事先清除之前暫存檔, 減少浪費硬碟空間
 *         funProg,
 *     })
 *
 *     //len
 *     let len = fs.statSync(fp).size
 *     console.log('len', len)
 *
 *     console.log('done:', fp)
 * }
 * test()
 *     .catch((err) => {
 *         console.log('catch', err)
 *     })
 * // prog 0.97% 1378 140769
 * // prog 100.00% 140769 140769
 * // len 140769
 * // done: ./abc.mp4
 *
 */
async function WDwloadFile(url, fp, opt = {}) {

    //clean
    let clean = get(opt, 'clean')
    if (!isbol(clean)) {
        clean = false
    }

    //funProg
    let funProg = get(opt, 'funProg')

    //check
    if (!isestr(url)) {
        return Promise.reject('url is not a file')
    }

    //bFunProg
    let bFunProg = isfun(funProg)

    //fd
    let fd = getPathParent(fp)
    if (!fsIsFolder(fd)) {
        fsCreateFolder(fd)
    }

    //以stream方式下載
    let res = await axios.get(url, {
        responseType: 'stream',
        timeout: get(opt, 'timeout', 5 * 60 * 1000), //預設5分鐘
        maxRedirects: get(opt, 'maxRedirects', 5),
        headers: get(opt, 'headers', {}),
        validateStatus: s => s >= 200 && s < 400,
    })

    //nnTotal
    let nnTotal = cdbl(res.headers['content-length'])

    //throttle限制每秒觸發一次
    let funProgThrottle = null
    if (bFunProg) {
        funProgThrottle = throttle((prog, nn, nnTotal) => {
            funProg(prog, nn, nnTotal)
        }, 1000, { leading: true, trailing: true })
    }

    // 計數並回報進度
    let nn = 0
    let progressTap = new Transform({
        transform(chunk, _enc, cb) {
            nn += chunk.length
            let prog = nn / nnTotal * 99 //最高99%, 最後100%由最後完成階段觸發
            if (bFunProg) {
                funProgThrottle(prog, nn, nnTotal)
            }
            cb(null, chunk)
        }
    })

    //pipeline
    await pipeline(
        res.data, //axios的stream
        progressTap, //計數tap
        fs.createWriteStream(fp) //寫入檔案
    )

    //funProg
    if (bFunProg) {
        funProgThrottle?.cancel?.() //清掉throttle已觸發排程
        funProg(100, nnTotal, nnTotal)
    }

    return 'ok'
}


export default WDwloadFile