From cb40ea89a79c4384b62217e37991e9c9eb7eaf5c Mon Sep 17 00:00:00 2001 From: kura Date: Thu, 7 May 2026 18:21:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86windows=E8=80=81=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E9=A2=91=E7=B9=81=E5=BC=80=E5=A7=8B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=AF=BB=E5=86=99=E6=B5=81=E5=AF=BC=E8=87=B4=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/file/utils/fileTransfer.ts | 69 +++++++++++++++++++++------- src/pages/file/utils/peer.ts | 17 ++----- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/pages/file/utils/fileTransfer.ts b/src/pages/file/utils/fileTransfer.ts index 091261d..436c8ac 100644 --- a/src/pages/file/utils/fileTransfer.ts +++ b/src/pages/file/utils/fileTransfer.ts @@ -80,6 +80,9 @@ export class FileTransfer { this.pausePromise = undefined; this.pauseResolve = undefined; this.aborted = false; + this.closeReceiveWritable().catch((error) => { + console.error("close receive writable error", error); + }); this.receiveQueue = Promise.resolve(); } @@ -119,6 +122,8 @@ export class FileTransfer { public totalSize: number = 0; private fileBuffer: ArrayBuffer | null = null; private receiveQueue: Promise = Promise.resolve(); + private receiveWritable: FileSystemWritableFileStream | null = null; + private receiveWritablePath: string = ""; private async waitForWritable(targetBufferedAmount = MAX_BUFFERED_AMOUNT) { const dc = this.conn.dataChannel; if (!dc) { @@ -139,6 +144,36 @@ export class FileTransfer { await new Promise((resolve) => setTimeout(resolve, 10)); } } + private async getReceiveWritable(path: string) { + if (this.receiveWritable && this.receiveWritablePath === path) { + return this.receiveWritable; + } + + if (this.receiveWritable) { + await this.receiveWritable.close(); + this.receiveWritable = null; + } + + let dir = this.file.fileDirHandler as FileSystemDirectoryHandle; + let name = path; + if (name.split("/").length > 1) { + const dirPath = name.split("/").slice(0, -1).join("/"); + name = name.split("/").slice(-1).join(""); + dir = await this.file.createPath(dirPath); + } + + const fileHandle = await dir.getFileHandle(name, { create: true }); + this.receiveWritable = await fileHandle.createWritable(); + this.receiveWritablePath = path; + return this.receiveWritable; + } + private async closeReceiveWritable() { + if (!this.receiveWritable) return; + const writable = this.receiveWritable; + this.receiveWritable = null; + this.receiveWritablePath = ""; + await writable.close(); + } // 发送文件 (流水线模式 - 受限深度,防止撑爆SCTP缓冲区) public async sendFile(savePath: string = ""): Promise { try { @@ -182,7 +217,6 @@ export class FileTransfer { data: this.fileData, }, this.conn, - true, ); this.offset = end; @@ -261,11 +295,15 @@ export class FileTransfer { ); } else { const path = fData.savePath + "/" + fData.name; - await this.file.createFile( - path, - fData.chunkData.buffer, - fData.chunkData.offset, - ); + const writable = await this.getReceiveWritable(path); + await writable.write({ + type: "write", + position: fData.chunkData.offset, + data: fData.chunkData.buffer, + }); + if (this.status === TransferStatus.COMPLETED) { + await this.closeReceiveWritable(); + } // if (this.status == TransferStatus.COMPLETED) { // await this.file.renameFile(path, fData.savePath + '/' + fData.name); // } @@ -304,6 +342,9 @@ export class FileTransfer { // 取消传输 public abort() { this.aborted = true; + this.closeReceiveWritable().catch((error) => { + console.error("close receive writable error", error); + }); this.resume(); // 恢复暂停的传输以便能够正确退出 } @@ -400,22 +441,16 @@ export class TransferTask { this.updateProgress(); } public updateProgress(): TransferProgress { - if (this.status == TransferStatus.COMPLETED) { - this.progress.updateTime = Date.now(); - return this.progress; - } const totalSize = this.fileData.chunkData?.totalSize || this.fileData.size; const transferredSize = - this.fileData.chunkData?.offset || this.fileData.size; + this.fileData.chunkData + ? this.fileData.chunkData.offset + this.fileData.chunkData.buffer.byteLength + : this.fileData.size; const speed = this.fileData.chunkData - ? ((transferredSize + this.fileData.chunkData.buffer.byteLength) / - (Date.now() - this.startTime)) * - 1000 + ? (transferredSize / (Date.now() - this.startTime)) * 1000 : (this.fileData.size / (Date.now() - this.startTime)) * 1000; const percent = this.fileData.chunkData - ? ((transferredSize + this.fileData.chunkData.buffer.byteLength) / - totalSize) * - 100 + ? (transferredSize / totalSize) * 100 : (transferredSize / totalSize) * 100; this.status = transferredSize >= totalSize ? TransferStatus.COMPLETED : this.status; diff --git a/src/pages/file/utils/peer.ts b/src/pages/file/utils/peer.ts index c4a825f..726e32e 100644 --- a/src/pages/file/utils/peer.ts +++ b/src/pages/file/utils/peer.ts @@ -16,7 +16,7 @@ import { } from "./common"; // 发送超时时间(毫秒) -const SEND_TIMEOUT = 10000; +const SEND_TIMEOUT = 30000; class Peer extends EventTarget { peer: PeerJs; @@ -600,23 +600,16 @@ class Peer extends EventTarget { case MessageType.push_file_chunk: resData.type = MessageType.response_push_file_chunk; resData.data = "ok"; - // 先回复确认,再异步处理文件I/O,避免阻塞消息循环 let fData = remoteD as FileData; if (fData.preView) { - fileMgrInstance.remoteRootFile + await fileMgrInstance.remoteRootFile .getFileInfo(fData.path) .getTransfer(conn) - .receiveFile(fData, true) - .catch((err) => { - console.error("receiveFile preview error", err); - }); + .receiveFile(fData, true); } else { - (await fileMgrInstance.getRootFile()) + await (await fileMgrInstance.getRootFile()) .getTransfer(conn) - .receiveFile(fData) - .catch((err) => { - console.error("receiveFile error", err); - }); + .receiveFile(fData); } break; case MessageType.push_file_complete: