import fs from 'fs'
import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import evem from './evem.mjs'
import genPm from './genPm.mjs'
import isestr from './isestr.mjs'
import ispint from './ispint.mjs'
import isarr from './isarr.mjs'
import ispnum from './ispnum.mjs'
import cint from './cint.mjs'
import j2o from './j2o.mjs'
import o2j from './o2j.mjs'
import sep from './sep.mjs'
import getFileName from './getFileName.mjs'
import ltdtDiffByKey from './ltdtDiffByKey.mjs'
import fsIsFolder from './fsIsFolder.mjs'
import fsCreateFolder from './fsCreateFolder.mjs'
import fsGetFilesWithHashInFolder from './fsGetFilesWithHashInFolder.mjs'
/**
* 後端nodejs偵測指定資料夾下之檔案變化,僅偵測新增與變更檔案,不偵測刪除檔案
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/fsTask.test.mjs Github}
* @memberOf wsemi
* @param {String} fd 輸入欲列舉的資料夾字串
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Integer} [opt.levelLimit=1] 輸入列舉層數限制正整數,設定1為列舉資料夾下第一層的檔案,設定null為無窮遍歷所有檔案,預設1
* @param {String} [opt.type='md5'] 輸入計算HASH方法,預設'md5'
* @param {Number} [opt.timeInterval=60000] 輸入定時器偵測時長正整數,單位為毫秒ms,預設60000,為1分鐘
* @returns {Object} 回傳事件物件,包含run、stop函數,on可進行監聽指定事件,run為啟動偵測函數,stop為停止偵測函數
* @example
* //need test in nodejs
*
* import fsDeleteFile from './src/fsDeleteFile.mjs'
* import fsCreateFolder from './src/fsCreateFolder.mjs'
* import fsDeleteFolder from './src/fsDeleteFolder.mjs'
* import fsTask from './src/fsTask.mjs'
*
* let test = async () => {
* return new Promise((resolve, reject) => {
* let ms = []
*
* let fpc = './_tkfs'
* fsDeleteFolder(fpc) //預先清除fsTask持久化數據資料夾
*
* let fpt = './_test_fsTask'
* fsDeleteFolder(fpt) //預先清除任務資料夾
*
* let fpr = './_test_fsTask_result'
* fsCreateFolder(fpr) //創建結果資料夾
*
* let ev = fsTask(fpt, { timeInterval: 500 })
* ev.on('change', (msg) => {
* console.log(msg.type, msg.fn)
*
* //content
* let c = ''
* try {
* c = fs.readFileSync(msg.fp, 'utf8')
* }
* catch (err) {}
*
* if (msg.fn === 'abc.txt') {
* //僅針對abc.txt任務
*
* if (msg.type === 'add' || msg.type === 'diff') {
* //針對新增或變更任務
*
* console.log(`task[${msg.fn}]`, `content[${c}]`, 'calculating')
* ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'calculating' })
*
* //模擬計算延遲
* setTimeout(() => {
*
* //模擬計算完儲存結果
* fs.writeFileSync(`${fpr}/res1.json`, 'res1', 'utf8')
* fs.writeFileSync(`${fpr}/res2.json`, 'res2', 'utf8')
*
* //使用setResult紀錄完成分析後之關聯結果檔
* ev.setResult(msg.fp, msg.hash, [
* {
* type: 'file',
* path: `${fpr}/res1.json`,
* },
* {
* type: 'file',
* path: `${fpr}/res2.json`,
* },
* ])
*
* console.log(`task[${msg.fn}]`, `content[${c}]`, 'save-result')
* ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'save-result' })
*
* msg.pm.resolve()
* }, 2000)
*
* }
* else { //msg.type === 'del'
* //針對任務刪除
*
* console.log(`task[${msg.fn}]`, 'remove-task')
* ms.push({ type: msg.type, fp: msg.fn, mode: 'remove-task' })
*
* //刪除任務時, 自動刪除關聯結果檔
* let rrs = ev.getAndEliminateResult(msg.fp, msg.hash)
* // console.log('rrs', rrs)
* for (let k = 0; k < rrs.length; k++) {
* fsDeleteFile(rrs[k].path)
* }
*
* console.log(`task[${msg.fn}]`, 'remove-result')
* ms.push({ type: msg.type, fp: msg.fn, mode: 'remove-result' })
*
* msg.pm.resolve()
* }
*
* }
* else {
* //針對其他任務
*
* console.log(`task[${msg.fn}]`, `content[${c}]`, 'skip')
* ms.push({ type: msg.type, fp: msg.fn, content: c, mode: 'skip' })
* msg.pm.resolve()
* }
*
* })
*
* setTimeout(() => {
* fsCreateFolder(fpt)
* }, 1)
*
* setTimeout(() => {
* fs.writeFileSync(`${fpt}/abc.txt`, 'abc', 'utf8')
* }, 3000)
*
* setTimeout(() => {
* fs.writeFileSync(`${fpt}/abc.txt`, 'mnop', 'utf8')
* fs.writeFileSync(`${fpt}/def.txt`, 'def', 'utf8')
* }, 6000)
*
* setTimeout(() => {
* fsDeleteFile(`${fpt}/abc.txt`)
* }, 9000)
*
* setTimeout(() => {
* fsDeleteFolder(fpt) //最終階段清除任務資料夾
* }, 12000)
*
* setTimeout(() => {
* ev.clear() //結束後中止ev
* fsDeleteFolder(fpc) //結束後清除fsTask持久化數據資料夾
* fsDeleteFolder(fpr) //結束後清除結果資料夾
* console.log('ms', ms)
* resolve(ms)
* }, 15000)
*
* })
* }
* test()
* .catch(() => {})
* // add abc.txt
* // task[abc.txt] content[abc] calculating
* // task[abc.txt] content[abc] save-result
* // diff abc.txt
* // task[abc.txt] content[mnop] calculating
* // task[abc.txt] content[mnop] save-result
* // add def.txt
* // task[def.txt] content[def] skip
* // del abc.txt
* // task[abc.txt] remove-task
* // task[abc.txt] remove-result
* // del def.txt
* // task[def.txt] content[] skip
* // ms [
* // { type: 'add', fp: 'abc.txt', content: 'abc', mode: 'calculating' },
* // { type: 'add', fp: 'abc.txt', content: 'abc', mode: 'save-result' },
* // { type: 'diff', fp: 'abc.txt', content: 'mnop', mode: 'calculating' },
* // { type: 'diff', fp: 'abc.txt', content: 'mnop', mode: 'save-result' },
* // { type: 'add', fp: 'def.txt', content: 'def', mode: 'skip' },
* // { type: 'del', fp: 'abc.txt', mode: 'remove-task' },
* // { type: 'del', fp: 'abc.txt', mode: 'remove-result' },
* // { type: 'del', fp: 'def.txt', content: '', mode: 'skip' }
* // ]
*
*/
function fsTask(fd, opt = {}) {
//fdStorage
let fdStorage = get(opt, 'fdStorage', '')
if (!isestr(fdStorage)) {
fdStorage = './_tkfs'
}
//levelLimit
let levelLimit = get(opt, 'levelLimit', '')
if (!ispint(levelLimit)) {
levelLimit = 1
}
levelLimit = cint(levelLimit)
//typeHash
let typeHash = get(opt, 'typeHash', '')
if (!isestr(typeHash)) {
typeHash = 'md5'
}
//timeInterval
let timeInterval = get(opt, 'timeInterval')
if (!ispnum(timeInterval)) {
timeInterval = 60 * 1000 //1min
}
timeInterval = cint(timeInterval)
//fsCreateFolder
fsCreateFolder(fdStorage)
//ev
let ev = evem()
//lock
let lock = false
//fpHash, fpResult
let fpHash = `${fdStorage}/fpHash.json`
// let fpState = `${fdStorage}/fpState.json`
let fpResult = `${fdStorage}/fpResult.json`
//readObHash
let readObHash = () => {
let r = {} //儲存格式為物件
try {
let j = fs.readFileSync(fpHash, 'utf8')
r = j2o(j)
}
catch (err) {}
return r
}
//writeObHash
let writeObHash = (r) => {
try {
let j = o2j(r)
fs.writeFileSync(fpHash, j, 'utf8')
}
catch (err) {}
}
//calcHash
let calcHash = () => {
//kpHash
let kpHash = {}
//check
if (!fsIsFolder(fd)) {
return kpHash
}
//fsGetFilesWithHashInFolder
let fps = fsGetFilesWithHashInFolder(fd, levelLimit, { type: typeHash })
// console.log('fps', fps)
//push
each(fps, (v) => {
kpHash[v.path] = v.hash
})
return kpHash
}
//compareHashAndEmitOne
let compareHashAndEmitOne = () => {
//check
if (lock === true) {
// console.log('locking')
return
}
//kpHashsOld
let kpHashsOld = readObHash()
// console.log('kpHashsOld', kpHashsOld)
//kpHashNew
let kpHashNew = calcHash()
// console.log('kpHashNew', kpHashNew)
// //getObKpState
// let kpState = getObKpState(kpHashNew)
// // console.log('kpState', kpState)
// //_getObState
// let _getObState = (fp) => {
// let state = get(kpState, fp, 'none')
// return state //可能為none, doing, finish
// }
// //check state
// if (true) {
// let b = false
// each(kpHashNew, (_, path) => {
// let state = _getObState(path)
// if (state === 'doing') {
// // console.log(`state of path[${path}] is ${state}`)
// b = true
// return false //跳出
// }
// })
// if (b) {
// return
// }
// }
//hsOld
let hsOld = map(kpHashsOld, (hash, path) => {
return { path, hash }
})
// console.log('hsOld', hsOld)
//hsNew
let hsNew = map(kpHashNew, (hash, path) => {
return { path, hash }
})
// console.log('hsNew', hsNew)
//rd
let rd = ltdtDiffByKey(hsOld, hsNew, 'path')
// console.log('rd.infor', rd.infor)
//emit
each(rd.infor, (v, k) => {
//check, 未變更則跳出
if (v === 'same') {
return true //跳出換下一個
}
// //check, 刪除監聽檔案時清除state
// if (v === 'del') {
// setObState(k, 'none')
// }
// //states
// let state = _getObState(k)
// // console.log('state', state)
//hashOld
let hashOld = get(kpHashsOld, k, '')
// console.log(v, 'hashOld', hashOld)
//hashNew
let hashNew = get(kpHashNew, k, '')
// console.log(v, 'hashNew', hashNew)
//hash
let hash = ''
if (v === 'del') {
hash = hashOld
}
else {
hash = hashNew
}
// //result
// let result = getObResult(k, hashNew)
// //check, 若有結果則跳出
// if (result === 'yes') {
// return true //跳出換下一個
// }
//type, fp, fn
let type = v
let fp = k
let fn = getFileName(k)
//lock
lock = true
//pm
let pm = genPm()
//emit
ev.emit('change', { type, fp, fn, hash, pm })
//wait
pm
.then(() => {
//kpHashsModify
let kpHashsModify = cloneDeep(kpHashsOld)
//update
kpHashsModify[fp] = kpHashNew[fp]
//writeObHash
writeObHash(kpHashsModify)
})
.finally(() => {
//unlock
lock = false
})
return false //跳出
})
}
// //readObState
// let readObState = () => {
// let r = {} //儲存格式為物件
// try {
// let j = fs.readFileSync(fpState, 'utf8')
// r = j2o(j)
// }
// catch (err) {}
// return r
// }
// //writeObState
// let writeObState = (r = {}) => {
// try {
// let j = o2j(r)
// fs.writeFileSync(fpState, j, 'utf8')
// }
// catch (err) {}
// }
// //getObKpState, 基於指定任務hs與已儲存狀態kpStateOld, 回傳各任務狀態kpStateNow
// let getObKpState = (kpHash) => {
// //kpStateOld
// let kpStateOld = readObState()
// // console.log('kpStateOld', kpStateOld)
// //kpStateNow, 當前任務狀態
// let kpStateNow = {}
// each(kpHash, (_, path) => {
// // console.log('h', h)
// //state
// let state = get(kpStateOld, path, '')
// if (state !== 'doing' && state !== 'finish') {
// state = 'none'
// }
// // console.log('state', state)
// //update
// kpStateNow[path] = state
// })
// // console.log('kpStateNow', kpStateNow)
// return kpStateNow
// }
// //getObState, 基於已儲存狀態kpState, 取得fp(絕對檔名或檔名)之狀態
// let getObState = (fp) => {
// //readObState
// let kpState = readObState()
// // console.log('kpState', kpState)
// //check fp
// if (haskey(kpState, fp)) {
// let state = get(kpState, fp, 'none')
// return state
// }
// //check fn
// if (true) {
// let state = 'none'
// each(kpState, (_state, _fp) => {
// let _fn = getFileName(_fp)
// if (fp === _fn) { //輸入fp實際是fn
// state = _state
// return false //跳出
// }
// })
// return state
// }
// }
// //setObState, 針對絕對檔名fp變更儲存狀態
// let setObState = (fp, state) => {
// //check
// if (state !== 'none' && state !== 'doing' && state !== 'finish') {
// throw new Error(`state[${state}] must be one of ['none', 'doing', 'finish']`)
// }
// //readObState
// let kpState = readObState()
// // console.log('kpState', kpState)
// //update
// kpState[fp] = state
// //writeObState
// writeObState(kpState)
// }
// //compareState
// let compareState = () => {
// }
// //writeObState
// writeObState({}) //一啟動就清空fpState
//readObResult
let readObResult = () => {
let r = {} //儲存格式為物件
try {
let j = fs.readFileSync(fpResult, 'utf8')
r = j2o(j)
}
catch (err) {}
return r
}
//writeObResult
let writeObResult = (r = {}) => {
try {
let j = o2j(r)
fs.writeFileSync(fpResult, j, 'utf8')
}
catch (err) {}
}
//getObResult
let getObResult = (fp, hash) => {
//key
let key = `${fp}|${hash}`
//readObResult
let kpResult = readObResult()
// console.log('kpResult', kpResult)
//result
let result = get(kpResult, key, [])
// console.log(key, 'result', result)
//check
if (!isarr(result)) {
console.log(`result for key[${key}] is not an array`)
}
return result
}
//eliminateObResult
let eliminateObResult = (kpResult, fp) => {
let kp = {}
each(kpResult, (rs, key) => {
let s = sep(key, '|')
let _fp = get(s, 0, '')
// let _hash = get(s, 1, '')
if (_fp !== fp) {
kp[key] = rs
}
})
return kp
}
//getAndEliminateObResult
let getAndEliminateObResult = (fp, hash) => {
//key
let key = `${fp}|${hash}`
//readObResult
let kpResult = readObResult()
// console.log('kpResult', kpResult)
//result
let result = get(kpResult, key, [])
// console.log(key, 'result', result)
//check
if (!isarr(result)) {
console.log(`result for key[${key}] is not an array`)
}
//eliminateObResult
kpResult = eliminateObResult(kpResult, fp)
// console.log('kpResult(modify)', kpResult)
//writeObResult
writeObResult(kpResult)
return result
}
//setObResult
let setObResult = (fp, hash, result) => {
//check
if (!isarr(result)) {
console.log(`result is not an array, default to []`)
result = []
}
//key
let key = `${fp}|${hash}`
//readObResult
let kpResult = readObResult()
// console.log('kpResult', kpResult)
//update
kpResult[key] = result
// console.log('kpResult(modify)', kpResult)
//writeObResult
writeObResult(kpResult)
}
//timer
let t = setInterval(() => {
//compareHashAndEmitOne
compareHashAndEmitOne()
}, timeInterval)
// // let rs = []
// each(fps, (v) => {
// //default
// if (!haskey(kpHash, v.path)) {
// kpHash[v.path] = ''
// }
// //hash_ori
// let hash_ori = get(kpHash, v.path, '')
// //hash_new
// let hash_new = get(v, 'hash', '')
// //check
// if (!isestr(hash_new)) {
// return true //跳出換下一個
// }
// //check
// if (hash_ori === hash_new) {
// return true //跳出換下一個
// }
// //save
// kpHash[v.path] = hash_new
// // //push
// // rs.push(v.path)
// //emit
// })
// console.log('size(rs)', size(rs))
// //core
// let t = null
// let b = false
// let kpHash = {}
// let core = () => {
// //check
// if (b) {
// return
// }
// //lock
// b = true
// //compareHashAndEmitOne
// compareHashAndEmitOne()
// // //check
// // if (!fsIsFolder(fd)) {
// // console.log(`fd[${fd}] does not exist`)
// // return
// // }
// // //fsGetFilesWithHashInFolder
// // let fps = fsGetFilesWithHashInFolder(fd, levelLimit, { type: typeHash })
// // // console.log('fps', fps)
// // // let rs = []
// // each(fps, (v) => {
// // //default
// // if (!haskey(kpHash, v.path)) {
// // kpHash[v.path] = ''
// // }
// // //hash_ori
// // let hash_ori = get(kpHash, v.path, '')
// // //hash_new
// // let hash_new = get(v, 'hash', '')
// // //check
// // if (!isestr(hash_new)) {
// // return true //跳出換下一個
// // }
// // //check
// // if (hash_ori === hash_new) {
// // return true //跳出換下一個
// // }
// // //save
// // kpHash[v.path] = hash_new
// // // //push
// // // rs.push(v.path)
// // //emit
// // })
// // // console.log('size(rs)', size(rs))
// // //check
// // if (size(rs) > 0) {
// // //emit
// // ev.emit('change', rs)
// // }
// //unlock
// b = false
// }
//clear
let clear = () => {
clearInterval(t)
}
//save
// ev.getState = getObState
// ev.setState = setObState
ev.getResult = getObResult
ev.getAndEliminateResult = getAndEliminateObResult
ev.setResult = setObResult
ev.clear = clear
return ev
}
export default fsTask