chore:Use English notes
This commit is contained in:
@@ -114,14 +114,14 @@ export function useFileTransferHandler({
|
||||
);
|
||||
|
||||
if (fileToDownload) {
|
||||
// 检查文件是否为空
|
||||
// Check if file is empty
|
||||
if (fileToDownload.size === 0) {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] ERROR: File has 0 size! This explains the 0-byte download.`
|
||||
);
|
||||
}
|
||||
|
||||
// 检查文件是否为有效的Blob
|
||||
// Check if file is a valid Blob
|
||||
if (!(fileToDownload instanceof Blob)) {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] WARNING: File is not a Blob object, type: ${typeof fileToDownload}`
|
||||
@@ -131,7 +131,7 @@ export function useFileTransferHandler({
|
||||
downloadAs(fileToDownload, fileToDownload.name);
|
||||
return true;
|
||||
} else {
|
||||
// 调试日志:记录未找到文件的情况
|
||||
// Debug log: Record the case where file is not found
|
||||
const availableFileNames = latestRetrievedFiles.map((f) => f.name);
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] File NOT found! Looking for: "${
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
/**
|
||||
* 浏览器检测工具函数
|
||||
* 扩展以支持Firefox WebRTC兼容性处理
|
||||
* Browser detection utility functions
|
||||
* Extended to support Firefox WebRTC compatibility handling
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检测是否为 Chrome 浏览器
|
||||
* @returns {boolean} 如果是 Chrome 返回 true,否则返回 false
|
||||
* Detect if the browser is Chrome
|
||||
* @returns {boolean} Returns true if it's Chrome, otherwise false
|
||||
*/
|
||||
export const isChrome = (): boolean => {
|
||||
// 检测 Chrome 浏览器,排除基于 Chromium 的 Edge
|
||||
// Detect Chrome browser, excluding Chromium-based Edge
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
return (
|
||||
userAgent.includes("Chrome") && !userAgent.includes("Edg") // 排除 Edge
|
||||
userAgent.includes("Chrome") && !userAgent.includes("Edg") // Exclude Edge
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 检测是否支持程序化下载
|
||||
* Chrome 支持长时间传输后的自动下载,其他浏览器可能有限制
|
||||
* @returns {boolean} 如果支持自动下载返回 true
|
||||
* Detect if programmatic download is supported
|
||||
* Chrome supports automatic download after long transfers, other browsers may have limitations
|
||||
* @returns {boolean} Returns true if automatic download is supported
|
||||
*/
|
||||
|
||||
export const supportsAutoDownload = (): boolean => {
|
||||
return isChrome();
|
||||
};
|
||||
|
||||
+212
-160
@@ -1,9 +1,9 @@
|
||||
// 🚀 新流程 - 接收端主导的文件传输:
|
||||
// 1. 接收文件元数据 (fileMetadata)
|
||||
// 2. 用户点击下载,发送文件请求 (fileRequest)
|
||||
// 3. 接收所有数据块,自动检测完整性
|
||||
// 4. 完成Store同步后,主动发送完成确认 (fileReceiveComplete/folderReceiveComplete)
|
||||
// 文件夹传输:重复单文件流程,最后发送文件夹完成确认
|
||||
// 🚀 New Process - Receiver-Dominated File Transfer:
|
||||
// 1. Receive file metadata (fileMetadata)
|
||||
// 2. User clicks download, send file request (fileRequest)
|
||||
// 3. Receive all data chunks, automatically detect integrity
|
||||
// 4. After completing Store synchronization, proactively send completion confirmation (fileReceiveComplete/folderReceiveComplete)
|
||||
// Folder Transfer: Repeat single file process, finally send folder completion confirmation
|
||||
import { SpeedCalculator } from "@/lib/speedCalculator";
|
||||
import WebRTC_Recipient from "./webrtc_Recipient";
|
||||
import {
|
||||
@@ -22,14 +22,14 @@ import {
|
||||
EmbeddedChunkMeta,
|
||||
} from "@/types/webrtc";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;
|
||||
/**
|
||||
* 🚀 严格按序缓冲写入管理器 - 优化大文件磁盘I/O性能
|
||||
* 🚀 Strict Sequential Buffering Writer - Optimizes large file disk I/O performance
|
||||
*/
|
||||
class SequencedDiskWriter {
|
||||
private writeQueue = new Map<number, ArrayBuffer>();
|
||||
private nextWriteIndex = 0;
|
||||
private readonly maxBufferSize = 100; // 最多缓冲100个chunk(约6.4MB)
|
||||
private readonly maxBufferSize = 100; // Buffer up to 100 chunks (approximately 6.4MB)
|
||||
private readonly stream: FileSystemWritableFileStream;
|
||||
private totalWritten = 0;
|
||||
|
||||
@@ -38,55 +38,61 @@ class SequencedDiskWriter {
|
||||
this.nextWriteIndex = startIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入一个chunk,自动管理顺序和缓冲
|
||||
*/
|
||||
/**\n * Write a chunk, automatically managing order and buffering\n */
|
||||
async writeChunk(chunkIndex: number, chunk: ArrayBuffer): Promise<void> {
|
||||
// 1. 如果是期待的下一个chunk,立即写入
|
||||
// 1. If it is the expected next chunk, write immediately
|
||||
if (chunkIndex === this.nextWriteIndex) {
|
||||
await this.flushSequentialChunks(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 如果是未来的chunk,缓冲起来
|
||||
// 2. If it's a future chunk, buffer it
|
||||
if (chunkIndex > this.nextWriteIndex) {
|
||||
if (this.writeQueue.size < this.maxBufferSize) {
|
||||
this.writeQueue.set(chunkIndex, chunk);
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 缓冲区满,强制处理最早的chunk以释放空间
|
||||
// Buffer full, forcing processing of the earliest chunk to free up space
|
||||
await this.forceFlushOldest();
|
||||
this.writeQueue.set(chunkIndex, chunk);
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 如果是过期的chunk,记录警告但忽略(已写入)
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ DUPLICATE chunk #${chunkIndex} ignored (already written #${this.nextWriteIndex})`
|
||||
);
|
||||
// 3. If the chunk is expired, log a warning but ignore (already written)
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ DUPLICATE chunk #${chunkIndex} ignored (already written #${this.nextWriteIndex})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入当前chunk并尝试连续写入后续的chunk
|
||||
* Write current chunk and attempt to sequentially write subsequent chunks
|
||||
*/
|
||||
private async flushSequentialChunks(firstChunk: ArrayBuffer): Promise<void> {
|
||||
// 写入当前chunk
|
||||
// Write current chunk
|
||||
await this.stream.write(firstChunk);
|
||||
this.totalWritten += firstChunk.byteLength;
|
||||
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✓ DISK_WRITE chunk #${this.nextWriteIndex} - ${firstChunk.byteLength} bytes, total: ${this.totalWritten}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✓ DISK_WRITE chunk #${this.nextWriteIndex} - ${firstChunk.byteLength} bytes, total: ${this.totalWritten}`
|
||||
);
|
||||
}
|
||||
|
||||
this.nextWriteIndex++;
|
||||
|
||||
// 尝试连续写入缓冲中的chunk
|
||||
// Try to sequentially write chunks from buffer
|
||||
let flushCount = 0;
|
||||
while (this.writeQueue.has(this.nextWriteIndex)) {
|
||||
const chunk = this.writeQueue.get(this.nextWriteIndex)!;
|
||||
@@ -99,14 +105,16 @@ class SequencedDiskWriter {
|
||||
}
|
||||
|
||||
if (flushCount > 0) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 🔥 SEQUENTIAL_FLUSH ${flushCount} chunks, now at #${this.nextWriteIndex}, queue: ${this.writeQueue.size}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 🔥 SEQUENTIAL_FLUSH ${flushCount} chunks, now at #${this.nextWriteIndex}, queue: ${this.writeQueue.size}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新最早的chunk以释放缓冲区空间
|
||||
* Force refresh the earliest chunk to release buffer space
|
||||
*/
|
||||
private async forceFlushOldest(): Promise<void> {
|
||||
if (this.writeQueue.size === 0) return;
|
||||
@@ -114,24 +122,26 @@ class SequencedDiskWriter {
|
||||
const oldestIndex = Math.min(...Array.from(this.writeQueue.keys()));
|
||||
const chunk = this.writeQueue.get(oldestIndex)!;
|
||||
|
||||
// 警告:非序写入
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})`
|
||||
);
|
||||
// Warning: Unordered write
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})`
|
||||
);
|
||||
}
|
||||
|
||||
// 使用seek在正确位置写入(降级处理)
|
||||
const fileOffset = oldestIndex * 65536; // 假设每个chunk 64KB
|
||||
// Use seek to write at the correct position (fallback handling)
|
||||
const fileOffset = oldestIndex * 65536; // Assume each chunk is 64KB
|
||||
await this.stream.seek(fileOffset);
|
||||
await this.stream.write(chunk);
|
||||
this.writeQueue.delete(oldestIndex);
|
||||
|
||||
// 恢复到当前位置
|
||||
// Return to current position
|
||||
const currentOffset = this.nextWriteIndex * 65536;
|
||||
await this.stream.seek(currentOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓冲区状态
|
||||
* Get buffer status
|
||||
*/
|
||||
getBufferStatus(): {
|
||||
queueSize: number;
|
||||
@@ -146,10 +156,10 @@ class SequencedDiskWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭并清理资源
|
||||
* Close and clean up resources
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
// 尝试刷新所有剩余的chunk
|
||||
// Try to flush all remaining chunks
|
||||
const remainingIndexes = Array.from(this.writeQueue.keys()).sort(
|
||||
(a, b) => a - b
|
||||
);
|
||||
@@ -158,35 +168,35 @@ class SequencedDiskWriter {
|
||||
const fileOffset = chunkIndex * 65536;
|
||||
await this.stream.seek(fileOffset);
|
||||
await this.stream.write(chunk);
|
||||
postLogToBackend(
|
||||
`[DEBUG] 💾 FINAL_FLUSH chunk #${chunkIndex} at cleanup`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 💾 FINAL_FLUSH chunk #${chunkIndex} at cleanup`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.writeQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 新版本:管理按序列化融合数据包的文件接收状态
|
||||
*/
|
||||
/**\n * 🚀 New Version: Manage file reception state for serialized embedded packets\n */
|
||||
interface ActiveFileReception {
|
||||
meta: fileMetadata; // If meta is present, it means this file is currently being received; null means no file is being received.
|
||||
chunks: (ArrayBuffer | null)[]; // 按序号排列的数据块数组
|
||||
chunks: (ArrayBuffer | null)[]; // Array of data chunks arranged by index
|
||||
receivedSize: number;
|
||||
initialOffset: number; // For resuming downloads
|
||||
fileHandle: FileSystemFileHandle | null; // Object related to writing to disk -- current file.
|
||||
writeStream: FileSystemWritableFileStream | null; // Object related to writing to disk.
|
||||
sequencedWriter: SequencedDiskWriter | null; // 🚀 新增:严格按序写入管理器
|
||||
sequencedWriter: SequencedDiskWriter | null; // 🚀 Added: Strict sequential writing manager
|
||||
completionNotifier: {
|
||||
resolve: () => void;
|
||||
reject: (reason?: any) => void;
|
||||
};
|
||||
// 🚀 新版本:简化的按序接收管理
|
||||
receivedChunksCount: number; // 实际接收到的chunk数量
|
||||
expectedChunksCount: number; // 预期的chunk数量
|
||||
chunkSequenceMap: Map<number, boolean>; // 跟踪哪些chunk已经接收(用于chunk序号)
|
||||
isFinalized?: boolean; // 防止重复finalize的标记
|
||||
// 🚀 New Version: Simplified sequential reception management
|
||||
receivedChunksCount: number; // Actual number of chunks received
|
||||
expectedChunksCount: number; // Expected number of chunks
|
||||
chunkSequenceMap: Map<number, boolean>; // Track which chunks have been received (for chunk numbering)
|
||||
isFinalized?: boolean; // Flag to prevent duplicate finalize operations
|
||||
}
|
||||
|
||||
class FileReceiver {
|
||||
@@ -253,7 +263,7 @@ class FileReceiver {
|
||||
}
|
||||
|
||||
if (this.activeFileReception) {
|
||||
// 🚀 在错误时也要清理SequencedWriter
|
||||
// 🚀 Also clean up SequencedWriter on error
|
||||
if (this.activeFileReception.sequencedWriter) {
|
||||
this.activeFileReception.sequencedWriter.close().catch((err) => {
|
||||
this.log(
|
||||
@@ -343,18 +353,18 @@ class FileReceiver {
|
||||
}
|
||||
|
||||
const receptionPromise = new Promise<void>((resolve, reject) => {
|
||||
const expectedChunksCount = Math.ceil((fileInfo.size - offset) / 65536); // 计算预期chunk数量
|
||||
const expectedChunksCount = Math.ceil((fileInfo.size - offset) / 65536); // Calculate expected chunk count
|
||||
|
||||
this.activeFileReception = {
|
||||
meta: fileInfo,
|
||||
chunks: new Array(expectedChunksCount).fill(null), // 🚀 初始化为按索引排列的空数组
|
||||
chunks: new Array(expectedChunksCount).fill(null), // 🚀 Initialize as an empty array arranged by index
|
||||
receivedSize: 0,
|
||||
initialOffset: offset,
|
||||
fileHandle: null,
|
||||
writeStream: null,
|
||||
sequencedWriter: null, // 🚀 新增:严格按序写入管理器
|
||||
sequencedWriter: null, // 🚀 Added: Strict sequential writing manager
|
||||
completionNotifier: { resolve, reject },
|
||||
// 🚀 新版本:简化的按序接收管理
|
||||
// 🚀 New Version: Simplified sequential reception management
|
||||
receivedChunksCount: 0,
|
||||
expectedChunksCount: expectedChunksCount,
|
||||
chunkSequenceMap: new Map<number, boolean>(),
|
||||
@@ -370,9 +380,11 @@ class FileReceiver {
|
||||
this.webrtcConnection.sendData(JSON.stringify(request), this.peerId);
|
||||
this.log("log", "Sent fileRequest", { request });
|
||||
} else {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] ERROR: Cannot send fileRequest - no peerId available!`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ERROR: Cannot send fileRequest - no peerId available!`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return receptionPromise;
|
||||
@@ -433,18 +445,20 @@ class FileReceiver {
|
||||
}
|
||||
this.currentFolderName = null;
|
||||
|
||||
// 🚀 新流程:发送文件夹接收完成确认
|
||||
// 收集所有成功完成的文件ID
|
||||
// 🚀 New Process: Send folder reception completion confirmation
|
||||
// Collect all successfully completed file IDs
|
||||
const completedFileIds = folderProgress.fileIds.filter((fileId) => {
|
||||
// 这里可以添加更复杂的验证逻辑,现在简单假设都成功了
|
||||
// More complex validation logic can be added here, now simply assume all succeeded
|
||||
return true;
|
||||
});
|
||||
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] 📁 All files in folder completed - ${folderName}, files: ${completedFileIds.length}/${folderProgress.fileIds.length}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📁 All files in folder completed - ${folderName}, files: ${completedFileIds.length}/${folderProgress.fileIds.length}`
|
||||
);
|
||||
}
|
||||
|
||||
// 发送文件夹完成消息
|
||||
// Send folder completion message
|
||||
this.sendFolderReceiveComplete(folderName, completedFileIds, true);
|
||||
}
|
||||
// endregion
|
||||
@@ -452,8 +466,8 @@ class FileReceiver {
|
||||
// region WebRTC Data Handlers
|
||||
|
||||
/**
|
||||
* 将各种二进制数据格式转换为ArrayBuffer
|
||||
* 支持Firefox的Blob、Uint8Array等格式
|
||||
* Convert various binary data formats to ArrayBuffer
|
||||
* Supports Blob, Uint8Array, and other formats for Firefox
|
||||
*/
|
||||
private async convertToArrayBuffer(data: any): Promise<ArrayBuffer | null> {
|
||||
const originalType = Object.prototype.toString.call(data);
|
||||
@@ -464,13 +478,17 @@ class FileReceiver {
|
||||
try {
|
||||
const arrayBuffer = await data.arrayBuffer();
|
||||
if (data.size !== arrayBuffer.byteLength) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Blob size mismatch: ${data.size}→${arrayBuffer.byteLength}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Blob size mismatch: ${data.size}→${arrayBuffer.byteLength}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return arrayBuffer;
|
||||
} catch (error) {
|
||||
postLogToBackend(`[DEBUG] ❌ Blob conversion failed: ${error}`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(`[DEBUG] ❌ Blob conversion failed: ${error}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else if (data instanceof Uint8Array || ArrayBuffer.isView(data)) {
|
||||
@@ -483,66 +501,82 @@ class FileReceiver {
|
||||
new Uint8Array(newArrayBuffer).set(uint8Array);
|
||||
return newArrayBuffer;
|
||||
} catch (error) {
|
||||
postLogToBackend(`[DEBUG] ❌ TypedArray conversion failed: ${error}`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(`[DEBUG] ❌ TypedArray conversion failed: ${error}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call(data)}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call(
|
||||
data
|
||||
)}`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 新增:解析融合数据包
|
||||
* 格式: [4字节长度] + [JSON元数据] + [实际chunk数据]
|
||||
* 🚀 Parsing fusion packets
|
||||
* Format: [4 bytes length] + [JSON metadata] + [actual chunk data]
|
||||
*/
|
||||
private parseEmbeddedChunkPacket(arrayBuffer: ArrayBuffer): {
|
||||
chunkMeta: EmbeddedChunkMeta;
|
||||
chunkData: ArrayBuffer;
|
||||
} | null {
|
||||
try {
|
||||
// 1. 检查数据包最小长度
|
||||
// 1. Check minimum packet length
|
||||
if (arrayBuffer.byteLength < 4) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Invalid embedded packet - too small: ${arrayBuffer.byteLength}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Invalid embedded packet - too small: ${arrayBuffer.byteLength}`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 读取元数据长度(4字节)
|
||||
// 2. Read metadata length (4 bytes)
|
||||
const lengthView = new Uint32Array(arrayBuffer, 0, 1);
|
||||
const metaLength = lengthView[0];
|
||||
|
||||
// 3. 验证数据包的完整性
|
||||
// 3. Verify packet integrity
|
||||
const expectedTotalLength = 4 + metaLength;
|
||||
if (arrayBuffer.byteLength < expectedTotalLength) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Incomplete embedded packet - expected: ${expectedTotalLength}, got: ${arrayBuffer.byteLength}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Incomplete embedded packet - expected: ${expectedTotalLength}, got: ${arrayBuffer.byteLength}`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 提取元数据部分
|
||||
// 4. Extract metadata section
|
||||
const metaBytes = new Uint8Array(arrayBuffer, 4, metaLength);
|
||||
const metaJson = new TextDecoder().decode(metaBytes);
|
||||
const chunkMeta: EmbeddedChunkMeta = JSON.parse(metaJson);
|
||||
|
||||
// 5. 提取实际chunk数据部分
|
||||
// 5. Extract actual chunk data section
|
||||
const chunkDataStart = 4 + metaLength;
|
||||
const chunkData = arrayBuffer.slice(chunkDataStart);
|
||||
|
||||
// 6. 验证chunk数据大小
|
||||
// 6. Verify chunk data size
|
||||
if (chunkData.byteLength !== chunkMeta.chunkSize) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Chunk size mismatch - meta: ${chunkMeta.chunkSize}, actual: ${chunkData.byteLength}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Chunk size mismatch - meta: ${chunkMeta.chunkSize}, actual: ${chunkData.byteLength}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { chunkMeta, chunkData };
|
||||
} catch (error) {
|
||||
postLogToBackend(`[DEBUG] ❌ Failed to parse embedded packet: ${error}`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Failed to parse embedded packet: ${error}`
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -570,14 +604,16 @@ class FileReceiver {
|
||||
this.fireError("Error parsing received JSON data", { error });
|
||||
}
|
||||
} else {
|
||||
// 🚀 新版本:处理融合数据包 - 彻底解决Firefox乱序问题
|
||||
// 🚀 New Version: Process embedded packets - Completely solve Firefox out-of-order issue
|
||||
const arrayBuffer = await this.convertToArrayBuffer(data);
|
||||
|
||||
if (arrayBuffer) {
|
||||
if (!this.activeFileReception) {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] ERROR: Received file chunk but no active file reception!`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ERROR: Received file chunk but no active file reception!`
|
||||
);
|
||||
}
|
||||
this.fireError(
|
||||
"Received a file chunk without an active file reception.",
|
||||
{ peerId }
|
||||
@@ -585,12 +621,14 @@ class FileReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🚀 统一处理:所有数据都作为融合数据包处理
|
||||
// 🚀 Unified processing: All data is processed as embedded packets
|
||||
await this.handleEmbeddedChunkPacket(arrayBuffer);
|
||||
} else {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] ERROR: Failed to convert binary data to ArrayBuffer`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ERROR: Failed to convert binary data to ArrayBuffer`
|
||||
);
|
||||
}
|
||||
this.fireError("Received unsupported binary data format", {
|
||||
dataType: Object.prototype.toString.call(data),
|
||||
peerId,
|
||||
@@ -653,12 +691,10 @@ class FileReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region File and Folder Processing
|
||||
|
||||
/**
|
||||
* 🚀 新版本:处理融合数据包
|
||||
* 🚀 New Version: Process embedded packets
|
||||
*/
|
||||
private async handleEmbeddedChunkPacket(
|
||||
arrayBuffer: ArrayBuffer
|
||||
@@ -672,21 +708,25 @@ class FileReceiver {
|
||||
const { chunkMeta, chunkData } = parsed;
|
||||
const reception = this.activeFileReception!;
|
||||
|
||||
// 验证fileId匹配
|
||||
// Verify fileId match
|
||||
if (chunkMeta.fileId !== reception.meta.fileId) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ FileId mismatch - expected: ${reception.meta.fileId}, got: ${chunkMeta.fileId}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ FileId mismatch - expected: ${reception.meta.fileId}, got: ${chunkMeta.fileId}`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新预期 chunks 数量(可能与初始预估不同)
|
||||
// Update expected chunks count (may differ from initial estimate)
|
||||
if (chunkMeta.totalChunks !== reception.expectedChunksCount) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Chunk count adjustment - expected: ${reception.expectedChunksCount}, actual: ${chunkMeta.totalChunks}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Chunk count adjustment - expected: ${reception.expectedChunksCount}, actual: ${chunkMeta.totalChunks}`
|
||||
);
|
||||
}
|
||||
reception.expectedChunksCount = chunkMeta.totalChunks;
|
||||
// 调整chunks数组大小
|
||||
// Adjust chunks array size
|
||||
if (reception.chunks.length < chunkMeta.totalChunks) {
|
||||
const newChunks = new Array(chunkMeta.totalChunks).fill(null);
|
||||
reception.chunks.forEach((chunk, index) => {
|
||||
@@ -696,33 +736,35 @@ class FileReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
// 按序号存储chunk
|
||||
// Store chunk by index
|
||||
const chunkIndex = chunkMeta.chunkIndex;
|
||||
if (chunkIndex >= 0 && chunkIndex < reception.chunks.length) {
|
||||
reception.chunks[chunkIndex] = chunkData;
|
||||
reception.chunkSequenceMap.set(chunkIndex, true);
|
||||
reception.receivedChunksCount++;
|
||||
|
||||
// 更新进度
|
||||
// Update progress
|
||||
this.updateProgress(chunkData.byteLength);
|
||||
|
||||
if (reception.sequencedWriter) {
|
||||
// 🚀 使用严格按序写入管理器
|
||||
// 🚀 Use strict sequential write management
|
||||
await reception.sequencedWriter.writeChunk(chunkIndex, chunkData);
|
||||
}
|
||||
} else {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Invalid chunk index - ${chunkIndex}, expected 0-${
|
||||
reception.chunks.length - 1
|
||||
}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ Invalid chunk index - ${chunkIndex}, expected 0-${
|
||||
reception.chunks.length - 1
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.checkAndAutoFinalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 新版本:统一的自动完成检查 - 支持融合数据包和旧格式
|
||||
* 🚀 Unified auto-complete check
|
||||
*/
|
||||
private async checkAndAutoFinalize(): Promise<void> {
|
||||
if (!this.activeFileReception) return;
|
||||
@@ -731,13 +773,13 @@ class FileReceiver {
|
||||
const receivedChunks = reception.receivedChunksCount;
|
||||
const expectedChunks = reception.expectedChunksCount;
|
||||
|
||||
// 计算当前实际接收的总大小
|
||||
// Calculate current actual total received size
|
||||
const currentTotalSize = reception.chunks.reduce((sum, chunk) => {
|
||||
return sum + (chunk instanceof ArrayBuffer ? chunk.byteLength : 0);
|
||||
}, 0);
|
||||
const expectedSize = reception.meta.size;
|
||||
|
||||
// 🚀 统一完整性检查:按序接收模式
|
||||
// 🚀 Unified integrity check: sequential reception mode
|
||||
let sequencedCount = 0;
|
||||
for (let i = 0; i < expectedChunks; i++) {
|
||||
if (reception.chunks[i] instanceof ArrayBuffer) {
|
||||
@@ -749,7 +791,7 @@ class FileReceiver {
|
||||
const sizeComplete = currentTotalSize >= expectedSize;
|
||||
const isDataComplete = isSequencedComplete && sizeComplete;
|
||||
|
||||
// 防止重复finalize
|
||||
// Prevent duplicate finalize
|
||||
if (reception.isFinalized) {
|
||||
return;
|
||||
}
|
||||
@@ -765,7 +807,9 @@ class FileReceiver {
|
||||
}
|
||||
this.activeFileReception = null;
|
||||
} catch (error) {
|
||||
postLogToBackend(`[DEBUG] ❌ Auto-finalize ERROR: ${error}`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(`[DEBUG] ❌ Auto-finalize ERROR: ${error}`);
|
||||
}
|
||||
if (reception.completionNotifier) {
|
||||
reception.completionNotifier.reject(error);
|
||||
}
|
||||
@@ -844,16 +888,18 @@ class FileReceiver {
|
||||
this.activeFileReception.fileHandle = fileHandle;
|
||||
this.activeFileReception.writeStream = writeStream;
|
||||
|
||||
// 🚀 创建严格按序写入管理器
|
||||
const startChunkIndex = Math.floor(offset / 65536); // 计算起始块索引
|
||||
// 🚀 Create a strictly sequential write manager
|
||||
const startChunkIndex = Math.floor(offset / 65536); // Calculate starting chunk index
|
||||
this.activeFileReception.sequencedWriter = new SequencedDiskWriter(
|
||||
writeStream,
|
||||
startChunkIndex
|
||||
);
|
||||
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📢 SEQUENCED_WRITER created - startIndex: ${startChunkIndex}, offset: ${offset}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📢 SEQUENCED_WRITER created - startIndex: ${startChunkIndex}, offset: ${offset}`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
this.fireError("Failed to create file on disk", {
|
||||
err,
|
||||
@@ -888,20 +934,24 @@ class FileReceiver {
|
||||
if (!reception?.writeStream || !reception.fileHandle) return;
|
||||
|
||||
try {
|
||||
// 🚀 先关闭严格按序写入管理器(刷新所有缓冲)
|
||||
// 🚀 First close the strict sequential writing manager (flush all buffers)
|
||||
if (reception.sequencedWriter) {
|
||||
await reception.sequencedWriter.close();
|
||||
const status = reception.sequencedWriter.getBufferStatus();
|
||||
postLogToBackend(
|
||||
`[DEBUG] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}`
|
||||
);
|
||||
}
|
||||
reception.sequencedWriter = null;
|
||||
}
|
||||
|
||||
// 然后关闭文件流
|
||||
// Then close the file stream
|
||||
await reception.writeStream.close();
|
||||
|
||||
postLogToBackend(`[DEBUG] ✅ LARGE_FILE finalized successfully`);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(`[DEBUG] ✅ LARGE_FILE finalized successfully`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.fireError("Error finalizing large file", { error });
|
||||
}
|
||||
@@ -913,7 +963,7 @@ class FileReceiver {
|
||||
const reception = this.activeFileReception;
|
||||
if (!reception) return;
|
||||
|
||||
// 🚀 简化版:验证按序接收的数据
|
||||
// 🚀 Simplified: Verify sequentially received data
|
||||
let totalChunkSize = 0;
|
||||
let validChunks = 0;
|
||||
|
||||
@@ -924,15 +974,17 @@ class FileReceiver {
|
||||
}
|
||||
});
|
||||
|
||||
// 最终验证
|
||||
// Final verification
|
||||
const sizeDifference = reception.meta.size - totalChunkSize;
|
||||
if (sizeDifference !== 0) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件
|
||||
// Create file
|
||||
const fileBlob = new Blob(
|
||||
reception.chunks.filter(
|
||||
(chunk) => chunk instanceof ArrayBuffer
|
||||
@@ -959,7 +1011,7 @@ class FileReceiver {
|
||||
storeUpdated = true;
|
||||
}
|
||||
|
||||
// 发送完成确认
|
||||
// Send completion confirmation
|
||||
this.sendFileReceiveComplete(
|
||||
reception.meta.fileId,
|
||||
totalChunkSize,
|
||||
@@ -967,12 +1019,10 @@ class FileReceiver {
|
||||
storeUpdated
|
||||
);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Communication
|
||||
|
||||
/**
|
||||
* 发送文件接收完成确认 - 新的接收端主导流程
|
||||
* Send file reception completion confirmation - New receiver-dominated process
|
||||
*/
|
||||
private sendFileReceiveComplete(
|
||||
fileId: string,
|
||||
@@ -997,7 +1047,7 @@ class FileReceiver {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文件夹接收完成确认
|
||||
* Send folder reception completion confirmation
|
||||
*/
|
||||
private sendFolderReceiveComplete(
|
||||
folderName: string,
|
||||
@@ -1018,9 +1068,11 @@ class FileReceiver {
|
||||
this.peerId
|
||||
);
|
||||
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] 📤 Sent folderReceiveComplete - folderName: ${folderName}, completedFiles: ${completedFileIds.length}, allStoreUpdated: ${allStoreUpdated}, success: ${success}`
|
||||
);
|
||||
if (developmentEnv === "true") {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📤 Sent folderReceiveComplete - folderName: ${folderName}, completedFiles: ${completedFileIds.length}, allStoreUpdated: ${allStoreUpdated}, success: ${success}`
|
||||
);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -1030,7 +1082,7 @@ class FileReceiver {
|
||||
"log",
|
||||
"Attempting to gracefully close sequenced writer on page unload."
|
||||
);
|
||||
// 🚀 先关闭严格按序写入管理器
|
||||
// 🚀 First close the strict sequential writing manager
|
||||
this.activeFileReception.sequencedWriter.close().catch((err) => {
|
||||
this.log(
|
||||
"error",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// 🚀 新流程 - 接收端主导的文件传输
|
||||
// 重构后的FileSender - 使用模块化架构
|
||||
// 🚀 New process - Receiver-initiated file transfer
|
||||
// Refactored FileSender - Using modular architecture
|
||||
|
||||
import WebRTC_Initiator from "./webrtc_Initiator";
|
||||
import { CustomFile } from "@/types/webrtc";
|
||||
import { FileTransferOrchestrator } from "./transfer/FileTransferOrchestrator";
|
||||
|
||||
/**
|
||||
* 🚀 FileSender - 向后兼容包装层
|
||||
* 🚀 FileSender - Backward compatible wrapper layer
|
||||
*
|
||||
* 重构说明:
|
||||
* - 原875行单体类重构为模块化架构
|
||||
* - 内部使用FileTransferOrchestrator统一编排
|
||||
* - 保持100%向后兼容的公共API
|
||||
* - 获得高性能文件读取、智能背压控制等优势
|
||||
* Refactoring notes:
|
||||
* - Original 875-line monolithic class refactored into modular architecture
|
||||
* - Internally uses FileTransferOrchestrator for unified orchestration
|
||||
* - Maintains 100% backward compatible public API
|
||||
* - Gains advantages such as high-performance file reading and intelligent backpressure control
|
||||
*/
|
||||
class FileSender {
|
||||
private orchestrator: FileTransferOrchestrator;
|
||||
@@ -22,8 +22,6 @@ class FileSender {
|
||||
console.log("[FileSender] ✅ Initialized with modular architecture");
|
||||
}
|
||||
|
||||
// ===== 向后兼容的公共API =====
|
||||
|
||||
public sendFileMeta(files: CustomFile[], peerId?: string): void {
|
||||
return this.orchestrator.sendFileMeta(files, peerId);
|
||||
}
|
||||
@@ -39,8 +37,6 @@ class FileSender {
|
||||
return this.orchestrator.setProgressCallback(callback, peerId);
|
||||
}
|
||||
|
||||
// ===== 新增API =====
|
||||
|
||||
public getTransferStats(peerId?: string) {
|
||||
return this.orchestrator.getTransferStats(peerId);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const downloadAs = async (
|
||||
file: Blob | File,
|
||||
saveName: string
|
||||
): Promise<void> => {
|
||||
// 检查文件是否为空
|
||||
// Check if file is empty
|
||||
if (file.size === 0) {
|
||||
postLogToBackend(
|
||||
`[Firefox Debug] CRITICAL ERROR: downloadAs received a file with 0 size! This is the root cause of the 0-byte download issue.`
|
||||
|
||||
@@ -16,8 +16,8 @@ import WebRTC_Initiator from "../webrtc_Initiator";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;
|
||||
/**
|
||||
* 🚀 文件传输编排器
|
||||
* 整合所有组件,提供统一的文件传输服务
|
||||
* 🚀 File transfer orchestrator
|
||||
* Integrates all components to provide unified file transfer services
|
||||
*/
|
||||
export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
private stateManager: StateManager;
|
||||
@@ -26,7 +26,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
private progressTracker: ProgressTracker;
|
||||
|
||||
constructor(private webrtcConnection: WebRTC_Initiator) {
|
||||
// 初始化所有组件
|
||||
// Initialize all components
|
||||
this.stateManager = new StateManager();
|
||||
this.networkTransmitter = new NetworkTransmitter(
|
||||
webrtcConnection,
|
||||
@@ -35,19 +35,19 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
this.progressTracker = new ProgressTracker(this.stateManager);
|
||||
this.messageHandler = new MessageHandler(this.stateManager, this);
|
||||
|
||||
// 设置数据处理器
|
||||
// Set up data handler
|
||||
this.setupDataHandler();
|
||||
|
||||
this.log("log", "FileTransferOrchestrator initialized");
|
||||
}
|
||||
|
||||
// ===== 公共API - 简化的接口 =====
|
||||
// ===== Public API - Simplified interface =====
|
||||
|
||||
/**
|
||||
* 🎯 发送文件元数据
|
||||
* 🎯 Send file metadata
|
||||
*/
|
||||
public sendFileMeta(files: CustomFile[], peerId?: string): void {
|
||||
// 记录属于文件夹的文件大小,用于进度计算
|
||||
// Record file sizes belonging to folders for progress calculation
|
||||
files.forEach((file) => {
|
||||
if (file.folderName) {
|
||||
const fileId = generateFileId(file);
|
||||
@@ -55,7 +55,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
});
|
||||
|
||||
// 循环发送所有文件的元数据
|
||||
// Loop to send metadata for all files
|
||||
const peers = peerId
|
||||
? [peerId]
|
||||
: Array.from(this.webrtcConnection.peerConnections.keys());
|
||||
@@ -80,7 +80,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 发送字符串内容
|
||||
* 🎯 Send string content
|
||||
*/
|
||||
public async sendString(content: string, peerId: string): Promise<void> {
|
||||
const chunkSize = TransferConfig.FILE_CONFIG.CHUNK_SIZE;
|
||||
@@ -90,7 +90,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
chunks.push(content.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
// 首先发送元数据
|
||||
// First send metadata
|
||||
await this.networkTransmitter.sendWithBackpressure(
|
||||
JSON.stringify({
|
||||
type: "stringMetadata",
|
||||
@@ -99,7 +99,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
peerId
|
||||
);
|
||||
|
||||
// 逐块发送,使用背压控制
|
||||
// Send chunks one by one using backpressure control
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const data = JSON.stringify({
|
||||
type: "string",
|
||||
@@ -118,16 +118,16 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 设置进度回调
|
||||
* 🎯 Set progress callback
|
||||
*/
|
||||
public setProgressCallback(callback: ProgressCallback, peerId: string): void {
|
||||
this.progressTracker.setProgressCallback(callback, peerId);
|
||||
}
|
||||
|
||||
// ===== MessageHandlerDelegate 实现 =====
|
||||
// ===== MessageHandlerDelegate Implementation =====
|
||||
|
||||
/**
|
||||
* 📄 处理文件请求(来自MessageHandler的委托)
|
||||
* 📄 Handle file request (delegated from MessageHandler)
|
||||
*/
|
||||
async handleFileRequest(request: FileRequest, peerId: string): Promise<void> {
|
||||
const file = this.stateManager.getPendingFile(request.fileId);
|
||||
@@ -145,7 +145,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📝 日志记录(来自MessageHandler的委托)
|
||||
* 📝 Logging (delegated from MessageHandler)
|
||||
*/
|
||||
public log(
|
||||
level: "log" | "warn" | "error",
|
||||
@@ -156,10 +156,10 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
console[level](prefix, message, context || "");
|
||||
}
|
||||
|
||||
// ===== 内部编排方法 =====
|
||||
// ===== Internal Orchestration Methods =====
|
||||
|
||||
/**
|
||||
* 🎯 发送单个文件
|
||||
* 🎯 Send single file
|
||||
*/
|
||||
private async sendSingleFile(
|
||||
file: CustomFile,
|
||||
@@ -174,7 +174,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化发送状态
|
||||
// Initialize sending state
|
||||
this.stateManager.updatePeerState(peerId, {
|
||||
isSending: true,
|
||||
currentFolderName: file.folderName,
|
||||
@@ -183,7 +183,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
isReading: false,
|
||||
});
|
||||
|
||||
// 初始化进度统计
|
||||
// Initialize progress statistics
|
||||
const currentSent = this.stateManager.getFileBytesSent(peerId, fileId);
|
||||
this.stateManager.updateFileBytesSent(peerId, fileId, offset - currentSent);
|
||||
|
||||
@@ -200,7 +200,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 处理发送队列 - 使用StreamingFileReader
|
||||
* 🚀 Process send queue - Using StreamingFileReader
|
||||
*/
|
||||
private async processSendQueue(
|
||||
file: CustomFile,
|
||||
@@ -210,7 +210,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
const transferStartTime = performance.now();
|
||||
|
||||
// 1. 初始化流式文件读取器
|
||||
// 1. Initialize streaming file reader
|
||||
const streamReader = new StreamingFileReader(
|
||||
file,
|
||||
peerState.readOffset || 0
|
||||
@@ -234,20 +234,20 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
let totalProgressTime = 0;
|
||||
let lastProgressTime = performance.now();
|
||||
|
||||
// 2. 流式处理:逐个获取64KB网络块并发送
|
||||
// 2. Stream processing: Get 64KB network chunks one by one and send
|
||||
while (peerState.isSending) {
|
||||
// 获取下一个网络块
|
||||
// Get next network chunk
|
||||
const readStartTime = performance.now();
|
||||
const chunkInfo = await streamReader.getNextNetworkChunk();
|
||||
const readTime = performance.now() - readStartTime;
|
||||
totalReadTime += readTime;
|
||||
|
||||
// 检查是否已完成
|
||||
// Check if completed
|
||||
if (chunkInfo.chunk === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 构建嵌入式元数据
|
||||
// Build embedded metadata
|
||||
const embeddedMeta: EmbeddedChunkMeta = {
|
||||
chunkIndex: chunkInfo.chunkIndex,
|
||||
totalChunks: chunkInfo.totalChunks,
|
||||
@@ -257,7 +257,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
fileId,
|
||||
};
|
||||
|
||||
// 发送带嵌入元数据的网络块
|
||||
// Send network chunk with embedded metadata
|
||||
let sendSuccessful = false;
|
||||
const sendStartTime = performance.now();
|
||||
try {
|
||||
@@ -280,7 +280,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
const sendTime = performance.now() - sendStartTime;
|
||||
totalSendTime += sendTime;
|
||||
|
||||
// 更新状态和进度
|
||||
// Update state and progress
|
||||
if (sendSuccessful) {
|
||||
this.stateManager.updatePeerState(peerId, {
|
||||
readOffset: chunkInfo.fileOffset + chunkInfo.chunk.byteLength,
|
||||
@@ -300,7 +300,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
|
||||
networkChunkIndex++;
|
||||
|
||||
// 检查是否为最后一块
|
||||
// Check if it's the last chunk
|
||||
if (chunkInfo.isLastChunk) {
|
||||
break;
|
||||
}
|
||||
@@ -329,13 +329,13 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
// 清理资源
|
||||
// Clean up resources
|
||||
streamReader.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ⏳ 等待传输完成确认
|
||||
* ⏳ Wait for transfer completion confirmation
|
||||
*/
|
||||
private async waitForTransferComplete(peerId: string): Promise<void> {
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
@@ -345,7 +345,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📋 获取文件元数据
|
||||
* 📋 Get file metadata
|
||||
*/
|
||||
private getFileMeta(file: CustomFile): fileMetadata {
|
||||
const fileId = generateFileId(file);
|
||||
@@ -361,7 +361,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* ❌ 中止文件发送
|
||||
* ❌ Abort file sending
|
||||
*/
|
||||
private abortFileSend(fileId: string, peerId: string): void {
|
||||
this.log("warn", `Aborting file send for ${fileId} to ${peerId}`);
|
||||
@@ -369,7 +369,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔧 设置数据处理器
|
||||
* 🔧 Set up data handler
|
||||
*/
|
||||
private setupDataHandler(): void {
|
||||
this.webrtcConnection.onDataReceived = (data, peerId) => {
|
||||
@@ -385,7 +385,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 错误处理
|
||||
* 🔥 Error handling
|
||||
*/
|
||||
private fireError(message: string, context?: Record<string, any>) {
|
||||
this.webrtcConnection.fireError(message, {
|
||||
@@ -394,10 +394,10 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 状态查询和调试 =====
|
||||
// ===== State Query and Debugging =====
|
||||
|
||||
/**
|
||||
* 📊 获取传输统计信息
|
||||
* 📊 Get transfer statistics
|
||||
*/
|
||||
public getTransferStats(peerId?: string) {
|
||||
const stats = {
|
||||
@@ -414,7 +414,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 清理所有资源
|
||||
* 🧹 Clean up all resources
|
||||
*/
|
||||
public cleanup(): void {
|
||||
this.stateManager.cleanup();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { StateManager } from "./StateManager";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;
|
||||
/**
|
||||
* 🚀 消息处理接口 - 与主编排器通信
|
||||
* 🚀 Message handling interface - Communicate with main orchestrator
|
||||
*/
|
||||
export interface MessageHandlerDelegate {
|
||||
handleFileRequest(request: FileRequest, peerId: string): Promise<void>;
|
||||
@@ -20,8 +20,8 @@ export interface MessageHandlerDelegate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 消息处理器
|
||||
* 负责WebRTC消息的路由和处理逻辑
|
||||
* 🚀 Message handler
|
||||
* Responsible for WebRTC message routing and processing logic
|
||||
*/
|
||||
export class MessageHandler {
|
||||
constructor(
|
||||
@@ -30,10 +30,10 @@ export class MessageHandler {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 🎯 处理接收到的信令消息
|
||||
* 🎯 Handle received signaling message
|
||||
*/
|
||||
handleSignalingMessage(message: WebRTCMessage, peerId: string): void {
|
||||
// 删除频繁的消息接收日志
|
||||
// Delete frequent message reception logs
|
||||
|
||||
switch (message.type) {
|
||||
case "fileRequest":
|
||||
@@ -57,7 +57,7 @@ export class MessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📄 处理文件请求消息
|
||||
* 📄 Handle file request message
|
||||
*/
|
||||
private async handleFileRequest(
|
||||
request: FileRequest,
|
||||
@@ -70,10 +70,10 @@ export class MessageHandler {
|
||||
`Handling file request for ${request.fileId} from ${peerId} with offset ${offset}`
|
||||
);
|
||||
|
||||
// Firefox兼容性修复:添加稍长延迟确保接收端完全准备好
|
||||
// Firefox compatibility fix: Add slightly longer delay to ensure receiver is fully ready
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// 委托给主编排器处理具体的文件传输
|
||||
// Delegate to main orchestrator for specific file transfer
|
||||
try {
|
||||
await this.delegate.handleFileRequest(request, peerId);
|
||||
} catch (error) {
|
||||
@@ -86,24 +86,24 @@ export class MessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 处理文件接收完成确认消息
|
||||
* ✅ Handle file receive completion confirmation message
|
||||
*/
|
||||
private handleFileReceiveComplete(
|
||||
message: FileReceiveComplete,
|
||||
peerId: string
|
||||
): void {
|
||||
// 清理发送状态
|
||||
// Clean up sending state
|
||||
this.stateManager.updatePeerState(peerId, { isSending: false });
|
||||
|
||||
// 获取peer状态以触发进度回调
|
||||
// Get peer state to trigger progress callback
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
|
||||
// 触发单文件100%进度(只有非文件夹情况)
|
||||
// Trigger single file 100% progress (only for non-folder cases)
|
||||
if (!peerState.currentFolderName) {
|
||||
// 删除频繁的进度日志
|
||||
// Delete frequent progress logs
|
||||
peerState.progressCallback?.(message.fileId, 1, 0);
|
||||
} else {
|
||||
// 删除频繁的文件夹进度日志
|
||||
// Delete frequent folder progress logs
|
||||
}
|
||||
|
||||
this.delegate.log("log", `File reception confirmed by peer ${peerId}`, {
|
||||
@@ -114,7 +114,7 @@ export class MessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📁 处理文件夹接收完成确认消息
|
||||
* 📁 Handle folder receive completion confirmation message
|
||||
*/
|
||||
private handleFolderReceiveComplete(
|
||||
message: FolderReceiveComplete,
|
||||
@@ -126,10 +126,10 @@ export class MessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// 获取peer状态以触发进度回调
|
||||
// Get peer state to trigger progress callback
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
|
||||
// 触发文件夹100%进度
|
||||
// Trigger folder 100% progress
|
||||
const folderMeta = this.stateManager.getFolderMeta(message.folderName);
|
||||
if (folderMeta) {
|
||||
postLogToBackend(
|
||||
@@ -155,21 +155,21 @@ export class MessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 获取消息处理统计信息
|
||||
* 📊 Get message handling statistics
|
||||
*/
|
||||
public getMessageStats(): {
|
||||
handledMessages: number;
|
||||
lastMessageTime: number | null;
|
||||
} {
|
||||
// 这里可以添加消息统计逻辑,如果需要的话
|
||||
// Message statistics logic can be added here if needed
|
||||
return {
|
||||
handledMessages: 0, // TODO: 实现消息计数
|
||||
lastMessageTime: null, // TODO: 记录最后消息时间
|
||||
handledMessages: 0, // TODO: Implement message counting
|
||||
lastMessageTime: null, // TODO: Record last message time
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 清理资源
|
||||
* 🧹 Clean up resources
|
||||
*/
|
||||
public cleanup(): void {
|
||||
postLogToBackend("[DEBUG] 🧹 MessageHandler cleaned up");
|
||||
|
||||
@@ -4,8 +4,8 @@ import WebRTC_Initiator from "../webrtc_Initiator";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;
|
||||
/**
|
||||
* 🚀 网络传输器 - 简化版
|
||||
* 使用WebRTC原生bufferedAmountLowThreshold进行背压控制
|
||||
* 🚀 Network transmitter - Simplified version
|
||||
* Uses WebRTC native bufferedAmountLowThreshold for backpressure control
|
||||
*/
|
||||
export class NetworkTransmitter {
|
||||
constructor(
|
||||
@@ -14,7 +14,7 @@ export class NetworkTransmitter {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 🎯 发送带序号的融合数据包
|
||||
* 🎯 Send embedded chunk packet with sequence number
|
||||
*/
|
||||
async sendEmbeddedChunk(
|
||||
chunkData: ArrayBuffer,
|
||||
@@ -22,16 +22,16 @@ export class NetworkTransmitter {
|
||||
peerId: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// 1. 构建融合数据包
|
||||
// 1. Build fused data packet
|
||||
const embeddedPacket = this.createEmbeddedChunkPacket(
|
||||
chunkData,
|
||||
metadata
|
||||
);
|
||||
|
||||
// 2. 发送完整的融合数据包(不可分片)
|
||||
// 2. Send complete fused data packet (no fragmentation)
|
||||
await this.sendSingleData(embeddedPacket, peerId);
|
||||
|
||||
// 关键节点日志(仅开发环境)
|
||||
// Key node logs (development environment only)
|
||||
|
||||
if (
|
||||
developmentEnv === "true" &&
|
||||
@@ -58,26 +58,26 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 构建融合元数据的数据包
|
||||
* 🚀 Build data packet with embedded metadata
|
||||
*/
|
||||
private createEmbeddedChunkPacket(
|
||||
chunkData: ArrayBuffer,
|
||||
chunkMeta: EmbeddedChunkMeta
|
||||
): ArrayBuffer {
|
||||
// 1. 将元数据序列化为JSON
|
||||
// 1. Serialize metadata to JSON
|
||||
const metaJson = JSON.stringify(chunkMeta);
|
||||
const metaBytes = new TextEncoder().encode(metaJson);
|
||||
|
||||
// 2. 元数据长度(4字节)
|
||||
// 2. Metadata length (4 bytes)
|
||||
const metaLengthBuffer = new ArrayBuffer(4);
|
||||
const metaLengthView = new Uint32Array(metaLengthBuffer);
|
||||
metaLengthView[0] = metaBytes.length;
|
||||
|
||||
// 3. 构建最终的融合数据包
|
||||
// 3. Build final fused packet
|
||||
const totalLength = 4 + metaBytes.length + chunkData.byteLength;
|
||||
const finalPacket = new Uint8Array(totalLength);
|
||||
|
||||
// 拼接: [4字节长度] + [元数据] + [原始chunk数据]
|
||||
// Concatenate: [4-byte length] + [metadata] + [original chunk data]
|
||||
finalPacket.set(new Uint8Array(metaLengthBuffer), 0);
|
||||
finalPacket.set(metaBytes, 4);
|
||||
finalPacket.set(new Uint8Array(chunkData), 4 + metaBytes.length);
|
||||
@@ -86,7 +86,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 发送单个数据包(禁止分片)
|
||||
* 🚀 Send single data packet (no fragmentation)
|
||||
*/
|
||||
private async sendSingleData(
|
||||
data: string | ArrayBuffer,
|
||||
@@ -97,10 +97,10 @@ export class NetworkTransmitter {
|
||||
throw new Error("Data channel not found");
|
||||
}
|
||||
|
||||
// 简化背压控制
|
||||
// Simplified backpressure control
|
||||
await this.simpleBufferControl(dataChannel, peerId);
|
||||
|
||||
// 直接发送,不分片
|
||||
// Send directly, no fragmentation
|
||||
const sendResult = this.webrtcConnection.sendData(data, peerId);
|
||||
|
||||
if (!sendResult) {
|
||||
@@ -114,21 +114,21 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 原生背压控制 - 使用WebRTC标准机制
|
||||
* 🎯 Native backpressure control - Using WebRTC standard mechanism
|
||||
*/
|
||||
private async simpleBufferControl(
|
||||
dataChannel: RTCDataChannel,
|
||||
peerId: string
|
||||
): Promise<void> {
|
||||
const maxBuffer = 3 * 1024 * 1024; // 3MB最大缓冲
|
||||
const lowThreshold = 512 * 1024; // 512KB低阈值
|
||||
const maxBuffer = 3 * 1024 * 1024; // 3MB maximum buffer
|
||||
const lowThreshold = 512 * 1024; // 512KB low threshold
|
||||
|
||||
// 设置原生低阈值
|
||||
// Set native low threshold
|
||||
if (dataChannel.bufferedAmountLowThreshold !== lowThreshold) {
|
||||
dataChannel.bufferedAmountLowThreshold = lowThreshold;
|
||||
}
|
||||
|
||||
// 如果缓冲区超过最大值,等待降到低阈值
|
||||
// If buffer exceeds maximum, wait until it drops to low threshold
|
||||
if (dataChannel.bufferedAmount > maxBuffer) {
|
||||
const startTime = performance.now();
|
||||
const initialBuffered = dataChannel.bufferedAmount;
|
||||
@@ -140,14 +140,14 @@ export class NetworkTransmitter {
|
||||
};
|
||||
dataChannel.addEventListener("bufferedamountlow", onLow);
|
||||
|
||||
// 添加超时保护,避免无限等待
|
||||
// Add timeout protection to avoid infinite waiting
|
||||
setTimeout(() => {
|
||||
dataChannel.removeEventListener("bufferedamountlow", onLow);
|
||||
resolve();
|
||||
}, 5000); // 5秒超时
|
||||
}, 5000); // 5 second timeout
|
||||
});
|
||||
|
||||
// 仅在开发环境输出背压日志
|
||||
// Only output backpressure logs in development environment
|
||||
if (developmentEnv === "true") {
|
||||
const waitTime = performance.now() - startTime;
|
||||
postLogToBackend(
|
||||
@@ -162,7 +162,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 发送带背压控制的数据
|
||||
* 🚀 Send data with backpressure control
|
||||
*/
|
||||
async sendWithBackpressure(
|
||||
data: string | ArrayBuffer,
|
||||
@@ -174,7 +174,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
// 对于ArrayBuffer,如果超过64KB,需要分片发送(修复sendData failed)
|
||||
// For ArrayBuffer, if larger than 64KB, needs to be fragmented (fix sendData failed)
|
||||
if (data instanceof ArrayBuffer) {
|
||||
await this.sendLargeArrayBuffer(data, peerId);
|
||||
} else {
|
||||
@@ -190,7 +190,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 发送大型ArrayBuffer(分片处理)
|
||||
* 🚀 Send large ArrayBuffer (fragmentation processing)
|
||||
*/
|
||||
private async sendLargeArrayBuffer(
|
||||
data: ArrayBuffer,
|
||||
@@ -199,13 +199,13 @@ export class NetworkTransmitter {
|
||||
const networkChunkSize = 65536; // 64KB
|
||||
const totalSize = data.byteLength;
|
||||
|
||||
// 如果数据小于64KB,直接发送
|
||||
// If data is less than 64KB, send directly
|
||||
if (totalSize <= networkChunkSize) {
|
||||
await this.sendSingleData(data, peerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 大块数据分片发送
|
||||
// Fragment large data for sending
|
||||
let offset = 0;
|
||||
let fragmentIndex = 0;
|
||||
|
||||
@@ -213,7 +213,7 @@ export class NetworkTransmitter {
|
||||
const chunkSize = Math.min(networkChunkSize, totalSize - offset);
|
||||
const chunk = data.slice(offset, offset + chunkSize);
|
||||
|
||||
// 发送分片
|
||||
// Send fragment
|
||||
await this.sendSingleData(chunk, peerId);
|
||||
|
||||
offset += chunkSize;
|
||||
@@ -222,7 +222,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 获取传输统计信息
|
||||
* 📊 Get transmission statistics
|
||||
*/
|
||||
public getTransmissionStats(peerId: string) {
|
||||
const dataChannel = this.webrtcConnection.dataChannels.get(peerId);
|
||||
@@ -236,7 +236,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 清理资源
|
||||
* 🧹 Clean up resources
|
||||
*/
|
||||
public cleanup(): void {
|
||||
if (developmentEnv === "true") {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { StateManager } from "./StateManager";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
|
||||
/**
|
||||
* 🚀 进度回调类型定义
|
||||
* 🚀 Progress callback type definition
|
||||
*/
|
||||
export type ProgressCallback = (
|
||||
fileId: string,
|
||||
@@ -12,8 +12,8 @@ export type ProgressCallback = (
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* 🚀 进度跟踪器
|
||||
* 负责文件和文件夹的进度计算、速度统计、回调触发
|
||||
* 🚀 Progress tracker
|
||||
* Responsible for file and folder progress calculation, speed statistics, and callback triggering
|
||||
*/
|
||||
export class ProgressTracker {
|
||||
private speedCalculator = new SpeedCalculator();
|
||||
@@ -21,7 +21,7 @@ export class ProgressTracker {
|
||||
constructor(private stateManager: StateManager) {}
|
||||
|
||||
/**
|
||||
* 🎯 更新文件传输进度
|
||||
* 🎯 Update file transfer progress
|
||||
*/
|
||||
async updateFileProgress(
|
||||
byteLength: number,
|
||||
@@ -33,20 +33,20 @@ export class ProgressTracker {
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
if (!peerState) return;
|
||||
|
||||
// 重要修复:只有成功发送的数据才更新统计
|
||||
// Important fix: Only update statistics for successfully sent data
|
||||
if (!wasActuallySent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新文件已发送字节数
|
||||
// Update file sent bytes
|
||||
this.stateManager.updateFileBytesSent(peerId, fileId, byteLength);
|
||||
|
||||
// 计算进度ID和统计数据
|
||||
// Calculate progress ID and statistics
|
||||
let progressFileId = fileId;
|
||||
let currentBytes = this.stateManager.getFileBytesSent(peerId, fileId);
|
||||
let totalSize = fileSize;
|
||||
|
||||
// 如果文件属于文件夹,重新计算文件夹进度
|
||||
// If file belongs to a folder, recalculate folder progress
|
||||
if (peerState.currentFolderName) {
|
||||
const folderName = peerState.currentFolderName;
|
||||
const folderMeta = this.stateManager.getFolderMeta(folderName);
|
||||
@@ -54,24 +54,24 @@ export class ProgressTracker {
|
||||
progressFileId = folderName;
|
||||
totalSize = folderMeta?.totalSize || 0;
|
||||
|
||||
// 重新计算文件夹进度(从其所有文件的进度总和)
|
||||
// 这对于断点续传更加健壮和正确
|
||||
// Recalculate folder progress (sum of progress from all its files)
|
||||
// This is more robust and correct for resume downloads
|
||||
currentBytes = this.stateManager.getFolderBytesSent(peerId, folderName);
|
||||
|
||||
// 删除频繁的文件夹进度日志
|
||||
// Delete frequent folder progress logs
|
||||
}
|
||||
|
||||
// 更新速度计算器
|
||||
// Update speed calculator
|
||||
this.speedCalculator.updateSendSpeed(peerId, currentBytes);
|
||||
const speed = this.speedCalculator.getSendSpeed(peerId);
|
||||
const progress = totalSize > 0 ? currentBytes / totalSize : 0;
|
||||
|
||||
// 触发进度回调
|
||||
// Trigger progress callback
|
||||
this.triggerProgressCallback(peerId, progressFileId, progress, speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 更新文件夹传输进度
|
||||
* 🎯 Update folder transfer progress
|
||||
*/
|
||||
async updateFolderProgress(
|
||||
folderName: string,
|
||||
@@ -84,7 +84,7 @@ export class ProgressTracker {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算文件夹总进度
|
||||
// Calculate total folder progress
|
||||
let totalSentBytes = 0;
|
||||
folderMeta.fileIds.forEach((fileId) => {
|
||||
totalSentBytes += this.stateManager.getFileBytesSent(peerId, fileId);
|
||||
@@ -94,7 +94,7 @@ export class ProgressTracker {
|
||||
folderMeta.totalSize > 0 ? totalSentBytes / folderMeta.totalSize : 0;
|
||||
const speed = this.speedCalculator.getSendSpeed(peerId);
|
||||
|
||||
// 触发文件夹进度回调
|
||||
// Trigger folder progress callback
|
||||
this.triggerProgressCallback(peerId, folderName, progress, speed);
|
||||
|
||||
postLogToBackend(
|
||||
@@ -107,14 +107,14 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 设置进度回调函数
|
||||
* 🎯 Set progress callback function
|
||||
*/
|
||||
setProgressCallback(callback: ProgressCallback, peerId: string): void {
|
||||
this.stateManager.updatePeerState(peerId, { progressCallback: callback });
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 触发进度回调
|
||||
* 🎯 Trigger progress callback
|
||||
*/
|
||||
private triggerProgressCallback(
|
||||
peerId: string,
|
||||
@@ -135,14 +135,14 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 计算当前传输速度
|
||||
* 🎯 Calculate current transfer speed
|
||||
*/
|
||||
getCurrentSpeed(peerId: string): number {
|
||||
return this.speedCalculator.getSendSpeed(peerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 完成文件传输进度(设置为100%)
|
||||
* 🎯 Complete file transfer progress (set to 100%)
|
||||
*/
|
||||
completeFileProgress(fileId: string, peerId: string): void {
|
||||
this.triggerProgressCallback(peerId, fileId, 1.0, 0);
|
||||
@@ -151,7 +151,7 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 完成文件夹传输进度(设置为100%)
|
||||
* 🎯 Complete folder transfer progress (set to 100%)
|
||||
*/
|
||||
completeFolderProgress(folderName: string, peerId: string): void {
|
||||
this.triggerProgressCallback(peerId, folderName, 1.0, 0);
|
||||
@@ -160,13 +160,13 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 获取详细的进度统计信息
|
||||
* 📊 Get detailed progress statistics
|
||||
*/
|
||||
getProgressStats(peerId: string) {
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
const currentSpeed = this.getCurrentSpeed(peerId);
|
||||
|
||||
// 计算总的已发送字节数
|
||||
// Calculate total sent bytes
|
||||
let totalBytesSent = 0;
|
||||
Object.values(peerState.totalBytesSent).forEach((bytes) => {
|
||||
totalBytesSent += bytes;
|
||||
@@ -184,7 +184,7 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 获取文件夹的详细进度信息
|
||||
* 📊 Get detailed folder progress information
|
||||
*/
|
||||
getFolderProgressDetails(folderName: string, peerId: string) {
|
||||
const folderMeta = this.stateManager.getFolderMeta(folderName);
|
||||
@@ -198,8 +198,8 @@ export class ProgressTracker {
|
||||
|
||||
folderMeta.fileIds.forEach((fileId) => {
|
||||
const sent = this.stateManager.getFileBytesSent(peerId, fileId);
|
||||
// 注意:这里需要从pendingFiles获取文件大小,暂时使用0
|
||||
const total = 0; // TODO: 需要从StateManager获取文件大小
|
||||
// Note: Need to get file size from pendingFiles, temporarily using 0
|
||||
const total = 0; // TODO: Need to get file size from StateManager
|
||||
totalSent += sent;
|
||||
|
||||
fileProgresses[fileId] = {
|
||||
@@ -221,10 +221,10 @@ export class ProgressTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 清理进度跟踪资源
|
||||
* 🧹 Clean up progress tracking resources
|
||||
*/
|
||||
cleanup(): void {
|
||||
// SpeedCalculator 内部会自动清理过期数据
|
||||
// SpeedCalculator internally automatically cleans up expired data
|
||||
postLogToBackend("[DEBUG] 🧹 ProgressTracker cleaned up");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { PeerState, CustomFile, FolderMeta } from "@/types/webrtc";
|
||||
// 简化版不再依赖TransferConfig的复杂配置
|
||||
// Simplified version no longer depends on TransferConfig's complex configuration
|
||||
|
||||
/**
|
||||
* 🚀 网络性能监控指标接口
|
||||
* 🚀 Network performance monitoring metrics interface
|
||||
*/
|
||||
export interface NetworkPerformanceMetrics {
|
||||
avgClearingRate: number; // 平均网络清理速度 KB/s
|
||||
optimalThreshold: number; // 动态优化的阈值
|
||||
avgWaitTime: number; // 平均等待时间
|
||||
sampleCount: number; // 样本计数
|
||||
avgClearingRate: number; // Average network clearing speed KB/s
|
||||
optimalThreshold: number; // Dynamically optimized threshold
|
||||
avgWaitTime: number; // Average waiting time
|
||||
sampleCount: number; // Sample count
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 状态管理类
|
||||
* 集中管理所有传输相关的状态数据
|
||||
* 🚀 State management class
|
||||
* Centrally manages all transfer-related state data
|
||||
*/
|
||||
export class StateManager {
|
||||
private peerStates = new Map<string, PeerState>();
|
||||
@@ -21,10 +21,10 @@ export class StateManager {
|
||||
private pendingFolderMeta: Record<string, FolderMeta> = {};
|
||||
private networkPerformance = new Map<string, NetworkPerformanceMetrics>();
|
||||
|
||||
// ===== Peer状态管理 =====
|
||||
// ===== Peer state management =====
|
||||
|
||||
/**
|
||||
* 获取或创建peer状态
|
||||
* Get or create peer state
|
||||
*/
|
||||
public getPeerState(peerId: string): PeerState {
|
||||
if (!this.peerStates.has(peerId)) {
|
||||
@@ -42,7 +42,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新peer状态
|
||||
* Update peer state
|
||||
*/
|
||||
public updatePeerState(peerId: string, updates: Partial<PeerState>): void {
|
||||
const currentState = this.getPeerState(peerId);
|
||||
@@ -50,7 +50,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置peer状态(传输完成或出错时)
|
||||
* Reset peer state (when transfer is complete or error occurs)
|
||||
*/
|
||||
public resetPeerState(peerId: string): void {
|
||||
const peerState = this.getPeerState(peerId);
|
||||
@@ -58,51 +58,51 @@ export class StateManager {
|
||||
peerState.readOffset = 0;
|
||||
peerState.bufferQueue = [];
|
||||
peerState.isReading = false;
|
||||
// 保留 currentFolderName, totalBytesSent, progressCallback
|
||||
// Preserve currentFolderName, totalBytesSent, progressCallback
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除peer状态(peer断开连接时)
|
||||
* Remove peer state (when peer disconnects)
|
||||
*/
|
||||
public removePeerState(peerId: string): void {
|
||||
this.peerStates.delete(peerId);
|
||||
this.networkPerformance.delete(peerId);
|
||||
}
|
||||
|
||||
// ===== 文件管理 =====
|
||||
// ===== File management =====
|
||||
|
||||
/**
|
||||
* 添加待发送文件
|
||||
* Add pending file to send
|
||||
*/
|
||||
public addPendingFile(fileId: string, file: CustomFile): void {
|
||||
this.pendingFiles.set(fileId, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待发送文件
|
||||
* Get pending file to send
|
||||
*/
|
||||
public getPendingFile(fileId: string): CustomFile | undefined {
|
||||
return this.pendingFiles.get(fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除待发送文件
|
||||
* Remove pending file to send
|
||||
*/
|
||||
public removePendingFile(fileId: string): void {
|
||||
this.pendingFiles.delete(fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有待发送文件
|
||||
* Get all pending files to send
|
||||
*/
|
||||
public getAllPendingFiles(): Map<string, CustomFile> {
|
||||
return new Map(this.pendingFiles);
|
||||
}
|
||||
|
||||
// ===== 文件夹元数据管理 =====
|
||||
// ===== Folder metadata management =====
|
||||
|
||||
/**
|
||||
* 添加或更新文件夹元数据
|
||||
* Add or update folder metadata
|
||||
*/
|
||||
public addFileToFolder(folderName: string, fileId: string, fileSize: number): void {
|
||||
if (!this.pendingFolderMeta[folderName]) {
|
||||
@@ -117,22 +117,22 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件夹元数据
|
||||
* Get folder metadata
|
||||
*/
|
||||
public getFolderMeta(folderName: string): FolderMeta | undefined {
|
||||
return this.pendingFolderMeta[folderName];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文件夹元数据
|
||||
* Get all folder metadata
|
||||
*/
|
||||
public getAllFolderMeta(): Record<string, FolderMeta> {
|
||||
return { ...this.pendingFolderMeta };
|
||||
}
|
||||
// ===== 进度跟踪相关状态 =====
|
||||
// ===== Progress tracking related state =====
|
||||
|
||||
/**
|
||||
* 更新文件发送字节数
|
||||
* Update file sent bytes
|
||||
*/
|
||||
public updateFileBytesSent(peerId: string, fileId: string, bytes: number): void {
|
||||
const peerState = this.getPeerState(peerId);
|
||||
@@ -143,7 +143,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件已发送字节数
|
||||
* Get file sent bytes
|
||||
*/
|
||||
public getFileBytesSent(peerId: string, fileId: string): number {
|
||||
const peerState = this.peerStates.get(peerId);
|
||||
@@ -151,7 +151,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件夹总发送字节数
|
||||
* Calculate folder total sent bytes
|
||||
*/
|
||||
public getFolderBytesSent(peerId: string, folderName: string): number {
|
||||
const folderMeta = this.getFolderMeta(folderName);
|
||||
@@ -167,10 +167,10 @@ export class StateManager {
|
||||
return totalSent;
|
||||
}
|
||||
|
||||
// ===== 清理和重置 =====
|
||||
// ===== Cleanup and reset =====
|
||||
|
||||
/**
|
||||
* 清理所有状态(系统重置时)
|
||||
* Clean up all states (when system resets)
|
||||
*/
|
||||
public cleanup(): void {
|
||||
this.peerStates.clear();
|
||||
@@ -180,7 +180,7 @@ export class StateManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态统计信息(调试用)
|
||||
* Get state statistics (for debugging)
|
||||
*/
|
||||
public getStateStats() {
|
||||
return {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TransferConfig } from "./TransferConfig";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development!;
|
||||
/**
|
||||
* 🚀 网络块信息接口
|
||||
* 🚀 Network chunk interface
|
||||
*/
|
||||
export interface NetworkChunk {
|
||||
chunk: ArrayBuffer | null;
|
||||
@@ -14,33 +14,33 @@ export interface NetworkChunk {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 高性能流式文件读取器
|
||||
* 使用双层缓冲架构:大块批量读取 + 小块网络发送
|
||||
* 解决文件读取性能瓶颈问题
|
||||
* 🚀 High-performance streaming file reader
|
||||
* Uses a two-layer buffering architecture: large batch reading + small network chunk sending
|
||||
* Solves file reading performance bottleneck issues
|
||||
*/
|
||||
export class StreamingFileReader {
|
||||
// 配置参数
|
||||
// Configuration parameters
|
||||
private readonly BATCH_SIZE =
|
||||
TransferConfig.FILE_CONFIG.CHUNK_SIZE *
|
||||
TransferConfig.FILE_CONFIG.BATCH_SIZE; // 32MB批次
|
||||
TransferConfig.FILE_CONFIG.BATCH_SIZE; // 32MB batches
|
||||
private readonly NETWORK_CHUNK_SIZE =
|
||||
TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE; // 64KB网络块
|
||||
private readonly CHUNKS_PER_BATCH = this.BATCH_SIZE / this.NETWORK_CHUNK_SIZE; // 512块
|
||||
TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE; // 64KB network chunks
|
||||
private readonly CHUNKS_PER_BATCH = this.BATCH_SIZE / this.NETWORK_CHUNK_SIZE; // 512 chunks
|
||||
|
||||
// 文件状态
|
||||
// File state
|
||||
private file: File;
|
||||
private fileReader: FileReader;
|
||||
private totalFileSize: number;
|
||||
|
||||
// 批次缓冲状态
|
||||
private currentBatch: ArrayBuffer | null = null; // 当前32MB批次数据
|
||||
private currentBatchStartOffset = 0; // 当前批次在文件中的起始位置
|
||||
private currentChunkIndexInBatch = 0; // 当前网络块在批次中的索引
|
||||
// Batch buffering state
|
||||
private currentBatch: ArrayBuffer | null = null; // Current 32MB batch data
|
||||
private currentBatchStartOffset = 0; // Starting position of current batch in file
|
||||
private currentChunkIndexInBatch = 0; // Index of current network chunk in batch
|
||||
|
||||
// 全局状态
|
||||
private totalFileOffset = 0; // 当前在整个文件中的位置
|
||||
// Global state
|
||||
private totalFileOffset = 0; // Current position in the entire file
|
||||
private isFinished = false;
|
||||
private isReading = false; // 防止并发读取
|
||||
private isReading = false; // Prevent concurrent reading
|
||||
|
||||
constructor(file: CustomFile, startOffset: number = 0) {
|
||||
this.file = file;
|
||||
@@ -60,15 +60,15 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 核心方法:获取下一个64KB网络块
|
||||
* 🎯 Core method: Get next 64KB network chunk
|
||||
*/
|
||||
async getNextNetworkChunk(): Promise<NetworkChunk> {
|
||||
// 1. 检查是否需要加载新批次
|
||||
// 1. Check if new batch needs to be loaded
|
||||
if (this.needsNewBatch()) {
|
||||
await this.loadNextBatch();
|
||||
}
|
||||
|
||||
// 2. 检查是否已到文件末尾
|
||||
// 2. Check if end of file has been reached
|
||||
if (this.isFinished || !this.currentBatch) {
|
||||
return {
|
||||
chunk: null,
|
||||
@@ -79,15 +79,15 @@ export class StreamingFileReader {
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 从当前批次中切片出64KB网络块
|
||||
// 3. Slice 64KB network chunk from current batch
|
||||
const networkChunk = this.sliceNetworkChunkFromBatch();
|
||||
const globalChunkIndex = this.calculateGlobalChunkIndex();
|
||||
const isLast = this.isLastNetworkChunk(networkChunk);
|
||||
|
||||
// 4. 更新状态
|
||||
// 4. Update state
|
||||
this.updateChunkState(networkChunk);
|
||||
|
||||
// 删除频繁的chunk进度日志
|
||||
// Delete frequent chunk progress logs
|
||||
|
||||
return {
|
||||
chunk: networkChunk,
|
||||
@@ -99,18 +99,18 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔍 判断是否需要加载新批次
|
||||
* 🔍 Determine if new batch needs to be loaded
|
||||
*/
|
||||
private needsNewBatch(): boolean {
|
||||
return (
|
||||
this.currentBatch === null || // 还未加载任何批次
|
||||
this.currentChunkIndexInBatch >= this.CHUNKS_PER_BATCH || // 当前批次用完
|
||||
this.isCurrentBatchEmpty() // 当前批次已无数据
|
||||
this.currentBatch === null || // No batch loaded yet
|
||||
this.currentChunkIndexInBatch >= this.CHUNKS_PER_BATCH || // Current batch exhausted
|
||||
this.isCurrentBatchEmpty() // Current batch has no data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔍 判断当前批次是否为空
|
||||
* 🔍 Check if current batch is empty
|
||||
*/
|
||||
private isCurrentBatchEmpty(): boolean {
|
||||
if (!this.currentBatch) return true;
|
||||
@@ -120,11 +120,11 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📥 加载下一个32MB批次到内存
|
||||
* 📥 Load next 32MB batch into memory
|
||||
*/
|
||||
private async loadNextBatch(): Promise<void> {
|
||||
if (this.isReading) {
|
||||
// 防止并发读取
|
||||
// Prevent concurrent reading
|
||||
while (this.isReading) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
@@ -135,10 +135,10 @@ export class StreamingFileReader {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 1. 清理旧批次内存
|
||||
// 1. Clean up old batch memory
|
||||
this.currentBatch = null;
|
||||
|
||||
// 2. 计算本次要读取的大小
|
||||
// 2. Calculate size to read this time
|
||||
const remainingFileSize = this.totalFileSize - this.totalFileOffset;
|
||||
const batchSize = Math.min(this.BATCH_SIZE, remainingFileSize);
|
||||
|
||||
@@ -147,7 +147,7 @@ export class StreamingFileReader {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 执行大块文件读取
|
||||
// 3. Perform large chunk file reading
|
||||
const sliceStartTime = performance.now();
|
||||
const fileSlice = this.file.slice(
|
||||
this.totalFileOffset,
|
||||
@@ -155,7 +155,7 @@ export class StreamingFileReader {
|
||||
);
|
||||
const sliceTime = performance.now() - sliceStartTime;
|
||||
|
||||
// 4. 异步读取文件数据
|
||||
// 4. Asynchronously read file data
|
||||
const readStartTime = performance.now();
|
||||
this.currentBatch = await this.readFileSlice(fileSlice);
|
||||
const readTime = performance.now() - readStartTime;
|
||||
@@ -163,7 +163,7 @@ export class StreamingFileReader {
|
||||
this.currentBatchStartOffset = this.totalFileOffset;
|
||||
this.currentChunkIndexInBatch = 0;
|
||||
|
||||
// 仅在开发环境输出批次读取日志
|
||||
// Only output batch reading logs in development environment
|
||||
if (developmentEnv === "true") {
|
||||
const totalTime = performance.now() - startTime;
|
||||
const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000);
|
||||
@@ -186,7 +186,7 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📄 执行文件读取操作
|
||||
* 📄 Perform file reading operation
|
||||
*/
|
||||
private async readFileSlice(fileSlice: Blob): Promise<ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -214,7 +214,7 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* ✂️ 从32MB批次中切片出64KB网络块
|
||||
* ✂️ Slice 64KB network chunk from 32MB batch
|
||||
*/
|
||||
private sliceNetworkChunkFromBatch(): ArrayBuffer {
|
||||
if (!this.currentBatch) {
|
||||
@@ -235,12 +235,12 @@ export class StreamingFileReader {
|
||||
chunkStartInBatch + chunkSize
|
||||
);
|
||||
|
||||
// 删除频繁的slice日志,只在需要时输出
|
||||
// Delete frequent slice logs, only output when needed
|
||||
return networkChunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 计算全局网络块索引
|
||||
* 📊 Calculate global network chunk index
|
||||
*/
|
||||
private calculateGlobalChunkIndex(): number {
|
||||
const batchesBefore = Math.floor(
|
||||
@@ -251,34 +251,34 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 📈 计算总网络块数量
|
||||
* 📈 Calculate total network chunk count
|
||||
*/
|
||||
private calculateTotalNetworkChunks(): number {
|
||||
return Math.ceil(this.totalFileSize / this.NETWORK_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* ⏭️ 更新当前处理状态
|
||||
* ⏭️ Update current processing state
|
||||
*/
|
||||
private updateChunkState(chunk: ArrayBuffer): void {
|
||||
this.currentChunkIndexInBatch++;
|
||||
this.totalFileOffset += chunk.byteLength;
|
||||
|
||||
// 检查是否到达文件末尾
|
||||
// Check if end of file has been reached
|
||||
if (this.totalFileOffset >= this.totalFileSize) {
|
||||
this.isFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🏁 判断是否为最后一个网络块
|
||||
* 🏁 Check if this is the last network chunk
|
||||
*/
|
||||
private isLastNetworkChunk(chunk: ArrayBuffer): boolean {
|
||||
return this.totalFileOffset + chunk.byteLength >= this.totalFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 获取读取进度信息
|
||||
* 📊 Get reading progress information
|
||||
*/
|
||||
public getProgress(): {
|
||||
readBytes: number;
|
||||
@@ -317,7 +317,7 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔄 重置读取器状态(用于重新开始读取)
|
||||
* 🔄 Reset reader state (for restarting reading)
|
||||
*/
|
||||
public reset(startOffset: number = 0): void {
|
||||
this.totalFileOffset = startOffset;
|
||||
@@ -332,22 +332,22 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 清理和释放资源
|
||||
* 🧹 Cleanup and release resources
|
||||
*/
|
||||
public cleanup(): void {
|
||||
// 中断正在进行的文件读取
|
||||
// Abort ongoing file reading
|
||||
if (this.isReading) {
|
||||
this.fileReader.abort();
|
||||
}
|
||||
|
||||
// 清理内存
|
||||
// Clean up memory
|
||||
this.currentBatch = null;
|
||||
this.isFinished = true;
|
||||
this.isReading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔍 获取调试信息
|
||||
* 🔍 Get debug information
|
||||
*/
|
||||
public getDebugInfo() {
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 🚀 传输配置管理类
|
||||
* 集中管理所有文件传输相关的配置参数
|
||||
* 🚀 Transfer configuration management class
|
||||
* Centrally manages all file transfer related configuration parameters
|
||||
*/
|
||||
export class TransferConfig {
|
||||
// 文件I/O相关配置
|
||||
// File I/O related configuration
|
||||
static readonly FILE_CONFIG = {
|
||||
CHUNK_SIZE: 4194304, // 4MB - 文件读取块大小,减少FileReader调用次数
|
||||
BATCH_SIZE: 8, // 8个chunk批处理 - 32MB批处理提升性能
|
||||
NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小,修复sendData failed
|
||||
CHUNK_SIZE: 4194304, // 4MB - File reading chunk size, reduces FileReader calls
|
||||
BATCH_SIZE: 8, // 8 chunks batch processing - 32MB batch processing improves performance
|
||||
NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC safe sending size, fixes sendData failed
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
/**
|
||||
* 🚀 文件传输模块统一导出
|
||||
* 提供模块化的文件传输服务
|
||||
* 🚀 File transfer module unified export
|
||||
* Provides modular file transfer services
|
||||
*/
|
||||
|
||||
// 配置管理
|
||||
// Configuration management
|
||||
export { TransferConfig } from "./TransferConfig";
|
||||
|
||||
// 状态管理
|
||||
// State management
|
||||
export { StateManager } from "./StateManager";
|
||||
export type { NetworkPerformanceMetrics } from "./StateManager";
|
||||
|
||||
// 高性能文件读取
|
||||
// High-performance file reading
|
||||
export { StreamingFileReader } from "./StreamingFileReader";
|
||||
export type { NetworkChunk } from "./StreamingFileReader";
|
||||
|
||||
// 网络传输
|
||||
// Network transmission
|
||||
export { NetworkTransmitter } from "./NetworkTransmitter";
|
||||
|
||||
// 消息处理
|
||||
// Message handling
|
||||
export { MessageHandler } from "./MessageHandler";
|
||||
export type { MessageHandlerDelegate } from "./MessageHandler";
|
||||
|
||||
// 进度跟踪
|
||||
// Progress tracking
|
||||
export { ProgressTracker } from "./ProgressTracker";
|
||||
export type { ProgressCallback } from "./ProgressTracker";
|
||||
|
||||
// 主编排器
|
||||
// Main orchestrator
|
||||
export { FileTransferOrchestrator } from "./FileTransferOrchestrator";
|
||||
|
||||
/**
|
||||
* 🎯 便捷创建函数 - 快速初始化文件传输服务
|
||||
* 🎯 Convenience creation function - Quick initialization of file transfer services
|
||||
*/
|
||||
import WebRTC_Initiator from "../webrtc_Initiator";
|
||||
import { FileTransferOrchestrator } from "./FileTransferOrchestrator";
|
||||
|
||||
@@ -352,7 +352,7 @@ export default class BaseWebRTC {
|
||||
};
|
||||
|
||||
dataChannel.onmessage = (event) => {
|
||||
// 增强的数据类型检测 - 支持Firefox的多种二进制数据格式
|
||||
// Enhanced data type detection - supports multiple binary data formats in Firefox
|
||||
let dataType = "Unknown";
|
||||
let dataSize = 0;
|
||||
|
||||
@@ -372,7 +372,7 @@ export default class BaseWebRTC {
|
||||
dataType = "TypedArray";
|
||||
dataSize = event.data.byteLength;
|
||||
} else {
|
||||
// 详细的未知类型调试信息
|
||||
// Detailed unknown type debug information
|
||||
dataType = `Unknown(${Object.prototype.toString.call(event.data)})`;
|
||||
dataSize =
|
||||
event.data?.length || event.data?.size || event.data?.byteLength || 0;
|
||||
@@ -477,7 +477,7 @@ export default class BaseWebRTC {
|
||||
const dataChannel = this.dataChannels.get(peerId);
|
||||
if (dataChannel?.readyState === "open") {
|
||||
try {
|
||||
// Firefox兼容性调试:记录发送详细信息
|
||||
// Firefox compatibility debugging: Log sending details
|
||||
const dataType =
|
||||
typeof data === "string"
|
||||
? "string"
|
||||
|
||||
Reference in New Issue
Block a user