🎉 重构传输模块.
This commit is contained in:
@@ -41,6 +41,17 @@ export function readFileText(e: File, encoding = 'UTF-8'): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭 fileReader
|
||||||
|
export function closeFileReader(reader: FileReader) {
|
||||||
|
// 清理资源
|
||||||
|
if (reader.readyState === FileReader.LOADING) {
|
||||||
|
reader.abort();
|
||||||
|
}
|
||||||
|
reader.onload = null;
|
||||||
|
reader.onerror = null;
|
||||||
|
reader.onabort = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析路径类型
|
* 解析路径类型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<component :is="option.icon" />
|
<component :is="option.icon" />
|
||||||
<!-- 数量 -->
|
<!-- 数量 -->
|
||||||
<span class="status-count">
|
<span class="status-count">
|
||||||
{{ transferManager.transferList.filter(s => s.status === option.value).length }}
|
{{ transferTasks.filter(s => s.state.status === option.value).length }}
|
||||||
</span>
|
</span>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-space>
|
</a-space>
|
||||||
@@ -48,15 +48,15 @@
|
|||||||
max-height="100%"
|
max-height="100%"
|
||||||
:hoverable="true"
|
:hoverable="true"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:data="transferManager.transferList">
|
:data="transferTasks">
|
||||||
<!-- 空数据 -->
|
<!-- 空数据 -->
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<a-empty class="list-empty" description="无传输文件" />
|
<a-empty class="list-empty" description="无传输文件" />
|
||||||
</template>
|
</template>
|
||||||
<!-- 数据 -->
|
<!-- 数据 -->
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<!-- 传输 item -->
|
<!-- 传输任务 -->
|
||||||
<transfer-item v-show="filterItem(item)" :item="item" />
|
<transfer-task v-show="filterItem(item)" :task="item" />
|
||||||
</template>
|
</template>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -70,13 +70,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SftpTransferItem } from '@/views/terminal/interfaces';
|
import type { IFileTransferTask } from '@/views/terminal/interfaces';
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import useVisible from '@/hooks/visible';
|
import useVisible from '@/hooks/visible';
|
||||||
import { useDictStore, useTerminalStore } from '@/store';
|
import { useDictStore, useTerminalStore } from '@/store';
|
||||||
import { transferStatusKey } from '../../types/const';
|
import { transferStatusKey } from '../../types/const';
|
||||||
import TransferItem from './transfer-item.vue';
|
import TransferTask from './transfer-task.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['closed']);
|
const emits = defineEmits(['closed']);
|
||||||
|
|
||||||
@@ -87,6 +87,10 @@
|
|||||||
|
|
||||||
const filterStatus = ref<string>();
|
const filterStatus = ref<string>();
|
||||||
|
|
||||||
|
const transferTasks = computed(() => {
|
||||||
|
return [...transferManager.sftp.tasks, ...transferManager.rdp.tasks];
|
||||||
|
});
|
||||||
|
|
||||||
// 打开
|
// 打开
|
||||||
const open = () => {
|
const open = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
@@ -105,13 +109,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 过滤传输行
|
// 过滤传输行
|
||||||
const filterItem = (item: SftpTransferItem) => {
|
const filterItem = (task: IFileTransferTask) => {
|
||||||
return !filterStatus.value || item.status === filterStatus.value;
|
return !filterStatus.value || task.state.status === filterStatus.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移除全部任务
|
// 移除全部任务
|
||||||
const removeAllTask = () => {
|
const removeAllTask = () => {
|
||||||
transferManager.cancelAllTransfer();
|
transferManager.sftp.cancelAllTransfer();
|
||||||
|
transferManager.rdp.cancelAllTransfer();
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,46 +4,46 @@
|
|||||||
<!-- 左侧图标 -->
|
<!-- 左侧图标 -->
|
||||||
<div class="transfer-item-left">
|
<div class="transfer-item-left">
|
||||||
<span class="file-icon">
|
<span class="file-icon">
|
||||||
<icon-upload v-if="item.type === TransferType.UPLOAD" />
|
<icon-upload v-if="task.type === TransferType.UPLOAD" />
|
||||||
<icon-download v-else-if="item.type === TransferType.DOWNLOAD" />
|
<icon-download v-else-if="task.type === TransferType.DOWNLOAD" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 中间信息 -->
|
<!-- 中间信息 -->
|
||||||
<div class="transfer-item-center">
|
<div class="transfer-item-center">
|
||||||
<!-- 文件名称 -->
|
<!-- 文件名称 -->
|
||||||
<span class="file-name text-copy"
|
<span class="file-name text-copy"
|
||||||
:title="item.name"
|
:title="task.fileItem.name"
|
||||||
@click="copy(item.name)">
|
@click="copy(task.fileItem.name)">
|
||||||
{{ item.name }}
|
{{ task.fileItem.name }}
|
||||||
</span>
|
</span>
|
||||||
<!-- 传输进度 -->
|
<!-- 传输进度 -->
|
||||||
<span class="transfer-progress">
|
<span class="transfer-progress">
|
||||||
<!-- 当前大小 -->
|
<!-- 当前大小 -->
|
||||||
<span v-if="item.status === TransferStatus.TRANSFERRING">{{ getFileSize(item.currentSize) }}</span>
|
<span v-if="task.state.status === TransferStatus.TRANSFERRING && task.fileItem.unknownSize !== true">{{ getFileSize(task.state.currentSize) }}</span>
|
||||||
<span class="mx4" v-if="item.status === TransferStatus.TRANSFERRING">/</span>
|
<span class="mx4" v-if="task.state.status === TransferStatus.TRANSFERRING && task.fileItem.unknownSize !== true">/</span>
|
||||||
<!-- 总大小 -->
|
<!-- 总大小 -->
|
||||||
<span>{{ getFileSize(item.totalSize) }}</span>
|
<span>{{ getFileSize(task.state.totalSize) }}</span>
|
||||||
<!-- 进度百分比 -->
|
<!-- 进度百分比 -->
|
||||||
<span class="ml8" v-if="item.status === TransferStatus.TRANSFERRING">
|
<span class="ml8" v-if="task.state.status === TransferStatus.TRANSFERRING && task.fileItem.unknownSize !== true">
|
||||||
{{ item.progress }}%
|
{{ task.state.progress }}%
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<!-- 目标目录 -->
|
<!-- 目标目录 -->
|
||||||
<span class="target-path text-copy"
|
<span class="target-path text-copy"
|
||||||
:title="item.parentPath"
|
:title="task.fileItem.parentPath"
|
||||||
@click="copy(item.parentPath)">
|
@click="copy(task.fileItem.parentPath)">
|
||||||
{{ item.parentPath }}
|
{{ task.fileItem.parentPath }}
|
||||||
</span>
|
</span>
|
||||||
<!-- 错误信息 -->
|
<!-- 错误信息 -->
|
||||||
<a-tooltip v-if="item.errorMessage"
|
<a-tooltip v-if="task.state.errorMessage"
|
||||||
position="top"
|
position="top"
|
||||||
:mini="true"
|
:mini="true"
|
||||||
:auto-fix-position="false"
|
:auto-fix-position="false"
|
||||||
content-class="terminal-tooltip-content"
|
content-class="terminal-tooltip-content"
|
||||||
arrow-class="terminal-tooltip-content"
|
arrow-class="terminal-tooltip-content"
|
||||||
:content="item.errorMessage">
|
:content="task.state.errorMessage">
|
||||||
<span class="error-message">
|
<span class="error-message">
|
||||||
{{ item.errorMessage }}
|
{{ task.state.errorMessage }}
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,18 +52,20 @@
|
|||||||
<!-- 传输状态 -->
|
<!-- 传输状态 -->
|
||||||
<div class="transfer-item-right-progress">
|
<div class="transfer-item-right-progress">
|
||||||
<!-- 等待传输 -->
|
<!-- 等待传输 -->
|
||||||
<icon-clock-circle v-if="item.status === TransferStatus.WAITING" />
|
<icon-clock-circle v-if="task.state.status === TransferStatus.WAITING" />
|
||||||
|
<!-- 传输中-但不知道文件大小 -->
|
||||||
|
<icon-loading v-else-if="task.state.status === TransferStatus.TRANSFERRING && task.fileItem.unknownSize === true" />
|
||||||
<!-- 传输进度 -->
|
<!-- 传输进度 -->
|
||||||
<a-progress v-else
|
<a-progress v-else
|
||||||
type="circle"
|
type="circle"
|
||||||
size="mini"
|
size="mini"
|
||||||
:status="getDictValue(transferStatusKey, item.status, 'status')"
|
:status="getDictValue(transferStatusKey, task.state.status, 'status')"
|
||||||
:percent="item.currentSize / item.totalSize" />
|
:percent="task.state.currentSize / task.state.totalSize" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 传输操作 -->
|
<!-- 传输操作 -->
|
||||||
<div class="transfer-item-right-actions">
|
<div class="transfer-item-right-actions">
|
||||||
<!-- 关闭 -->
|
<!-- 关闭 -->
|
||||||
<span class="close-icon" @click="removeTask(item.fileId)">
|
<span class="close-icon" @click="removeTask">
|
||||||
<icon-close />
|
<icon-close />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,21 +76,21 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'transferItem'
|
name: 'transferTask'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SftpTransferItem } from '@/views/terminal/interfaces';
|
import type { IFileTransferTask } from '@/views/terminal/interfaces';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import useVisible from '@/hooks/visible';
|
import useVisible from '@/hooks/visible';
|
||||||
import { useDictStore, useTerminalStore } from '@/store';
|
import { useDictStore, useTerminalStore } from '@/store';
|
||||||
import { copy } from '@/hooks/copy';
|
import { copy } from '@/hooks/copy';
|
||||||
import { getFileSize } from '@/utils/file';
|
import { getFileSize } from '@/utils/file';
|
||||||
import { TransferStatus, TransferType, transferStatusKey } from '../../types/const';
|
import { TransferStatus, TransferType, transferStatusKey, TransferSource } from '../../types/const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: SftpTransferItem;
|
task: IFileTransferTask;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { transferManager } = useTerminalStore();
|
const { transferManager } = useTerminalStore();
|
||||||
@@ -97,8 +99,15 @@
|
|||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
|
|
||||||
// 移除任务
|
// 移除任务
|
||||||
const removeTask = (fileId: string) => {
|
const removeTask = () => {
|
||||||
transferManager.cancelTransfer(fileId);
|
const fileId = props.task.fileId;
|
||||||
|
let manager;
|
||||||
|
if (props.task.source === TransferSource.SFTP) {
|
||||||
|
manager = transferManager.sftp;
|
||||||
|
} else if (props.task.source === TransferSource.RDP) {
|
||||||
|
manager = transferManager.rdp;
|
||||||
|
}
|
||||||
|
manager?.cancelTransfer(fileId);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -121,8 +121,7 @@
|
|||||||
// 获取上传的文件
|
// 获取上传的文件
|
||||||
const files = fileList.value.map(s => s.file as File);
|
const files = fileList.value.map(s => s.file as File);
|
||||||
// 普通上传
|
// 普通上传
|
||||||
transferManager.addUpload(hostId.value, parentPath.value, files);
|
await transferManager.sftp.addUpload(hostId.value, parentPath.value, files);
|
||||||
Message.success('已开始上传, 点击右侧传输列表查看进度');
|
|
||||||
// 清空
|
// 清空
|
||||||
handlerClear();
|
handlerClear();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 下载文件
|
// 下载文件
|
||||||
const downloadFiles = (paths: Array<string>, clear: boolean) => {
|
const downloadFiles = async (paths: Array<string>, clear: boolean) => {
|
||||||
if (!paths.length) {
|
if (!paths.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,10 +172,9 @@
|
|||||||
if (clear) {
|
if (clear) {
|
||||||
selectFiles.value = [];
|
selectFiles.value = [];
|
||||||
}
|
}
|
||||||
Message.success('已开始下载, 点击右侧传输列表查看进度');
|
|
||||||
// 添加普通文件到下载队列
|
// 添加普通文件到下载队列
|
||||||
const normalFiles = files.filter(s => !s.isDir);
|
const normalFiles = files.filter(s => !s.isDir);
|
||||||
transferManager.addDownload(props.item.hostId as number, currentPath.value, normalFiles);
|
await transferManager.sftp.addDownload(props.item.hostId as number, currentPath.value, normalFiles);
|
||||||
// 将文件夹展开普通文件
|
// 将文件夹展开普通文件
|
||||||
const directoryPaths = files.filter(s => s.isDir).map(s => s.path);
|
const directoryPaths = files.filter(s => s.isDir).map(s => s.path);
|
||||||
if (directoryPaths.length) {
|
if (directoryPaths.length) {
|
||||||
@@ -283,7 +282,7 @@
|
|||||||
if (!checkResult(result, msg)) {
|
if (!checkResult(result, msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
transferManager.addDownload(props.item.hostId as number, currentPath, list);
|
transferManager.sftp.addDownload(props.item.hostId as number, currentPath, list);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化会话
|
// 初始化会话
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export interface ITerminalSession<Status extends ReactiveSessionStatus = Reactiv
|
|||||||
export interface ISshSession extends ITerminalSession, IDomViewportHandler {
|
export interface ISshSession extends ITerminalSession, IDomViewportHandler {
|
||||||
// terminal 实例
|
// terminal 实例
|
||||||
inst: Terminal;
|
inst: Terminal;
|
||||||
// 元素对象
|
// 会话配置
|
||||||
config: SshInitConfig;
|
config: SshInitConfig;
|
||||||
// 处理器
|
// 处理器
|
||||||
handler: ISshSessionHandler;
|
handler: ISshSessionHandler;
|
||||||
@@ -164,7 +164,8 @@ export interface IGuacdSession extends ITerminalSession<GuacdReactiveSessionStat
|
|||||||
|
|
||||||
// RDP 会话定义
|
// RDP 会话定义
|
||||||
export interface IRdpSession extends IGuacdSession {
|
export interface IRdpSession extends IGuacdSession {
|
||||||
// 元素对象
|
fileSystemName: string;
|
||||||
|
// 会话配置
|
||||||
config: GuacdInitConfig;
|
config: GuacdInitConfig;
|
||||||
// 视图处理器
|
// 视图处理器
|
||||||
displayHandler: IRdpSessionDisplayHandler;
|
displayHandler: IRdpSessionDisplayHandler;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { FileTransferItem, FileTransferReactiveState, IFileTransferTask } from '@/views/terminal/interfaces';
|
||||||
|
import type { Reactive } from 'vue';
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import { nextId } from '@/utils';
|
||||||
|
import { TransferStatus } from '@/views/terminal/types/const';
|
||||||
|
|
||||||
|
// 文件传输任务基类
|
||||||
|
export default abstract class BaseFileTransferTask implements IFileTransferTask {
|
||||||
|
public type: string;
|
||||||
|
public source: string;
|
||||||
|
public fileId: string;
|
||||||
|
public hostId: number;
|
||||||
|
public sessionKey: string;
|
||||||
|
// 文件
|
||||||
|
public fileItem: FileTransferItem;
|
||||||
|
// 状态
|
||||||
|
public state: Reactive<FileTransferReactiveState>;
|
||||||
|
|
||||||
|
protected constructor(type: string, source: string,
|
||||||
|
hostId: number, sessionKey: string,
|
||||||
|
fileItem: FileTransferItem,
|
||||||
|
state: Partial<FileTransferReactiveState>) {
|
||||||
|
this.type = type;
|
||||||
|
this.source = source;
|
||||||
|
this.fileId = nextId(10);
|
||||||
|
this.hostId = hostId;
|
||||||
|
this.sessionKey = sessionKey;
|
||||||
|
this.fileItem = fileItem;
|
||||||
|
this.state = reactive({
|
||||||
|
currentSize: 0,
|
||||||
|
totalSize: fileItem.size,
|
||||||
|
progress: 0,
|
||||||
|
status: TransferStatus.WAITING,
|
||||||
|
errorMessage: undefined,
|
||||||
|
finished: false,
|
||||||
|
aborted: false,
|
||||||
|
...state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始
|
||||||
|
abstract start(): void;
|
||||||
|
|
||||||
|
// 完成
|
||||||
|
abstract finish(): void;
|
||||||
|
|
||||||
|
// 失败
|
||||||
|
abstract error(): void;
|
||||||
|
|
||||||
|
// 中断
|
||||||
|
abstract abort(): void;
|
||||||
|
|
||||||
|
// 传输完成回调
|
||||||
|
abstract onFinish(): void;
|
||||||
|
|
||||||
|
// 传输失败回调
|
||||||
|
abstract onError(msg: string | undefined): void;
|
||||||
|
|
||||||
|
// 传输中断回调
|
||||||
|
abstract onAbort(): void;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import type { ISetTransferClient, FileTransferItem } from '@/views/terminal/interfaces';
|
||||||
|
import { TransferOperator, TransferStatus, TerminalMessages, TransferSource } from '../../types/const';
|
||||||
|
import { getPath } from '@/utils/file';
|
||||||
|
import BaseFileTransferTask from './base-file-transfer-task';
|
||||||
|
|
||||||
|
// sftp 传输任务一定义
|
||||||
|
export default abstract class SftpBaseTransferTask extends BaseFileTransferTask implements ISetTransferClient<WebSocket> {
|
||||||
|
|
||||||
|
protected client?: WebSocket;
|
||||||
|
|
||||||
|
protected constructor(type: string,
|
||||||
|
hostId: number,
|
||||||
|
fileItem: FileTransferItem) {
|
||||||
|
super(type, TransferSource.SFTP, hostId, undefined as unknown as string, fileItem, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置传输客户端
|
||||||
|
setClient(client: WebSocket) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始
|
||||||
|
start() {
|
||||||
|
this.state.status = TransferStatus.TRANSFERRING;
|
||||||
|
// 发送开始信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.START,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.hostId,
|
||||||
|
path: getPath(this.fileItem.parentPath + '/' + this.fileItem.name),
|
||||||
|
paths: this.fileItem.paths,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 完成
|
||||||
|
finish() {
|
||||||
|
this.state.finished = true;
|
||||||
|
this.state.progress = 100;
|
||||||
|
this.state.status = TransferStatus.SUCCESS;
|
||||||
|
// 发送完成的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.FINISH,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.hostId,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 失败
|
||||||
|
error() {
|
||||||
|
this.state.finished = true;
|
||||||
|
this.state.status = TransferStatus.ERROR;
|
||||||
|
// 发送上传失败的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.ERROR,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.hostId,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中断
|
||||||
|
abort() {
|
||||||
|
this.state.aborted = true;
|
||||||
|
// 发送中断的信息
|
||||||
|
this.client?.send(JSON.stringify({
|
||||||
|
operator: TransferOperator.ABORT,
|
||||||
|
type: this.type,
|
||||||
|
hostId: this.hostId,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 失败回调
|
||||||
|
onError(msg: string | undefined) {
|
||||||
|
this.state.finished = true;
|
||||||
|
this.state.status = TransferStatus.ERROR;
|
||||||
|
this.state.errorMessage = msg || TerminalMessages.fileTransferError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成回调
|
||||||
|
onFinish() {
|
||||||
|
this.state.finished = true;
|
||||||
|
this.state.progress = 100;
|
||||||
|
this.state.status = TransferStatus.SUCCESS;
|
||||||
|
this.state.currentSize = this.state.totalSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中断回调
|
||||||
|
onAbort() {
|
||||||
|
this.state.aborted = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { FileTransferItem, IFileDownloadTask } from '@/views/terminal/interfaces';
|
||||||
|
import { TransferStatus, TerminalMessages } from '../../types/const';
|
||||||
|
import { getFileName, openDownloadFile } from '@/utils/file';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
import { getDownloadTransferUrl } from '@/api/terminal/terminal-sftp';
|
||||||
|
import SftpBaseTransferTask from './sftp-base-transfer-task';
|
||||||
|
|
||||||
|
// sftp 下载任务实现
|
||||||
|
export default class SftpFileDownloadTask extends SftpBaseTransferTask implements IFileDownloadTask {
|
||||||
|
|
||||||
|
constructor(type: string, hostId: number, fileItem: FileTransferItem) {
|
||||||
|
super(type, hostId, fileItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始回调
|
||||||
|
onStart(channelId: string, token: string) {
|
||||||
|
// 获取下载 url
|
||||||
|
const url = getDownloadTransferUrl(channelId, token);
|
||||||
|
// 打开
|
||||||
|
openDownloadFile(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度回调
|
||||||
|
onProgress(totalSize: number | undefined, currentSize: number | undefined) {
|
||||||
|
if (totalSize) {
|
||||||
|
this.state.totalSize = totalSize;
|
||||||
|
}
|
||||||
|
if (currentSize) {
|
||||||
|
this.state.currentSize = currentSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 完成回调
|
||||||
|
onFinish() {
|
||||||
|
super.onFinish();
|
||||||
|
if (this.state.aborted) {
|
||||||
|
// 中断则不触发下载
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.state.totalSize === 0) {
|
||||||
|
// 空文件直接触发下载
|
||||||
|
try {
|
||||||
|
// 触发下载
|
||||||
|
saveAs(new Blob([], {
|
||||||
|
type: 'application/octet-stream'
|
||||||
|
}), getFileName(this.fileItem.name));
|
||||||
|
this.state.status = TransferStatus.SUCCESS;
|
||||||
|
} catch (e) {
|
||||||
|
this.state.status = TransferStatus.ERROR;
|
||||||
|
this.state.errorMessage = TerminalMessages.fileSaveError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.state.status = TransferStatus.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import type { FileTransferItem, IFileUploadTask } from '@/views/terminal/interfaces';
|
||||||
|
import { closeFileReader } from '@/utils/file';
|
||||||
|
import SftpBaseTransferTask from './sftp-base-transfer-task';
|
||||||
|
|
||||||
|
// 512 KB
|
||||||
|
export const PART_SIZE = 512 * 1024;
|
||||||
|
|
||||||
|
// sftp 上传任务实现
|
||||||
|
export default class SftpFileUploadTask extends SftpBaseTransferTask implements IFileUploadTask {
|
||||||
|
|
||||||
|
private currentPart: number;
|
||||||
|
private readonly totalPart: number;
|
||||||
|
|
||||||
|
constructor(type: string, hostId: number, fileItem: FileTransferItem) {
|
||||||
|
super(type, hostId, fileItem);
|
||||||
|
this.currentPart = 0;
|
||||||
|
this.totalPart = Math.ceil(fileItem.size / PART_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传完成
|
||||||
|
finish() {
|
||||||
|
super.finish();
|
||||||
|
// 释放资源
|
||||||
|
this.fileItem.file = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传下一个分片
|
||||||
|
async onNextPart() {
|
||||||
|
// 完成或者中断直接跳过
|
||||||
|
if (this.state.aborted || this.state.finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.hasNextPart()) {
|
||||||
|
try {
|
||||||
|
// 有下一个分片则上传
|
||||||
|
await this.uploadNextPart();
|
||||||
|
} catch (e) {
|
||||||
|
// 读取文件失败
|
||||||
|
this.error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行上传下一分片
|
||||||
|
private async uploadNextPart() {
|
||||||
|
// 读取数据
|
||||||
|
const start = this.currentPart * PART_SIZE;
|
||||||
|
const end = Math.min(this.fileItem.size, start + PART_SIZE);
|
||||||
|
const chunk = (this.fileItem.file as File).slice(start, end);
|
||||||
|
const reader = new FileReader();
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await new Promise((resolve, reject) => {
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
reader.readAsArrayBuffer(chunk);
|
||||||
|
});
|
||||||
|
// 发送数据
|
||||||
|
this.client?.send(arrayBuffer as ArrayBuffer);
|
||||||
|
this.currentPart++;
|
||||||
|
this.state.currentSize += (end - start);
|
||||||
|
} finally {
|
||||||
|
// 释放资源
|
||||||
|
closeFileReader(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否有下一个分片
|
||||||
|
private hasNextPart() {
|
||||||
|
return this.currentPart < this.totalPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import type { SftpTransferItem } from '@/views/terminal/interfaces';
|
|
||||||
import { TransferStatus } from '../../types/const';
|
|
||||||
import { getFileName, openDownloadFile } from '@/utils/file';
|
|
||||||
import { saveAs } from 'file-saver';
|
|
||||||
import { getDownloadTransferUrl } from '@/api/terminal/terminal-sftp';
|
|
||||||
import SftpTransferHandler from './sftp-transfer-handler';
|
|
||||||
|
|
||||||
// sftp 下载器实现
|
|
||||||
export default class SftpTransferDownloader extends SftpTransferHandler {
|
|
||||||
|
|
||||||
constructor(type: string, item: SftpTransferItem, client: WebSocket) {
|
|
||||||
super(type, item, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始回调
|
|
||||||
onStart(channelId: string, token: string) {
|
|
||||||
super.onStart(channelId, token);
|
|
||||||
// 获取下载 url
|
|
||||||
const url = getDownloadTransferUrl(channelId, token);
|
|
||||||
// 打开
|
|
||||||
openDownloadFile(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成回调
|
|
||||||
onFinish() {
|
|
||||||
super.onFinish();
|
|
||||||
if (this.aborted) {
|
|
||||||
// 中断则不触发下载
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.item.totalSize === 0) {
|
|
||||||
// 空文件直接触发下载
|
|
||||||
try {
|
|
||||||
// 触发下载
|
|
||||||
saveAs(new Blob([], {
|
|
||||||
type: 'application/octet-stream'
|
|
||||||
}), getFileName(this.item.name));
|
|
||||||
this.item.status = TransferStatus.SUCCESS;
|
|
||||||
} catch (e) {
|
|
||||||
this.item.status = TransferStatus.ERROR;
|
|
||||||
this.item.errorMessage = '保存失败';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.item.status = TransferStatus.SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import type { ISftpTransferHandler, SftpTransferItem } from '@/views/terminal/interfaces';
|
|
||||||
import { TransferOperator, TransferStatus } from '../../types/const';
|
|
||||||
import { getPath } from '@/utils/file';
|
|
||||||
|
|
||||||
// sftp 传输处理器定义
|
|
||||||
export default abstract class SftpTransferHandler implements ISftpTransferHandler {
|
|
||||||
|
|
||||||
public type: string;
|
|
||||||
public finished: boolean;
|
|
||||||
public aborted: boolean;
|
|
||||||
protected client: WebSocket;
|
|
||||||
protected item: SftpTransferItem;
|
|
||||||
|
|
||||||
protected constructor(type: string, item: SftpTransferItem, client: WebSocket) {
|
|
||||||
this.type = type;
|
|
||||||
this.finished = false;
|
|
||||||
this.aborted = false;
|
|
||||||
this.item = item;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始
|
|
||||||
start() {
|
|
||||||
this.item.status = TransferStatus.TRANSFERRING;
|
|
||||||
// 发送开始信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
operator: TransferOperator.START,
|
|
||||||
type: this.type,
|
|
||||||
path: getPath(this.item.parentPath + '/' + this.item.name),
|
|
||||||
hostId: this.item.hostId,
|
|
||||||
paths: this.item.paths,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 完成
|
|
||||||
finish() {
|
|
||||||
this.finished = true;
|
|
||||||
this.item.status = TransferStatus.SUCCESS;
|
|
||||||
// 发送完成的信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
operator: TransferOperator.FINISH,
|
|
||||||
type: this.type,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 失败
|
|
||||||
error() {
|
|
||||||
this.finished = true;
|
|
||||||
this.item.status = TransferStatus.ERROR;
|
|
||||||
// 发送上传失败的信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
operator: TransferOperator.ERROR,
|
|
||||||
type: this.type,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 中断
|
|
||||||
abort() {
|
|
||||||
this.aborted = true;
|
|
||||||
// 发送中断的信息
|
|
||||||
this.client?.send(JSON.stringify({
|
|
||||||
operator: TransferOperator.ABORT,
|
|
||||||
type: this.type,
|
|
||||||
hostId: this.item.hostId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否有下一个分片
|
|
||||||
hasNextPart() {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 下一页分片回调
|
|
||||||
onNextPart() {
|
|
||||||
return undefined as unknown as any;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始回调
|
|
||||||
onStart(channelId: string, token: string) {
|
|
||||||
};
|
|
||||||
|
|
||||||
// 进度回调
|
|
||||||
onProgress(totalSize: number | undefined, currentSize: number | undefined) {
|
|
||||||
if (this.item) {
|
|
||||||
if (totalSize) {
|
|
||||||
this.item.totalSize = totalSize;
|
|
||||||
}
|
|
||||||
if (currentSize) {
|
|
||||||
this.item.currentSize = currentSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 失败回调
|
|
||||||
onError(msg: string | undefined) {
|
|
||||||
this.finished = true;
|
|
||||||
this.item.status = TransferStatus.ERROR;
|
|
||||||
this.item.errorMessage = msg || '传输失败';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完成回调
|
|
||||||
onFinish() {
|
|
||||||
this.finished = true;
|
|
||||||
this.item.status = TransferStatus.SUCCESS;
|
|
||||||
this.item.currentSize = this.item.totalSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 中断回调
|
|
||||||
onAbort() {
|
|
||||||
this.aborted = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,105 +1,89 @@
|
|||||||
import type { ISftpTransferHandler, ISftpTransferManager, SftpFile, SftpTransferItem, TransferOperatorResponse } from '@/views/terminal/interfaces';
|
import type {
|
||||||
|
FileTransferTaskType,
|
||||||
|
ISetTransferClient,
|
||||||
|
ISftpTransferManager,
|
||||||
|
MaybeFileTransferTask,
|
||||||
|
SftpFile,
|
||||||
|
TransferOperatorResponse
|
||||||
|
} from '@/views/terminal/interfaces';
|
||||||
import { TerminalMessages, TransferReceiver, TransferStatus, TransferType } from '../../types/const';
|
import { TerminalMessages, TransferReceiver, TransferStatus, TransferType } from '../../types/const';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { getTerminalTransferToken, openTerminalTransferChannel } from '@/api/terminal/terminal';
|
import { getTerminalTransferToken, openTerminalTransferChannel } from '@/api/terminal/terminal';
|
||||||
import { nextId } from '@/utils';
|
import BaseTransferManager from './base-transfer-manager';
|
||||||
import SftpTransferUploader from './sftp-transfer-uploader';
|
import SftpFileUploadTask from './sftp-file-upload-task';
|
||||||
import SftpTransferDownloader from './sftp-transfer-downloader';
|
import SftpFileDownloadTask from './sftp-file-download-task';
|
||||||
|
|
||||||
// sftp 传输管理器实现
|
// sftp 传输管理器实现
|
||||||
export default class SftpTransferManager implements ISftpTransferManager {
|
export default class SftpTransferManager extends BaseTransferManager implements ISftpTransferManager {
|
||||||
|
|
||||||
private client?: WebSocket;
|
private client?: WebSocket;
|
||||||
|
|
||||||
private run: boolean;
|
private run: boolean;
|
||||||
|
|
||||||
private progressIntervalId?: any;
|
private currentTask?: FileTransferTaskType;
|
||||||
|
|
||||||
private currentItem?: SftpTransferItem;
|
|
||||||
|
|
||||||
private currentTransfer?: ISftpTransferHandler;
|
|
||||||
|
|
||||||
public transferList: Array<SftpTransferItem>;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.run = false;
|
this.run = false;
|
||||||
this.transferList = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加上传任务
|
// 添加上传任务
|
||||||
addUpload(hostId: number, parentPath: string, files: Array<File>) {
|
async addUpload(hostId: number, parentPath: string, files: Array<File>) {
|
||||||
// 转为上传任务
|
Message.info(TerminalMessages.fileUploading);
|
||||||
const items = files.map(s => {
|
// 创建任务
|
||||||
return {
|
for (let file of files) {
|
||||||
fileId: nextId(10),
|
const task = new SftpFileUploadTask(TransferType.UPLOAD, hostId, {
|
||||||
type: TransferType.UPLOAD,
|
name: file.webkitRelativePath || file.name,
|
||||||
hostId: hostId,
|
|
||||||
name: s.webkitRelativePath || s.name,
|
|
||||||
currentSize: 0,
|
|
||||||
totalSize: s.size,
|
|
||||||
progress: 0,
|
|
||||||
status: TransferStatus.WAITING,
|
|
||||||
parentPath: parentPath,
|
parentPath: parentPath,
|
||||||
file: s
|
size: file.size,
|
||||||
};
|
file,
|
||||||
});
|
});
|
||||||
|
this.tasks.push(task);
|
||||||
|
}
|
||||||
// 开始传输
|
// 开始传输
|
||||||
this.startTransfer(items);
|
await this.startTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加下载任务
|
// 添加下载任务
|
||||||
addDownload(hostId: number, currentPath: string, files: Array<SftpFile>) {
|
async addDownload(hostId: number, currentPath: string, files: Array<SftpFile>) {
|
||||||
|
Message.info(TerminalMessages.fileDownloading);
|
||||||
let pathIndex = currentPath === '/' ? 1 : currentPath.length + 1;
|
let pathIndex = currentPath === '/' ? 1 : currentPath.length + 1;
|
||||||
// 转为下载文件
|
for (let file of files) {
|
||||||
const items = files.map(s => {
|
// 创建任务
|
||||||
return {
|
const task = new SftpFileDownloadTask(TransferType.DOWNLOAD, hostId, {
|
||||||
fileId: nextId(10),
|
name: file.path.substring(pathIndex),
|
||||||
type: TransferType.DOWNLOAD,
|
|
||||||
hostId: hostId,
|
|
||||||
name: s.path.substring(pathIndex),
|
|
||||||
parentPath: currentPath,
|
parentPath: currentPath,
|
||||||
currentSize: 0,
|
size: file.size,
|
||||||
totalSize: s.size,
|
});
|
||||||
progress: 0,
|
this.tasks.push(task);
|
||||||
status: TransferStatus.WAITING,
|
}
|
||||||
};
|
|
||||||
}) as Array<SftpTransferItem>;
|
|
||||||
// 开始传输
|
// 开始传输
|
||||||
this.startTransfer(items);
|
await this.startTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始传输
|
// 开始传输
|
||||||
private startTransfer(items: Array<SftpTransferItem>) {
|
private async startTransfer() {
|
||||||
this.transferList.push(...items);
|
|
||||||
// 开始传输
|
// 开始传输
|
||||||
if (!this.run) {
|
if (!this.run) {
|
||||||
this.openClient();
|
await this.openClient();
|
||||||
}
|
}
|
||||||
|
// 开始计算进度
|
||||||
|
this.resetProgressTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消传输
|
// 取消传输
|
||||||
cancelTransfer(fileId: string): void {
|
cancelTransfer(fileId: string): void {
|
||||||
const index = this.transferList.findIndex(s => s.fileId === fileId);
|
const index = this.tasks.findIndex(s => s.fileId === fileId);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item = this.transferList[index];
|
const task = this.tasks[index];
|
||||||
if (item.status === TransferStatus.TRANSFERRING) {
|
if (task.state.status === TransferStatus.TRANSFERRING) {
|
||||||
// 传输中则中断传输
|
// 传输中则中断传输
|
||||||
this.currentTransfer?.abort();
|
this.currentTask?.abort();
|
||||||
}
|
}
|
||||||
// 从列表中移除
|
// 从列表中移除
|
||||||
this.transferList.splice(index, 1);
|
this.tasks.splice(index, 1);
|
||||||
}
|
|
||||||
|
|
||||||
// 取消全部传输
|
|
||||||
cancelAllTransfer(): void {
|
|
||||||
// 从列表中移除非传输中的元素
|
|
||||||
this.transferList.reduceRight((_, value: SftpTransferItem, index: number) => {
|
|
||||||
if (value.status !== TransferStatus.TRANSFERRING) {
|
|
||||||
this.transferList.splice(index, 1);
|
|
||||||
}
|
|
||||||
}, null as any);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开会话
|
// 打开会话
|
||||||
@@ -115,11 +99,10 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
Message.error('会话打开失败');
|
Message.error('会话打开失败');
|
||||||
console.error('transfer error', e);
|
console.error('transfer error', e);
|
||||||
// 将等待中和传输中任务修改为失败状态
|
// 将等待中和传输中任务修改为失败状态
|
||||||
this.transferList.filter(s => {
|
this.tasks.filter(s => {
|
||||||
return s.status === TransferStatus.WAITING
|
return s.state.status === TransferStatus.WAITING || s.state.status === TransferStatus.TRANSFERRING;
|
||||||
|| s.status === TransferStatus.TRANSFERRING;
|
|
||||||
}).forEach(s => {
|
}).forEach(s => {
|
||||||
s.status = TransferStatus.ERROR;
|
s.state.status = TransferStatus.ERROR;
|
||||||
});
|
});
|
||||||
// 关闭会话
|
// 关闭会话
|
||||||
this.close();
|
this.close();
|
||||||
@@ -132,100 +115,62 @@ export default class SftpTransferManager implements ISftpTransferManager {
|
|||||||
};
|
};
|
||||||
// 处理消息
|
// 处理消息
|
||||||
this.client.onmessage = this.resolveMessage.bind(this);
|
this.client.onmessage = this.resolveMessage.bind(this);
|
||||||
// 计算传输进度
|
|
||||||
this.progressIntervalId = setInterval(this.calcProgress.bind(this), 500);
|
|
||||||
// 打开后自动传输下一个任务
|
// 打开后自动传输下一个任务
|
||||||
this.transferNextItem();
|
this.transferNextItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算传输进度
|
|
||||||
private calcProgress() {
|
|
||||||
this.transferList.forEach(item => {
|
|
||||||
if (item.totalSize != 0) {
|
|
||||||
item.progress = (item.currentSize / item.totalSize * 100).toFixed(2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 传输下一条任务
|
// 传输下一条任务
|
||||||
private transferNextItem() {
|
private transferNextItem() {
|
||||||
this.currentTransfer = undefined;
|
|
||||||
// 释放内存
|
|
||||||
if (this.currentItem) {
|
|
||||||
this.currentItem.file = null as unknown as File;
|
|
||||||
}
|
|
||||||
// 获取任务
|
// 获取任务
|
||||||
this.currentItem = this.transferList.find(s => s.status === TransferStatus.WAITING);
|
this.currentTask = this.tasks.find(s => s.state.status === TransferStatus.WAITING);
|
||||||
if (this.currentItem) {
|
if (this.currentTask) {
|
||||||
// 创建传输器
|
// 设置 client
|
||||||
this.currentTransfer = this.createTransfer();
|
(this.currentTask as unknown as ISetTransferClient<WebSocket>).setClient(this.client as WebSocket);
|
||||||
// 开始
|
// 开始
|
||||||
this.currentTransfer?.start();
|
this.currentTask?.start();
|
||||||
} else {
|
} else {
|
||||||
// 无任务关闭会话
|
// 无任务关闭会话
|
||||||
this.client?.close();
|
this.client?.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建传输器
|
|
||||||
private createTransfer(): ISftpTransferHandler | undefined {
|
|
||||||
if (!this.currentItem) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (this.currentItem.type === TransferType.UPLOAD) {
|
|
||||||
// 上传
|
|
||||||
return new SftpTransferUploader(TransferType.UPLOAD, this.currentItem, this.client as WebSocket);
|
|
||||||
} else if (this.currentItem.type === TransferType.DOWNLOAD) {
|
|
||||||
// 下载
|
|
||||||
return new SftpTransferDownloader(TransferType.DOWNLOAD, this.currentItem, this.client as WebSocket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接收消息
|
// 接收消息
|
||||||
private async resolveMessage(message: MessageEvent) {
|
private async resolveMessage(message: MessageEvent) {
|
||||||
// 文本消息
|
// 文本消息
|
||||||
const data = JSON.parse(message.data) as TransferOperatorResponse;
|
const data = JSON.parse(message.data) as TransferOperatorResponse;
|
||||||
if (data.type === TransferReceiver.NEXT_PART) {
|
if (data.type === TransferReceiver.NEXT_PART) {
|
||||||
// 接收下一块数据回调
|
// 接收下一块数据回调
|
||||||
await this.currentTransfer?.onNextPart();
|
await (this.currentTask as MaybeFileTransferTask)?.onNextPart?.();
|
||||||
} else if (data.type === TransferReceiver.START) {
|
} else if (data.type === TransferReceiver.START) {
|
||||||
// 开始回调
|
// 开始下载回调
|
||||||
this.currentTransfer?.onStart(data.channelId as string, data.transferToken as string);
|
(this.currentTask as MaybeFileTransferTask)?.onStart?.(data.channelId as string, data.transferToken as string);
|
||||||
} else if (data.type === TransferReceiver.PROGRESS) {
|
} else if (data.type === TransferReceiver.PROGRESS) {
|
||||||
// 进度回调
|
// 下载进度回调
|
||||||
this.currentTransfer?.onProgress(data.totalSize, data.currentSize);
|
(this.currentTask as MaybeFileTransferTask)?.onProgress?.(data.totalSize, data.currentSize);
|
||||||
} else if (data.type === TransferReceiver.FINISH) {
|
} else if (data.type === TransferReceiver.FINISH) {
|
||||||
// 完成回调
|
// 完成回调
|
||||||
this.currentTransfer?.onFinish();
|
this.currentTask?.onFinish();
|
||||||
// 开始下一个传输任务
|
// 开始下一个传输任务
|
||||||
this.transferNextItem();
|
this.transferNextItem();
|
||||||
} else if (data.type === TransferReceiver.ERROR) {
|
} else if (data.type === TransferReceiver.ERROR) {
|
||||||
// 失败回调
|
// 失败回调
|
||||||
this.currentTransfer?.onError(data.msg);
|
this.currentTask?.onError(data.msg);
|
||||||
// 开始下一个传输任务
|
// 开始下一个传输任务
|
||||||
this.transferNextItem();
|
this.transferNextItem();
|
||||||
} else if (data.type === TransferReceiver.ABORT) {
|
} else if (data.type === TransferReceiver.ABORT) {
|
||||||
// 中断回调
|
// 中断回调
|
||||||
this.currentTransfer?.onAbort();
|
this.currentTask?.onAbort();
|
||||||
// 开始下一个传输任务
|
// 开始下一个传输任务
|
||||||
this.transferNextItem();
|
this.transferNextItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭 释放资源
|
// 关闭 释放资源
|
||||||
private close() {
|
protected close() {
|
||||||
// 重置 run
|
// 重置 run
|
||||||
this.run = false;
|
this.run = false;
|
||||||
// 关闭传输进度
|
// 关闭
|
||||||
clearInterval(this.progressIntervalId);
|
super.close();
|
||||||
// 进行中和等待中的文件改为失败
|
|
||||||
this.transferList.forEach(s => {
|
|
||||||
if (s.status === TransferStatus.WAITING ||
|
|
||||||
s.status === TransferStatus.TRANSFERRING) {
|
|
||||||
s.status = TransferStatus.ERROR;
|
|
||||||
s.errorMessage = TerminalMessages.sessionClosed;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import type { SftpTransferItem } from '@/views/terminal/interfaces';
|
|
||||||
import SftpTransferHandler from './sftp-transfer-handler';
|
|
||||||
|
|
||||||
// 512 KB
|
|
||||||
export const PART_SIZE = 512 * 1024;
|
|
||||||
|
|
||||||
// sftp 上传器实现
|
|
||||||
export default class SftpTransferUploader extends SftpTransferHandler {
|
|
||||||
|
|
||||||
private currentPart: number;
|
|
||||||
private readonly totalPart: number;
|
|
||||||
private file: File;
|
|
||||||
|
|
||||||
constructor(type: string, item: SftpTransferItem, client: WebSocket) {
|
|
||||||
super(type, item, client);
|
|
||||||
this.file = item.file;
|
|
||||||
this.currentPart = 0;
|
|
||||||
this.totalPart = Math.ceil(item.file.size / PART_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否有下一个分片
|
|
||||||
hasNextPart() {
|
|
||||||
return this.currentPart < this.totalPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传下一个分片
|
|
||||||
async onNextPart() {
|
|
||||||
super.onNextPart();
|
|
||||||
// 完成或者中断直接跳过
|
|
||||||
if (this.aborted || this.finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.hasNextPart()) {
|
|
||||||
try {
|
|
||||||
// 有下一个分片则上传
|
|
||||||
await this.doUploadNextPart();
|
|
||||||
} catch (e) {
|
|
||||||
// 读取文件失败
|
|
||||||
this.error();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行上传下一分片
|
|
||||||
private async doUploadNextPart() {
|
|
||||||
// 读取数据
|
|
||||||
const start = this.currentPart * PART_SIZE;
|
|
||||||
const end = Math.min(this.file.size, start + PART_SIZE);
|
|
||||||
const chunk = this.file.slice(start, end);
|
|
||||||
const reader = new FileReader();
|
|
||||||
const arrayBuffer = await new Promise((resolve, reject) => {
|
|
||||||
reader.onload = () => resolve(reader.result);
|
|
||||||
reader.onerror = (error) => reject(error);
|
|
||||||
reader.readAsArrayBuffer(chunk);
|
|
||||||
});
|
|
||||||
// 发送数据
|
|
||||||
this.client?.send(arrayBuffer as ArrayBuffer);
|
|
||||||
this.currentPart++;
|
|
||||||
this.item.currentSize += (end - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -98,6 +98,10 @@ export const TerminalMessages = {
|
|||||||
waitingReconnect: '输入回车重新连接...',
|
waitingReconnect: '输入回车重新连接...',
|
||||||
loggedElsewhere: '该账号已在另一台设备登录',
|
loggedElsewhere: '该账号已在另一台设备登录',
|
||||||
rdpConnectTimeout: '请检查远程计算机网络及其他配置是否正常',
|
rdpConnectTimeout: '请检查远程计算机网络及其他配置是否正常',
|
||||||
|
fileTransferError: '传输失败',
|
||||||
|
fileSaveError: '保存失败',
|
||||||
|
fileUploading: '已开始上传, 点击右侧传输列表查看进度',
|
||||||
|
fileDownloading: '已开始下载, 点击右侧传输列表查看进度',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 文件类型
|
// 文件类型
|
||||||
@@ -457,6 +461,12 @@ export const TransferType = {
|
|||||||
DOWNLOAD: 'download',
|
DOWNLOAD: 'download',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 传输来源
|
||||||
|
export const TransferSource = {
|
||||||
|
SFTP: 'sftp',
|
||||||
|
RDP: 'rdp',
|
||||||
|
};
|
||||||
|
|
||||||
// 传输操作
|
// 传输操作
|
||||||
export const TransferOperator = {
|
export const TransferOperator = {
|
||||||
START: 'start',
|
START: 'start',
|
||||||
|
|||||||
Reference in New Issue
Block a user