w-ftp
Version:
A tool for FTP(FTP, SFTP).
603 lines (471 loc) • 16 kB
JavaScript
import path from 'path'
import fs from 'fs'
import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import last from 'lodash-es/last.js'
import map from 'lodash-es/map.js'
import genPm from 'wsemi/src/genPm.mjs'
import cint from 'wsemi/src/cint.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import isnum from 'wsemi/src/isnum.mjs'
import getFileName from 'wsemi/src/getFileName.mjs'
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
import ot from 'dayjs'
import SFTPClient from 'ssh2-sftp-client'
import sepTreeFolders from './sepTreeFolders.mjs'
import ftpSyncToLocal from './ftpSyncToLocal.mjs'
import ftpSyncToRemote from './ftpSyncToRemote.mjs'
function CoreSFTP(opt = {}) {
let Ftp = null
//hostname
let hostname = get(opt, 'hostname')
if (!isestr(hostname)) {
throw new Error(`hostname[${hostname}] is not an effective string`)
}
//port
let port = get(opt, 'port')
if (!ispint(port)) {
// throw new Error(`port[${port}] is not a positive integer`)
port = 22
}
port = cint(port)
//username
let username = get(opt, 'username')
if (!isestr(username)) {
throw new Error(`username[${username}] is not an effective string`)
}
//password
let password = get(opt, 'password')
if (!isestr(password)) {
throw new Error(`password[${password}] is not an effective string`)
}
//timeLimit
let timeLimit = get(opt, 'timeLimit')
if (!isnum(timeLimit)) {
timeLimit = 1 * 60 * 1000 //1hr
}
timeLimit = cint(timeLimit)
async function ftpConn() {
//sftp
Ftp = new SFTPClient()
// console.log('Ftp', Ftp)
//connect
await Ftp.connect({
host: hostname, //hostname or ip
port, // defaults to 22
username,
password,
// forceIPv4: false, // boolean (optional) Only connect via IPv4 address
// forceIPv6: false, // boolean (optional) Only connect via IPv6 address
// agent: process.env.SSH_AGENT, // string - Path to ssh-agent's UNIX socket
// privateKey: fs.readFileSync('/path/to/key'), // Buffer or string that contains
// passphrase: 'a pass phrase', // string - For an encrypted private key
// readyTimeout: 20000, // integer How long (in ms) to wait for the SSH handshake
// strictVendor: true, // boolean - Performs a strict server vendor check
// debug: myDebug,// function - Set this to a function that receives a single, string argument to get detailed (local) debug information.
// debug: (msg) => {
// console.log('debug', msg)
// },
// retries: 2, // integer. Number of times to retry connecting
// retry_factor: 2, // integer. Time factor used to calculate time between retries
// retry_minTimeout: 2000, // integer. Minimum timeout between attempts
})
// console.log('Ftp(connect)', Ftp)
return 'ok'
}
async function ftpLs(fdRemote = '.') {
let errTemp = null
//pm
let pm = genPm()
//check
if (Ftp === null) {
pm.reject(`FTP has not been initialized. please use 'conn' to construct FTP`)
return pm
}
//timeLimit
let t = setTimeout(() => {
pm.reject(`ftpLs timeout[${timeLimit}]`)
}, timeLimit)
//list
let res = await Ftp.list(fdRemote)
.catch((err) => {
errTemp = err
})
//clearTimeout
clearTimeout(t)
//check
if (errTemp !== null) {
pm.reject(errTemp)
return pm
}
//add
res = map(res, (v) => {
// console.log('v', v)
let d
d = ot(v.accessTime)
v.atime = d.format('YYYY-MM-DDTHH:mm:ssZ') //添加UTC存取時間, 為UTC不須再轉
d = ot(v.modifyTime)
v.mtime = d.format('YYYY-MM-DDTHH:mm:ssZ') //添加UTC修改時間, 為UTC不須再轉
v.isFolder = ftpIsFolderCore(v)
return v
})
//resolve
pm.resolve(res)
return pm
}
async function ftpQuit() {
//pm
let pm = genPm()
//check
if (Ftp === null) {
pm.reject(`FTP has not been initialized. please use 'conn' to construct FTP`)
return pm
}
//timeLimit
let t = setTimeout(() => {
//end
Ftp.end()
//clear
Ftp = null
pm.reject(`ftpQuit timeout[${timeLimit}]`)
}, timeLimit)
try {
//end
Ftp.end()
//clear
Ftp = null
//clearTimeout
clearTimeout(t)
pm.resolve('ok')
}
catch (err) {
pm.reject(err)
}
return pm
}
async function ftpMkdirCore(fpRemote) {
let recursive = true
return Ftp.mkdir(fpRemote, recursive)
}
async function ftpMkdir(fpRemote) {
//輸入為檔案路徑, 會解析出父資料夾並進行逐層創建
//sepTreeFolders
let sstf = sepTreeFolders(fpRemote)
// console.log('sstf', sstf)
//若有多層資料夾才mkdir
if (size(sstf) >= 1) {
//last
let sl = last(sstf)
// console.log('sl', sl)
//ftpMkdirCore
await ftpMkdirCore(sl)
}
return 'ok'
}
// async function ftpMlst(fpRemote) {
// return ftpRaw('mlst', fpRemote)
// }
// async function ftpSize(fpRemote) {
// return ftpRaw('size', fpRemote)
// }
async function ftpCwd(fpRemote) {
return Ftp.cwd(fpRemote)
}
async function ftpPwd() {
return Ftp.pwd()
}
async function ftpStateTar(fpRemote) {
//stat
let file = null
await Ftp.stat(fpRemote)
.then((res) => {
// console.log('Ftp.stat then', res)
// mode: 33279, // integer representing type and permissions
// uid: 1000, // user ID
// gid: 985, // group ID
// size: 5, // file size
// accessTime: 1566868566000, // Last access time. milliseconds
// modifyTime: 1566868566000, // last modify time. milliseconds
// isDirectory: false, // true if object is a directory
// isFile: true, // true if object is a file
// isBlockDevice: false, // true if object is a block device
// isCharacterDevice: false, // true if object is a character device
// isSymbolicLink: false, // true if object is a symbolic link
// isFIFO: false, // true if object is a FIFO
// isSocket: false // true if object is a socket
let d
d = ot(res.accessTime)
let atime = d.format('YYYY-MM-DDTHH:mm:ssZ') //添加UTC存取時間, 為UTC不須再轉
d = ot(res.modifyTime)
let mtime = d.format('YYYY-MM-DDTHH:mm:ssZ') //添加UTC修改時間, 為UTC不須再轉
file = {
...res,
atime,
mtime,
name: fpRemote,
}
})
.catch((err) => {
// console.log('Ftp.stat catch', err)
file = {
err: err.toString(),
}
})
// console.log('file', file)
return file
}
function ftpIsFolderCore(fd) {
//check
if (!iseobj(fd)) {
return false
}
//b
let b
//isDirectory
b = get(fd, 'isDirectory', null) === true
if (b) {
return b
}
//type
let type = get(fd, 'type', '')
b = type === 'd'
if (b) {
return b
}
return false
}
async function ftpIsFolder(fpRemote) {
//ftpStateTar
let fd = await ftpStateTar(fpRemote)
//ftpIsFolderCore
let b = ftpIsFolderCore(fd)
return b
}
function ftpIsFileCore(file) {
//check
if (!iseobj(file)) {
return false
}
//b
let b
//isFile
b = get(file, 'isFile', null) === true
if (b) {
return b
}
//type
let type = get(file, 'type', '')
b = type === '-'
if (b) {
return b
}
return b
}
async function ftpIsFile(fpRemote) {
//ftpStateTar
let file = await ftpStateTar(fpRemote)
//ftpIsFileCore
let b = ftpIsFileCore(file)
return b
}
async function ftpDownload(fpRemote, fpLocal, cbProcess = null) {
//pm
let pm = genPm()
//check
if (Ftp === null) {
pm.reject(`FTP has not been initialized. please use 'conn' to construct FTP`)
return pm
}
//fpRemote
if (!isestr(fpRemote)) {
pm.reject(`fpRemote[${fpRemote}] is not an effective string`)
return pm
}
//fpLocal
if (!isestr(fpLocal)) {
pm.reject(`fpLocal[${fpLocal}] is not an effective string`)
return pm
}
//fdLocal
let fdLocal = path.dirname(fpLocal)
// console.log('fdLocal', fdLocal)
//check fdLocal
if (!fsIsFolder(fdLocal)) {
fsCreateFolder(fdLocal)
}
//ftpStateTar
let file = await ftpStateTar(fpRemote)
// console.log('file', file)
//ftpIsFileCore
let b = ftpIsFileCore(file)
// console.log('b', b)
//check
if (!b) {
pm.reject(`fpRemote[${fpRemote}] is not a file`)
return pm
}
//check
if (!iseobj(file)) {
pm.reject(`fpRemote[${fpRemote}] does not have information`)
return pm
}
//fileSize
let fileSize = get(file, 'size', '')
//check
if (!isnum(fileSize)) {
pm.reject(`can not get the size of file[${fpRemote}]`)
return pm
}
fileSize = cint(fileSize)
// console.log('fileSize', fileSize)
//check
if (fsIsFile(fpLocal)) {
fs.unlinkSync(fpLocal)
}
// console.log('fpLocal', fpLocal)
//timeLimit
let t = setTimeout(() => {
pm.reject(`ftpDownload timeout[${timeLimit}]`)
}, timeLimit)
//fastGet
Ftp.fastGet(fpRemote, fpLocal, {
step: (total_transferred, chunk, total) => {
// console.log(total_transferred, chunk, total)
// 32768 32768 1384928
// 65536 32768 1384928
// ...
// 1376256 32768 1384928
// 1384928 8672 1384928
//cbProcess
if (isfun(cbProcess)) {
let r = total_transferred / total * 100
// console.log(`receving... ${dig((r), 1)}%`)
cbProcess({
progress: r,
name: getFileName(fpLocal),
fpRemote,
fpLocal,
})
}
},
})
.then(() => {
pm.resolve('ok')
})
.catch((err) => {
pm.reject(err)
})
.finally(() => {
//clearTimeout
clearTimeout(t)
})
//clearTimeout
clearTimeout(t)
return pm
}
async function ftpUpload(fpLocal, fpRemote, cbProcess) {
//pm
let pm = genPm()
//check
if (Ftp === null) {
pm.reject(`FTP has not been initialized. please use 'conn' to construct FTP`)
return pm
}
//fpRemote
if (!isestr(fpRemote)) {
pm.reject(`fpRemote[${fpRemote}] is not an effective string`)
return pm
}
//fpLocal
if (!isestr(fpLocal)) {
pm.reject(`fpLocal[${fpLocal}] is not an effective string`)
return pm
}
//check
if (!fsIsFile(fpLocal)) {
pm.reject(`fpLocal[${fpLocal}] is not a file`)
return pm
}
//ftpMkdir
await ftpMkdir(fpRemote)
//statSync
let stat = fs.statSync(fpLocal)
//fileSize
let fileSize = get(stat, 'size', 0)
//check
if (!isnum(fileSize)) {
pm.reject(`can not get the size of file[${fpLocal}]`)
return pm
}
fileSize = cint(fileSize)
// console.log('fileSize', fileSize)
// //bufferLocal
// let bufferLocal = fs.readFileSync(fpLocal)
// //createReadStream
// let reader = fs.createReadStream(fpLocal)
//timeLimit
let t = setTimeout(() => {
pm.reject(`ftpUploadCore timeout[${timeLimit}]`)
}, timeLimit)
//fastPut
Ftp.fastPut(fpLocal, fpRemote, {
step: (total_transferred, chunk, total) => {
// console.log(total_transferred, chunk, total)
// 32768 32768 1384928
// 65536 32768 1384928
// ...
// 1376256 32768 1384928
// 1384928 8672 1384928
//cbProcess
if (isfun(cbProcess)) {
let r = total_transferred / total * 100
// console.log(`receving... ${dig((r), 1)}%`)
cbProcess({
progress: r,
name: getFileName(fpLocal),
fpRemote,
fpLocal,
})
}
},
})
.then(() => {
pm.resolve('ok')
})
.catch((err) => {
pm.reject(err)
})
.finally(() => {
//clearTimeout
clearTimeout(t)
})
//clearTimeout
clearTimeout(t)
return pm
}
return {
conn: ftpConn,
quit: ftpQuit,
ls: ftpLs,
isFile: ftpIsFile,
isFolder: ftpIsFolder,
download: ftpDownload,
upload: ftpUpload,
syncToLocal: async (fdRemote, fdLocal, cbProcess) => {
return ftpSyncToLocal(fdRemote, fdLocal, cbProcess, { ftpLs, ftpDownload })
},
syncToRemote: async (fdLocal, fdRemote, cbProcess) => {
return ftpSyncToRemote(fdLocal, fdRemote, cbProcess, { ftpLs, ftpUpload })
},
stat: ftpStateTar,
mkdir: ftpMkdir,
cwd: ftpCwd,
pwd: ftpPwd,
}
}
export default CoreSFTP