From c7e81eb44b0096544f30fc155dcde32ffc77a19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=8C=E8=90=8C=E5=93=92=E8=B5=AB=E8=90=9D?= Date: Mon, 21 Aug 2023 03:12:27 -0700 Subject: [PATCH] :sparkles: Feature: finish the implemention of sftp manage --- package.json | 2 +- src/main/manage/apis/sftp.ts | 82 ++++++++++++++++++++++++++++-------- src/main/utils/sshClient.ts | 61 ++++++++++++++++++++++++++- yarn.lock | 8 ++-- 4 files changed, 129 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 0ba6d6ec..16c4bd6d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "mitt": "^3.0.0", "node-ssh-no-cpu-features": "^1.0.1", "nodejs-file-downloader": "^4.12.1", - "piclist": "^0.8.11", + "piclist": "^0.8.12", "pinia": "^2.1.4", "pinia-plugin-persistedstate": "^3.1.0", "qiniu": "^7.9.0", diff --git a/src/main/manage/apis/sftp.ts b/src/main/manage/apis/sftp.ts index 6d33cc10..5b8c0fda 100644 --- a/src/main/manage/apis/sftp.ts +++ b/src/main/manage/apis/sftp.ts @@ -5,7 +5,7 @@ import ManageLogger from '../utils/logger' import SSHClient from '~/main/utils/sshClient' // 错误格式化函数、新的下载器、并发异步任务池 -import { formatError, ConcurrencyPromisePool } from '../utils/common' +import { formatError } from '../utils/common' // 是否为图片的判断函数 import { isImage } from '@/manage/utils/common' @@ -20,7 +20,7 @@ import { IWindowList } from '#/types/enum' import { ipcMain, IpcMainEvent } from 'electron' // 上传下载任务队列 -import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue' +import UpDownTaskQueue, { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '../datastore/upDownTaskQueue' // 路径处理库 import path from 'path' @@ -187,7 +187,7 @@ class SftpApi { } try { await this.connectClient() - res = await this.ctx.execCommand(`cd ${prefix} && ls -la --time-style=long-iso`) + res = await this.ctx.execCommand(`cd "${prefix}" && ls -la --time-style=long-iso`) this.ctx.close() if (this.isRequestSuccess(res.code)) { const formatedLSRes = this.formatLSResult(res.stdout, prefix) @@ -258,7 +258,7 @@ class SftpApi { } try { await this.connectClient() - res = await this.ctx.execCommand(`cd ${prefix} && ls -la --time-style=long-iso`) + res = await this.ctx.execCommand(`cd "${prefix}" && ls -la --time-style=long-iso`) this.ctx.close() if (this.isRequestSuccess(res.code)) { const formatedLSRes = this.formatLSResult(res.stdout, prefix) @@ -352,14 +352,39 @@ class SftpApi { targetFilePath: key, targetFileBucket: bucketName, targetFileRegion: region, - noProgress: true - }) - instance.updateUploadTask({ - id, - progress: 0, - status: commonTaskStatus.failed, - finishTime: new Date().toLocaleString() + noProgress: false }) + try { + await this.connectClient() + const res = await this.ctx.putFile(filePath, `/${key.replace(/^\/+/, '')}`, { + fileMode: this.fileMode, + dirMode: this.dirMode + }) + this.ctx.close() + if (res) { + instance.updateUploadTask({ + id, + progress: 100, + status: uploadTaskSpecialStatus.uploaded, + finishTime: new Date().toLocaleString() + }) + } else { + instance.updateUploadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } + } catch (error) { + this.logParam(error, 'uploadBucketFile') + instance.updateUploadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } } return true } @@ -379,9 +404,8 @@ class SftpApi { } async downloadBucketFile (configMap: IStringKeyMap): Promise { - const { downloadPath, fileArray, maxDownloadFileCount } = configMap + const { downloadPath, fileArray } = configMap const instance = UpDownTaskQueue.getInstance() - const promises = [] as any for (const item of fileArray) { const { alias, bucketName, region, key, fileName } = item const savedFilePath = path.join(downloadPath, fileName) @@ -396,11 +420,35 @@ class SftpApi { sourceFileName: fileName, targetFilePath: savedFilePath }) + try { + await this.connectClient() + const res = await this.ctx.getFile(savedFilePath, `/${key.replace(/^\/+/, '')}`) + this.ctx.close() + if (res) { + instance.updateDownloadTask({ + id, + progress: 100, + status: downloadTaskSpecialStatus.downloaded, + finishTime: new Date().toLocaleString() + }) + } else { + instance.updateDownloadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } + } catch (error) { + this.logParam(error, 'downloadBucketFile') + instance.updateDownloadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } } - const pool = new ConcurrencyPromisePool(maxDownloadFileCount) - pool.all(promises).catch((error) => { - this.logParam(error, 'downloadBucketFile') - }) return true } } diff --git a/src/main/utils/sshClient.ts b/src/main/utils/sshClient.ts index 005275d9..e6c8a531 100644 --- a/src/main/utils/sshClient.ts +++ b/src/main/utils/sshClient.ts @@ -1,4 +1,5 @@ import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features' +import path from 'path' import { ISftpPlistConfig } from 'piclist/dist/types' class SSHClient { @@ -67,8 +68,9 @@ class SSHClient { try { remote = this.changeWinStylePathToUnix(remote) local = this.changeWinStylePathToUnix(local) - console.log(`remote: ${remote}, local: ${local}`) - await SSHClient.client.getFile(local, remote) + await SSHClient.client.getFile(local, remote, undefined, { + concurrency: 1 + }) return true } catch (err: any) { console.log(err) @@ -76,6 +78,61 @@ class SSHClient { } } + async putFile (local: string, remote: string, config: { + fileMode?: string + dirMode?: string + } = {}): Promise { + if (!this._isConnected) { + throw new Error('SSH 未连接') + } + try { + remote = this.changeWinStylePathToUnix(remote) + await this.mkdir(path.dirname(remote).replace(/^\/+|\/+$/g, ''), config) + await SSHClient.client.putFile(local, remote) + const fileMode = config.fileMode || '0644' + if (fileMode !== '0644') { + const script = `chmod ${fileMode} "${remote}"` + return await this.exec(script) + } + return true + } catch (err: any) { + console.log(err) + return false + } + } + + async mkdir (dirPath: string, config: { + dirMode?: string + } = {}): Promise { + if (!this._isConnected) { + throw new Error('SSH 未连接') + } + try { + const directoryMode = config.dirMode || '0755' + if (directoryMode === '0755') { + const script = `mkdir -p "${dirPath}"` + return await this.exec(script) + } else { + const dirs = dirPath.split('/') + let currentPath = '' + for (const dir of dirs) { + if (dir) { + currentPath += `/${dir}` + const script = `mkdir "${currentPath}" && chmod ${directoryMode} "${currentPath}"` + const result = await this.exec(script) + if (!result) { + return false + } + } + } + return true + } + } catch (err: any) { + console.log(err) + return false + } + } + get isConnected (): boolean { return SSHClient.client.isConnected() } diff --git a/yarn.lock b/yarn.lock index 34975ee7..51ec3981 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10973,10 +10973,10 @@ performance-now@^2.1.0: resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -piclist@^0.8.11: - version "0.8.11" - resolved "https://registry.npmjs.org/piclist/-/piclist-0.8.11.tgz#0fd6d690f9eb9099cea161d0d38d24d81df3a7cc" - integrity sha512-rgUw4x7gk3IpfzG8kONW7oQddWhylkWNSSucavQqlZeR6/XDPXZj+BBy0enzyi8GM/em0U456HHhP3ncJJjxnQ== +piclist@^0.8.12: + version "0.8.12" + resolved "https://registry.npmjs.org/piclist/-/piclist-0.8.12.tgz#b9ca7311f42bf62b5d3f20f36dce0f61c93658b6" + integrity sha512-9I93JsOauw7I7Jen6H0RWyxW7dXtiqlU0YOsePwxfMgBSKjYJ0Gne5jkwTLZR6NdR/Z9nhM+Vn7kvmY3V0ipyw== dependencies: "@picgo/i18n" "^1.0.0" "@picgo/store" "^2.0.4"