fsCopyFileCore.mjs

import get from 'lodash-es/get.js'
import isbol from './isbol.mjs'
import fsIsFileCore from './fsIsFileCore.mjs'
import fsCreateFolderCore from './fsCreateFolderCore.mjs'
import toResultError from './_toResultError.mjs'


function fsCopyFileCoreSync(fpSrc, fpTar, opt = {}) {

    //path, fs
    let path = get(opt, 'path')
    let fs = get(opt, 'fs')

    //複製檔案
    try {

        //check, 置於try內: 注入之fs之lstatSync可拋錯, 於try外即成同步拋錯
        if (!fsIsFileCore(fpSrc, { fs })) {
            return {
                error: `fpSrc[${fpSrc}] is not a file`
            }
        }

        //fsCreateFolderCore
        fsCreateFolderCore(path.dirname(fpTar), { fs })

        //copyFileSync
        fs.copyFileSync(fpSrc, fpTar)

    }
    catch (err) {
        return {
            error: toResultError(err) //Error或非空字串原樣, 其餘(null、undefined等)轉字串, 恆為truthy
        }
    }

    return {
        success: 'done: ' + fpTar
    }
}


async function fsCopyFileCoreAsync(fpSrc, fpTar, opt = {}) {

    //path, fs
    let path = get(opt, 'path')
    let fs = get(opt, 'fs')

    //fsCopyFileCoreAsyncCore
    let fsCopyFileCoreAsyncCore = async (fpSrc, fpTar) => {

        //check, 置於async內: 注入之fs之lstatSync拋錯時轉為結果物件, 不觸發reject
        if (!fsIsFileCore(fpSrc, { fs })) {
            return `fpSrc[${fpSrc}] is not a file`
        }

        //fsCreateFolderCore
        fsCreateFolderCore(path.dirname(fpTar), { fs })

        //使用串流方式複製檔案
        await new Promise((resolve, reject) => {

            //streamRead, streanWrite
            let streamRead = null
            let streanWrite = null

            //fail, 任一端出錯即銷毀兩端: pipe不會於來源出錯時關閉目的端、亦不會於目的端出錯時關閉來源, 未銷毀者之fd殘留
            let fail = (err) => {
                try {
                    if (streamRead !== null) {
                        streamRead.destroy()
                    }
                }
                catch (e) {}
                try {
                    if (streanWrite !== null) {
                        streanWrite.destroy()
                    }
                }
                catch (e) {}
                reject(err)
            }

            //create, 寫入流之建構子可同步拋錯(如fpTar含NUL), 此時已建立之讀取流須銷毀
            try {
                streamRead = fs.createReadStream(fpSrc)
                streanWrite = fs.createWriteStream(fpTar)
            }
            catch (err) {
                fail(err)
                return
            }

            //on
            streamRead.on('error', fail)
            streanWrite.on('error', fail)
            streanWrite.on('finish', resolve)

            //pipe
            streamRead.pipe(streanWrite)

        })

    }

    //fsCopyFileCoreAsyncCore
    let r = null
    await fsCopyFileCoreAsyncCore(fpSrc, fpTar)
        .then((msgErr) => {
            if (msgErr) {
                r = {
                    error: msgErr
                }
                return
            }
            r = {
                success: 'done: ' + fpTar
            }
        })
        .catch((err) => {
            r = {
                error: toResultError(err) //Error或非空字串原樣, 其餘(如串流以null出錯)轉字串, 恆為truthy
            }
        })

    return r
}


/**
 * 後端nodejs複製檔案
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/fsCopyFileCore.test.mjs Github}
 * @memberOf wsemi
 * @param {String} fpSrc 輸入來源檔案路徑字串
 * @param {String} fpTar 輸入目的檔案路徑字串
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Boolean} [opt.useSync=true] 輸入是否使用同步函數布林值,預設true
 * @returns {Object|Promise} 若useSync=true回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),若useSync=false則回傳Promise,resolve回傳結果物件,提供鍵success代表成功訊息,提供鍵error代表錯誤訊息(說明字串或Error物件),不觸發reject
 * @example
 * need test in nodejs.
 *
 * //see fsCopyFile
 *
 */
function fsCopyFileCore(fpSrc, fpTar, opt = {}) {

    //useSync, 先讀以決定失敗時之回傳形式; opt本身或其屬性不可讀時視為預設true
    let useSync = null
    try {
        useSync = get(opt, 'useSync', '')
    }
    catch (err) {}
    if (!isbol(useSync)) {
        useSync = true
    }

    //path, fs, 讀取opt置於try內: opt本身或其屬性不可讀(getter拋錯、已撤銷之Proxy)時get拋出, 轉為結果物件而不同步拋出(useSync=false時以Promise resolve)
    let path = null
    let fs = null
    try {
        path = get(opt, 'path')
        fs = get(opt, 'fs')
    }
    catch (err) {
        let rErr = {
            error: toResultError(err), //Error原樣, 其餘轉字串, 恆為truthy
        }
        return useSync ? rErr : Promise.resolve(rErr)
    }

    let r = ''
    if (useSync) {
        r = fsCopyFileCoreSync(fpSrc, fpTar, { path, fs })
    }
    else {
        r = fsCopyFileCoreAsync(fpSrc, fpTar, { path, fs })
    }

    return r
}


export default fsCopyFileCore