downloadFiles.mjs

import get from 'lodash-es/get.js'
import filter from 'lodash-es/filter.js'
import isfun from 'wsemi/src/isfun.mjs'
import getErrorMessage from 'wsemi/src/getErrorMessage.mjs'
import fsCleanFolder from 'wsemi/src/fsCleanFolder.mjs'
import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
import WFtp from 'w-ftp/src/WFtp.mjs'


/**
 * 由FTP或SFTP下載檔案至本機暫存資料夾fdDwStorageTemp,並回傳所下載之檔案清單
 *
 * 下載前會先清空fdDwStorageTemp,僅下載st.fdIni內第1層之檔案,且強制複寫本機數據
 *
 * @param {Object} st 輸入設定FTP連線資訊物件
 * @param {String} [st.transportation='FTP'] 輸入傳輸協定字串,可選'FTP'、'SFTP',預設'FTP'
 * @param {String} [st.hostname=''] 輸入hostname字串,預設''
 * @param {Integer} [st.port=21|22] 輸入port正整數,當transportation='FTP'預設21,當transportation='SFTP'預設22
 * @param {String} [st.username=''] 輸入帳號字串,預設''
 * @param {String} [st.password=''] 輸入密碼字串,預設''
 * @param {String} [st.fdIni='./'] 輸入同步資料夾字串,預設'./'
 * @param {String} fdDwStorageTemp 輸入最新下載檔案存放資料夾字串
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Object} [opt.srLog=null] 輸入事件紀錄物件,需提供函數info與error,各接收一紀錄物件,未提供時各自以空函數替代不進行紀錄,預設null
 * @returns {Promise} 回傳Promise,resolve回傳列舉檔案陣列,各元素為檔案資訊物件,內含name、path、isFolder等鍵值,reject回傳錯誤訊息
 * @example
 *
 * import downloadFiles from './src/downloadFiles.mjs'
 *
 * let st = {
 *     'transportation': 'FTP',
 *     'hostname': '{hostname}',
 *     'port': 21,
 *     'username': '{username}',
 *     'password': '{password}',
 *     'fdIni': './'
 * }
 *
 * let opt = {
 *     srLog: {
 *         info: (msg) => {
 *             console.log('info', msg)
 *         },
 *         error: (msg) => {
 *             console.log('error', msg)
 *         },
 *     },
 * }
 *
 * let vfps = await downloadFiles(st, `./_dwStorageTemp`, opt)
 *     .catch((err) => {
 *         console.log(err)
 *     })
 * // console.log('vfps', vfps)
 * // info { event: 'ftp.conn', msg: 'start...' }
 * // info { event: 'ftp.conn', msg: 'done' }
 * // info { event: 'ftp.ls', length: 2, msg: 'done' }
 * // info { event: 'ftp.syncToLocal', msg: 'start...' }
 * // ...
 *
 */
let downloadFiles = async(st, fdDwStorageTemp, opt = {}) => {
    let errTemp = null

    //srLog
    let srLog = get(opt, 'srLog', null)

    //srLogInfo
    let srLogInfo = get(srLog, 'info', null)
    if (isfun(srLogInfo)) {
        srLogInfo = (...args) => {
            return srLog.info(...args)
        }
    }
    else {
        srLogInfo = () => {}
    }

    //srLogError
    let srLogError = get(srLog, 'error', null)
    if (isfun(srLogError)) {
        srLogError = (...args) => {
            return srLog.error(...args)
        }
    }
    else {
        srLogError = () => {}
    }

    //core
    let core = async() => {

        //fsCleanFolder
        fsCleanFolder(fdDwStorageTemp)

        //ftp
        let ftp = WFtp({
            ...st,
            timeLimit: 100 * 1000, //100s
        })
        // console.log('ftp', ftp)

        //syncFiles
        let syncFiles = async () => {

            //ls
            // let fps = await ftp.ls('.')
            let fps = await ftp.ls(st.fdIni)
            // console.log('ftp.ls', fps[0], fps.length)
            srLogInfo({ event: 'ftp.ls', length: fps.length, msg: 'done' })

            //syncToLocal
            srLogInfo({ event: 'ftp.syncToLocal', msg: 'start...' })
            let r = await ftp.syncToLocal(
                st.fdIni,
                fdDwStorageTemp,
                (p) => {
                    // console.log('ftp.syncToLocal p', p.name, p.progress)
                    srLogInfo({ event: 'ftp.syncToLocal', name: p.name, progress: p.progress, msg: 'running...' })
                },
                {
                    levelLimit: 1, //僅下載第1層內檔案
                    forceOverwriteWhenSync: true, //強制全下載進行複寫本機數據
                },
            )
            // console.log('ftp.syncToLocal r', r)
            srLogInfo({ event: 'ftp.syncToLocal', result: r, msg: 'done' })

        }

        try {

            //conn
            srLogInfo({ event: 'ftp.conn', msg: 'start...' })
            await ftp.conn()
            srLogInfo({ event: 'ftp.conn', msg: 'done' })

            //syncFiles
            srLogInfo({ event: 'syncFiles', msg: 'start...' })
            await syncFiles()
                .then(() => {
                    srLogInfo({ event: 'syncFiles', msg: 'done' })
                })
                .catch((err) => { //須catch, 避免操作指令失敗造成程序中止
                    // console.log(err)
                    srLogError({ event: 'syncFiles', msg: getErrorMessage(err) })
                })

        }
        finally {
            //quit, 凡conn過即須執行至quit, w-ftp(1.0.17+)之quit會硬關閉socket, 否則FTP停滯時socket殘留致行程無法退出

            //quit, 須catch, quit為收尾清理, 逾時路徑socket已被硬關閉, 失敗不應作廢已下載檔案
            await ftp.quit()
                .then((r) => {
                    // console.log('ftp.quit', r)
                    srLogInfo({ event: 'ftp.quit', result: r, msg: 'done' })
                })
                .catch((err) => {
                    // console.log(err)
                    srLogError({ event: 'ftp.quit', msg: getErrorMessage(err) })
                })

        }

    }

    //core
    srLogInfo({ event: 'core', msg: 'start...' })
    await core()
        .then(() => {
            srLogInfo({ event: 'core', msg: 'done' })
        })
        .catch((err) => { //須再catch, 避免無法連線SFTP時中止程序
            // console.log(err)
            srLogError({ event: 'core', msg: getErrorMessage(err) })
            errTemp = err
        })

    //check
    if (errTemp !== null) {
        return Promise.reject(errTemp)
    }

    //vfps
    let vfps = []
    try {
        srLogInfo({ event: 'getVfps', msg: 'start...' })
        vfps = fsTreeFolder(fdDwStorageTemp, 1)
        vfps = filter(vfps, (v) => {
            return !v.isFolder //僅使用檔案
        })
        srLogInfo({ event: 'getVfps', length: vfps.length, msg: 'done' })
    }
    catch (err) {
        // console.log(err)
        srLogError({ event: 'getVfps', msg: getErrorMessage(err) })
        errTemp = err
    }
    // console.log('vfps', vfps)

    //check
    if (errTemp !== null) {
        return Promise.reject(errTemp)
    }

    return vfps
}


export default downloadFiles