fix:Try to fix the problem of incomplete file size in resumable download

This commit is contained in:
david_bai
2025-09-14 07:35:34 +08:00
parent 8627544946
commit 33f2f041ac
10 changed files with 354 additions and 121 deletions
@@ -13,7 +13,7 @@ import type { Messages } from "@/types/messages";
import { useFileTransferStore } from "@/stores/fileTransferStore";
import { supportsAutoDownload } from "@/lib/browserUtils";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
function formatFolderDis(template: string, num: number, size: string) {
return template.replace("{num}", num.toString()).replace("{size}", size);
@@ -261,7 +261,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
if (isAutoDownloadSupported) {
// Browsers that support automatic downloads like Chrome: Download directly
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[Download Debug] Auto-downloading file: ${item.name}`
);
@@ -269,7 +269,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
onDownload(item);
} else {
// Non-Chrome browsers: Set to save status, wait for user manual click
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[Download Debug] Setting pendingSave for non-Chrome browser: ${item.name}`
);
@@ -280,7 +280,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
}));
}
} else {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`Skipping download logic - isSaveToDisk: ${isSaveToDisk}, onDownload: ${!!onDownload}`
);
+235 -67
View File
@@ -22,7 +22,7 @@ import {
EmbeddedChunkMeta,
} from "@/types/webrtc";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 Strict Sequential Buffering Writer - Optimizes large file disk I/O performance
*/
@@ -40,6 +40,18 @@ class SequencedDiskWriter {
/**\n * Write a chunk, automatically managing order and buffering\n */
async writeChunk(chunkIndex: number, chunk: ArrayBuffer): Promise<void> {
// 🔍 调试writeChunk调用
if (
developmentEnv === "development" &&
(chunkIndex <= 5 || chunkIndex === this.nextWriteIndex)
) {
postLogToBackend(
`[DEBUG-RESUME] 🎯 WriteChunk called - received:${chunkIndex}, expected:${
this.nextWriteIndex
}, match:${chunkIndex === this.nextWriteIndex}`
);
}
// 1. If it is the expected next chunk, write immediately
if (chunkIndex === this.nextWriteIndex) {
await this.flushSequentialChunks(chunk);
@@ -50,26 +62,26 @@ class SequencedDiskWriter {
if (chunkIndex > this.nextWriteIndex) {
if (this.writeQueue.size < this.maxBufferSize) {
this.writeQueue.set(chunkIndex, chunk);
if (developmentEnv === "true") {
postLogToBackend(
`[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}`
);
}
// if (developmentEnv === "development") {
// postLogToBackend(
// `[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}`
// );
// }
} else {
// Buffer full, forcing processing of the earliest chunk to free up space
await this.forceFlushOldest();
this.writeQueue.set(chunkIndex, chunk);
if (developmentEnv === "true") {
postLogToBackend(
`[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}`
);
}
// if (developmentEnv === "development") {
// postLogToBackend(
// `[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}`
// );
// }
}
return;
}
// 3. If the chunk is expired, log a warning but ignore (already written)
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ⚠️ DUPLICATE chunk #${chunkIndex} ignored (already written #${this.nextWriteIndex})`
);
@@ -87,7 +99,7 @@ class SequencedDiskWriter {
await this.stream.write(firstChunk);
this.totalWritten += firstChunk.byteLength;
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ✓ DISK_WRITE chunk #${this.nextWriteIndex} - ${firstChunk.byteLength} bytes, total: ${this.totalWritten}`
);
@@ -123,7 +135,7 @@ class SequencedDiskWriter {
}
if (flushCount > 0) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 🔥 SEQUENTIAL_FLUSH ${flushCount} chunks, now at #${this.nextWriteIndex}, queue: ${this.writeQueue.size}`
);
@@ -131,6 +143,13 @@ class SequencedDiskWriter {
}
}
/**
* Get the next expected write index
*/
get expectedIndex(): number {
return this.nextWriteIndex;
}
/**
* Force refresh the earliest chunk to release buffer space
*/
@@ -142,11 +161,11 @@ class SequencedDiskWriter {
const chunk = this.writeQueue.get(oldestIndex)!;
// Warning: Unordered write
if (developmentEnv === "true") {
postLogToBackend(
`[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})`
);
}
// if (developmentEnv === "development") {
// postLogToBackend(
// `[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})`
// );
// }
// Use seek to write at the correct position (fallback handling)
const fileOffset = oldestIndex * 65536; // Assume each chunk is 64KB
@@ -204,7 +223,7 @@ class SequencedDiskWriter {
const fileOffset = chunkIndex * 65536;
await this.stream.seek(fileOffset);
await this.stream.write(chunk);
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 💾 FINAL_FLUSH chunk #${chunkIndex} at cleanup`
);
@@ -410,6 +429,38 @@ class FileReceiver {
const receptionPromise = new Promise<void>((resolve, reject) => {
const expectedChunksCount = Math.ceil((fileInfo.size - offset) / 65536); // Calculate expected chunk count
// 🔍 调试expectedChunksCount计算
if (developmentEnv === "development") {
const totalChunks = Math.ceil(fileInfo.size / 65536);
const startChunkIndex = Math.floor(offset / 65536);
const calculatedExpected = totalChunks - startChunkIndex;
postLogToBackend(`[DEBUG-CHUNKS] File: ${fileInfo.name}`);
postLogToBackend(
`[DEBUG-CHUNKS] File size: ${fileInfo.size}, offset: ${offset}`
);
postLogToBackend(
`[DEBUG-CHUNKS] Total chunks in file: ${totalChunks} (0-${
totalChunks - 1
})`
);
postLogToBackend(
`[DEBUG-CHUNKS] Start chunk index: ${startChunkIndex}`
);
postLogToBackend(
`[DEBUG-CHUNKS] Expected chunks calculation: (${fileInfo.size} - ${offset}) / 65536 = ${expectedChunksCount}`
);
postLogToBackend(
`[DEBUG-CHUNKS] Alternative calculation: ${totalChunks} - ${startChunkIndex} = ${calculatedExpected}`
);
if (expectedChunksCount !== calculatedExpected) {
postLogToBackend(
`[DEBUG-CHUNKS] ⚠️ MISMATCH: ${expectedChunksCount} vs ${calculatedExpected}`
);
}
}
this.activeFileReception = {
meta: fileInfo,
chunks: new Array(expectedChunksCount).fill(null), // 🚀 Initialize as an empty array arranged by index
@@ -435,7 +486,7 @@ class FileReceiver {
this.webrtcConnection.sendData(JSON.stringify(request), this.peerId);
this.log("log", "Sent fileRequest", { request });
} else {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ERROR: Cannot send fileRequest - no peerId available!`
);
@@ -507,7 +558,7 @@ class FileReceiver {
return true;
});
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📁 All files in folder completed - ${folderName}, files: ${completedFileIds.length}/${folderProgress.fileIds.length}`
);
@@ -533,7 +584,7 @@ class FileReceiver {
try {
const arrayBuffer = await data.arrayBuffer();
if (data.size !== arrayBuffer.byteLength) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ⚠️ Blob size mismatch: ${data.size}${arrayBuffer.byteLength}`
);
@@ -541,7 +592,7 @@ class FileReceiver {
}
return arrayBuffer;
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ Blob conversion failed: ${error}`);
}
return null;
@@ -556,13 +607,13 @@ class FileReceiver {
new Uint8Array(newArrayBuffer).set(uint8Array);
return newArrayBuffer;
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ TypedArray conversion failed: ${error}`);
}
return null;
}
} else {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call(
data
@@ -584,7 +635,7 @@ class FileReceiver {
try {
// 1. Check minimum packet length
if (arrayBuffer.byteLength < 4) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ Invalid embedded packet - too small: ${arrayBuffer.byteLength}`
);
@@ -599,7 +650,7 @@ class FileReceiver {
// 3. Verify packet integrity
const expectedTotalLength = 4 + metaLength;
if (arrayBuffer.byteLength < expectedTotalLength) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ Incomplete embedded packet - expected: ${expectedTotalLength}, got: ${arrayBuffer.byteLength}`
);
@@ -618,7 +669,7 @@ class FileReceiver {
// 6. Verify chunk data size
if (chunkData.byteLength !== chunkMeta.chunkSize) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ⚠️ Chunk size mismatch - meta: ${chunkMeta.chunkSize}, actual: ${chunkData.byteLength}`
);
@@ -627,7 +678,7 @@ class FileReceiver {
return { chunkMeta, chunkData };
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ Failed to parse embedded packet: ${error}`
);
@@ -664,7 +715,7 @@ class FileReceiver {
if (arrayBuffer) {
if (!this.activeFileReception) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ERROR: Received file chunk but no active file reception!`
);
@@ -679,7 +730,7 @@ class FileReceiver {
// 🚀 Unified processing: All data is processed as embedded packets
await this.handleEmbeddedChunkPacket(arrayBuffer);
} else {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ERROR: Failed to convert binary data to ArrayBuffer`
);
@@ -773,7 +824,7 @@ class FileReceiver {
// Verify fileId match
if (chunkMeta.fileId !== reception.meta.fileId) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ⚠️ FileId mismatch - expected: ${reception.meta.fileId}, got: ${chunkMeta.fileId}`
);
@@ -781,44 +832,74 @@ class FileReceiver {
return;
}
// Update expected chunks count (may differ from initial estimate)
// 🔧 修复:续传时不要调整expectedChunksCount
// chunkMeta.totalChunks是文件总chunk数,但续传时我们只接收部分chunks
if (chunkMeta.totalChunks !== reception.expectedChunksCount) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
const startChunkIndex = Math.floor(reception.initialOffset / 65536);
const calculatedExpected = chunkMeta.totalChunks - startChunkIndex;
postLogToBackend(
`[DEBUG] ⚠️ Chunk count adjustment - expected: ${reception.expectedChunksCount}, actual: ${chunkMeta.totalChunks}`
`[DEBUG-CHUNKS] Chunk count info - fileTotal: ${chunkMeta.totalChunks}, currentExpected: ${reception.expectedChunksCount}, calculatedExpected: ${calculatedExpected}, startChunk: ${startChunkIndex}`
);
}
reception.expectedChunksCount = chunkMeta.totalChunks;
// Adjust chunks array size
if (reception.chunks.length < chunkMeta.totalChunks) {
const newChunks = new Array(chunkMeta.totalChunks).fill(null);
reception.chunks.forEach((chunk, index) => {
if (index < newChunks.length) newChunks[index] = chunk;
});
reception.chunks = newChunks;
// 🚫 不再调整expectedChunksCount,保持续传时的正确数量
// reception.expectedChunksCount = chunkMeta.totalChunks; // 这行导致了问题!
if (reception.expectedChunksCount !== calculatedExpected) {
postLogToBackend(
`[DEBUG-CHUNKS] ⚠️ Expected chunks mismatch, should be ${calculatedExpected}`
);
}
}
}
// Store chunk by index
const chunkIndex = chunkMeta.chunkIndex;
if (chunkIndex >= 0 && chunkIndex < reception.chunks.length) {
reception.chunks[chunkIndex] = chunkData;
reception.chunkSequenceMap.set(chunkIndex, true);
// Store chunk by index - 🔧 修复:将绝对索引映射到相对索引
const absoluteChunkIndex = chunkMeta.chunkIndex; // 发送端的绝对索引(如967-3650)
const startChunkIndex = Math.floor(reception.initialOffset / 65536); // 续传起始索引(如967
const relativeChunkIndex = absoluteChunkIndex - startChunkIndex; // 在chunks数组中的相对索引(如0-2683)
if (developmentEnv === "development" && absoluteChunkIndex <= 970) {
postLogToBackend(
`[DEBUG-CHUNKS] Index mapping - absolute:${absoluteChunkIndex}, start:${startChunkIndex}, relative:${relativeChunkIndex}, arraySize:${reception.chunks.length}`
);
}
if (
relativeChunkIndex >= 0 &&
relativeChunkIndex < reception.chunks.length
) {
reception.chunks[relativeChunkIndex] = chunkData;
reception.chunkSequenceMap.set(absoluteChunkIndex, true); // 序列映射仍使用绝对索引
reception.receivedChunksCount++;
// Update progress
this.updateProgress(chunkData.byteLength);
if (reception.sequencedWriter) {
// 🚀 Use strict sequential write management
await reception.sequencedWriter.writeChunk(chunkIndex, chunkData);
// 🔍 调试chunk接收匹配 (前5个和后5个chunks)
const lastFewChunks =
relativeChunkIndex >= reception.expectedChunksCount - 5;
if (
developmentEnv === "development" &&
(absoluteChunkIndex <= 970 || lastFewChunks)
) {
postLogToBackend(
`[DEBUG-CHUNKS] 📦 Chunk #${absoluteChunkIndex} received - relative:${relativeChunkIndex}, size:${chunkData.byteLength}, writerExpects:${reception.sequencedWriter.expectedIndex}, isLastFew:${lastFewChunks}`
);
}
// 🚀 Use strict sequential write management - 使用绝对索引
await reception.sequencedWriter.writeChunk(
absoluteChunkIndex,
chunkData
);
}
} else {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ Invalid chunk index - ${chunkIndex}, expected 0-${
reception.chunks.length - 1
}`
`[DEBUG-CHUNKS] ❌ Invalid relative chunk index - absolute:${absoluteChunkIndex}, relative:${relativeChunkIndex}, arraySize:${
reception.chunks.length
}, expected:0-${reception.chunks.length - 1}`
);
}
}
@@ -840,7 +921,8 @@ class FileReceiver {
const currentTotalSize = reception.chunks.reduce((sum, chunk) => {
return sum + (chunk instanceof ArrayBuffer ? chunk.byteLength : 0);
}, 0);
const expectedSize = reception.meta.size;
// 🔧 修复:续传时应该比较的是剩余文件大小,不是整个文件大小
const expectedSize = reception.meta.size - reception.initialOffset;
// 🚀 Unified integrity check: sequential reception mode
let sequencedCount = 0;
@@ -850,10 +932,55 @@ class FileReceiver {
}
}
const isSequencedComplete = sequencedCount === expectedChunks;
const sizeComplete = currentTotalSize >= expectedSize;
const isDataComplete = isSequencedComplete && sizeComplete;
// 🔍 详细调试完成检查 (减少频率,只在关键时刻输出)
if (
developmentEnv === "development" &&
(isDataComplete ||
sequencedCount % 500 === 0 ||
sequencedCount > expectedChunks - 10)
) {
// 检查最后几个chunks的状态 (显示相对索引)
const lastChunkIndex = expectedChunks - 1;
const lastFewChunks = [];
const startChunkIndex = Math.floor(reception.initialOffset / 65536);
for (let i = Math.max(0, lastChunkIndex - 3); i <= lastChunkIndex; i++) {
const chunk = reception.chunks[i];
const exists = chunk instanceof ArrayBuffer;
const size = exists ? (chunk as ArrayBuffer).byteLength : 0;
const absoluteIndex = startChunkIndex + i; // 对应的绝对索引
lastFewChunks.push(`rel#${i}(abs#${absoluteIndex}):${exists}(${size})`);
}
postLogToBackend(
`[DEBUG-COMPLETE] Check completion - file:${reception.meta.name}`
);
postLogToBackend(
`[DEBUG-COMPLETE] Chunks: received:${sequencedCount}/${expectedChunks}, isSequenceComplete:${isSequencedComplete}`
);
postLogToBackend(
`[DEBUG-COMPLETE] Size: current:${currentTotalSize}, expected:${expectedSize}, sizeComplete:${sizeComplete}, diff:${
expectedSize - currentTotalSize
}`
);
postLogToBackend(
`[DEBUG-COMPLETE] LastChunks: ${lastFewChunks.join(", ")}`
);
postLogToBackend(
`[DEBUG-COMPLETE] IsDataComplete: ${isDataComplete}, isFinalized: ${reception.isFinalized}`
);
if (reception.sequencedWriter) {
const writerStatus = reception.sequencedWriter.getBufferStatus();
postLogToBackend(
`[DEBUG-COMPLETE] SequencedWriter: nextIndex:${writerStatus.nextIndex}, totalWritten:${writerStatus.totalWritten}, queueSize:${writerStatus.queueSize}`
);
}
}
// Prevent duplicate finalize
if (reception.isFinalized) {
return;
@@ -862,6 +989,12 @@ class FileReceiver {
if (isDataComplete) {
reception.isFinalized = true;
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-COMPLETE] ✅ Starting finalization - isDataComplete:${isDataComplete}`
);
}
try {
await this.finalizeFileReceive();
@@ -870,7 +1003,7 @@ class FileReceiver {
}
this.activeFileReception = null;
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ Auto-finalize ERROR: ${error}`);
}
if (reception.completionNotifier) {
@@ -958,10 +1091,14 @@ class FileReceiver {
startChunkIndex
);
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📢 SEQUENCED_WRITER created - startIndex: ${startChunkIndex}, offset: ${offset}`
);
// 🔍 调试续传接收期望
postLogToBackend(
`[DEBUG-RESUME] 🎯 SequencedWriter expects - startIndex:${startChunkIndex}, offset:${offset}, calculatedFrom:${offset}/65536`
);
}
} catch (err) {
this.fireError("Failed to create file on disk", {
@@ -994,28 +1131,59 @@ class FileReceiver {
private async finalizeLargeFileReceive(): Promise<void> {
const reception = this.activeFileReception;
if (!reception?.writeStream || !reception.fileHandle) return;
if (!reception?.writeStream || !reception.fileHandle) {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-FINALIZE] ❌ Cannot finalize - missing writeStream:${!!reception?.writeStream} or fileHandle:${!!reception?.fileHandle}`
);
}
return;
}
try {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-FINALIZE] 🚀 Starting finalization for ${reception.meta.name}`
);
}
// 🚀 First close the strict sequential writing manager (flush all buffers)
if (reception.sequencedWriter) {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG-FINALIZE] Closing SequencedWriter...`);
}
await reception.sequencedWriter.close();
const status = reception.sequencedWriter.getBufferStatus();
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}`
`[DEBUG-FINALIZE] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}`
);
}
reception.sequencedWriter = null;
}
// Then close the file stream
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-FINALIZE] About to close writeStream for ${reception.meta.name}`
);
}
await reception.writeStream.close();
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG-FINALIZE] ✅ WriteStream closed successfully`);
}
if (developmentEnv === "true") {
postLogToBackend(`[DEBUG] ✅ LARGE_FILE finalized successfully`);
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-FINALIZE] ✅ LARGE_FILE finalized successfully - ${reception.meta.name}`
);
}
} catch (error) {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG-FINALIZE] ❌ Error during finalization: ${error}`
);
}
this.fireError("Error finalizing large file", { error });
}
}
@@ -1040,7 +1208,7 @@ class FileReceiver {
// Final verification
const sizeDifference = reception.meta.size - totalChunkSize;
if (sizeDifference !== 0) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes`
);
@@ -1131,7 +1299,7 @@ class FileReceiver {
this.peerId
);
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📤 Sent folderReceiveComplete - folderName: ${folderName}, completedFiles: ${completedFileIds.length}, allStoreUpdated: ${allStoreUpdated}, success: ${success}`
);
+1 -1
View File
@@ -4,7 +4,7 @@ export const trackReferrer = async () => {
// Get URL parameters
const urlParams = new URLSearchParams(window.location.search);
let ref = urlParams.get("ref");
if (process.env.NEXT_PUBLIC_development === "false") {
if (process.env.NODE_ENV === "production") {
ref = urlParams.get("ref") || "noRef"; // Production environment, count daily active users, record as noRef if there is no ref
}
@@ -14,7 +14,7 @@ import { StreamingFileReader } from "./StreamingFileReader";
import { TransferConfig } from "./TransferConfig";
import WebRTC_Initiator from "../webrtc_Initiator";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 File transfer orchestrator
* Integrates all components to provide unified file transfer services
@@ -211,7 +211,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
peerState.readOffset || 0
);
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 🚀 Starting transfer - file: ${file.name}, size: ${(
file.size /
@@ -301,20 +301,32 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
}
}
if (developmentEnv === "true") {
if (developmentEnv === "development") {
const totalTime = performance.now() - transferStartTime;
const avgSpeedMBps = totalBytesSent / 1024 / 1024 / (totalTime / 1000);
const expectedTotalChunks = Math.ceil(file.size / 65536);
const startOffset = peerState.readOffset || 0;
const startChunkIndex = Math.floor(startOffset / 65536);
const expectedChunksSent = expectedTotalChunks - startChunkIndex;
postLogToBackend(
`[DEBUG] ✅ Transfer complete - file: ${file.name}, time: ${(
`[DEBUG-CHUNKS] ✅ Transfer complete - file: ${file.name}, time: ${(
totalTime / 1000
).toFixed(1)}s, speed: ${avgSpeedMBps.toFixed(
1
)}MB/s, chunks: ${networkChunkIndex}`
).toFixed(1)}s, speed: ${avgSpeedMBps.toFixed(1)}MB/s`
);
postLogToBackend(
`[DEBUG-CHUNKS] Chunks sent: ${networkChunkIndex}, expected: ${expectedChunksSent}, startChunk: ${startChunkIndex}, totalFileChunks: ${expectedTotalChunks}`
);
if (networkChunkIndex !== expectedChunksSent) {
postLogToBackend(
`[DEBUG-CHUNKS] ⚠️ CHUNK MISMATCH: sent ${networkChunkIndex} but expected ${expectedChunksSent}`
);
}
}
} catch (error: any) {
const errorMessage = `Streaming send error: ${error.message}`;
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ Transfer error: ${errorMessage}`);
}
this.fireError(errorMessage, {
@@ -428,7 +440,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
public handlePeerReconnection(peerId: string): void {
// Clear all transfer states for this peer
this.stateManager.clearPeerState(peerId);
if (developmentEnv === "true")
if (developmentEnv === "development")
this.log(
"log",
`Successfully reset transfer state for reconnected peer ${peerId}`
@@ -443,7 +455,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
this.networkTransmitter.cleanup();
this.progressTracker.cleanup();
this.messageHandler.cleanup();
if (developmentEnv === "true")
if (developmentEnv === "development")
this.log("log", "FileTransferOrchestrator cleaned up");
}
}
+3 -3
View File
@@ -6,7 +6,7 @@ import {
} from "@/types/webrtc";
import { StateManager } from "./StateManager";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 Message handling interface - Communicate with main orchestrator
*/
@@ -120,7 +120,7 @@ export class MessageHandler {
message: FolderReceiveComplete,
peerId: string
): void {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📥 Folder complete - folderName: ${message.folderName}, files: ${message.completedFileIds.length}`
);
@@ -172,7 +172,7 @@ export class MessageHandler {
* 🧹 Clean up resources
*/
public cleanup(): void {
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend("[DEBUG] 🧹 MessageHandler cleaned up");
}
}
+7 -7
View File
@@ -2,7 +2,7 @@ import { EmbeddedChunkMeta } from "@/types/webrtc";
import { StateManager } from "./StateManager";
import WebRTC_Initiator from "../webrtc_Initiator";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 Network transmitter - Simplified version
* Uses WebRTC native bufferedAmountLowThreshold for backpressure control
@@ -34,7 +34,7 @@ export class NetworkTransmitter {
// Key node logs (development environment only)
if (
developmentEnv === "true" &&
developmentEnv === "development" &&
(metadata.chunkIndex % 100 === 0 || metadata.isLastChunk)
) {
postLogToBackend(
@@ -48,7 +48,7 @@ export class NetworkTransmitter {
return true;
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] ❌ CHUNK #${metadata.chunkIndex} send failed: ${error}`
);
@@ -106,7 +106,7 @@ export class NetworkTransmitter {
if (!sendResult) {
const errorMessage = `sendData failed`;
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ ${errorMessage}`);
}
throw new Error(errorMessage);
@@ -148,7 +148,7 @@ export class NetworkTransmitter {
});
// Only output backpressure logs in development environment
if (developmentEnv === "true") {
if (developmentEnv === "development") {
const waitTime = performance.now() - startTime;
postLogToBackend(
`[DEBUG] 🚀 BACKPRESSURE - wait: ${waitTime.toFixed(
@@ -182,7 +182,7 @@ export class NetworkTransmitter {
}
} catch (error) {
const errorMessage = `sendWithBackpressure failed: ${error}`;
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ ${errorMessage}`);
}
throw new Error(errorMessage);
@@ -239,7 +239,7 @@ export class NetworkTransmitter {
* 🧹 Clean up resources
*/
public cleanup(): void {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend("[DEBUG] 🧹 NetworkTransmitter cleaned up");
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { SpeedCalculator } from "@/lib/speedCalculator";
import { StateManager } from "./StateManager";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 Progress callback type definition
*/
@@ -225,7 +225,7 @@ export class ProgressTracker {
*/
cleanup(): void {
// SpeedCalculator internally automatically cleans up expired data
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend("[DEBUG] 🧹 ProgressTracker cleaned up");
}
}
+64 -11
View File
@@ -1,7 +1,7 @@
import { CustomFile } from "@/types/webrtc";
import { TransferConfig } from "./TransferConfig";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!;
const developmentEnv = process.env.NODE_ENV;
/**
* 🚀 Network chunk interface
*/
@@ -46,9 +46,11 @@ export class StreamingFileReader {
this.file = file;
this.totalFileSize = file.size;
this.totalFileOffset = startOffset;
// 🔧 修复:续传时currentBatchStartOffset应该从startOffset开始
this.currentBatchStartOffset = startOffset;
this.fileReader = new FileReader();
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 📖 StreamingFileReader created - file: ${file.name}, size: ${(
this.totalFileSize /
@@ -56,6 +58,13 @@ export class StreamingFileReader {
1024
).toFixed(1)}MB`
);
// 🔍 调试续传初始化
const expectedGlobalChunk = Math.floor(
startOffset / this.NETWORK_CHUNK_SIZE
);
postLogToBackend(
`[DEBUG-RESUME] 🏗️ StreamingFileReader created - totalFileOffset:${this.totalFileOffset}, currentBatchStartOffset:${this.currentBatchStartOffset}, expectedGlobalChunk:${expectedGlobalChunk}`
);
}
}
@@ -89,6 +98,15 @@ export class StreamingFileReader {
// Delete frequent chunk progress logs
// 🔍 调试chunk发送 (前5个和最后5个chunks)
const totalChunks = this.calculateTotalNetworkChunks();
const isLastFew = globalChunkIndex >= (totalChunks - 5);
if (developmentEnv === "development" && (globalChunkIndex <= 5 || isLastFew || isLast)) {
postLogToBackend(
`[DEBUG-CHUNKS] 📤 Send chunk #${globalChunkIndex}/${totalChunks} - size:${networkChunk.byteLength}, isLast:${isLast}, fileOffset:${this.totalFileOffset - networkChunk.byteLength}`
);
}
return {
chunk: networkChunk,
chunkIndex: globalChunkIndex,
@@ -160,11 +178,24 @@ export class StreamingFileReader {
this.currentBatch = await this.readFileSlice(fileSlice);
const readTime = performance.now() - readStartTime;
this.currentBatchStartOffset = this.totalFileOffset;
this.currentChunkIndexInBatch = 0;
const batchStartOffset = this.totalFileOffset;
this.currentBatchStartOffset = batchStartOffset;
// 🔧 修复:如果不是从batch边界开始,说明是续传情况,需要计算正确的batch内索引
if (batchStartOffset % this.BATCH_SIZE !== 0) {
// 续传情况:不是从batch边界开始
const globalChunkIndex = Math.floor(
batchStartOffset / this.NETWORK_CHUNK_SIZE
);
this.currentChunkIndexInBatch =
globalChunkIndex % this.CHUNKS_PER_BATCH;
} else {
// 正常情况:从batch边界开始
this.currentChunkIndexInBatch = 0;
}
// Only output batch reading logs in development environment
if (developmentEnv === "true") {
if (developmentEnv === "development") {
const totalTime = performance.now() - startTime;
const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000);
postLogToBackend(
@@ -174,9 +205,15 @@ export class StreamingFileReader {
1
)}MB/s`
);
// 🔍 调试batch内索引设置
postLogToBackend(
`[DEBUG-RESUME] 📖 BATCH loaded - batchStartOffset:${batchStartOffset}, currentChunkIndexInBatch:${
this.currentChunkIndexInBatch
}, isResume:${batchStartOffset % this.BATCH_SIZE !== 0}`
);
}
} catch (error) {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`[DEBUG] ❌ BATCH_READ failed: ${error}`);
}
throw new Error(`Failed to load file batch: ${error}`);
@@ -247,7 +284,19 @@ export class StreamingFileReader {
this.currentBatchStartOffset / this.BATCH_SIZE
);
const chunksInPreviousBatches = batchesBefore * this.CHUNKS_PER_BATCH;
return chunksInPreviousBatches + this.currentChunkIndexInBatch;
const result = chunksInPreviousBatches + this.currentChunkIndexInBatch;
// 🔍 调试chunk索引计算
if (
developmentEnv === "development" &&
this.currentChunkIndexInBatch <= 5
) {
postLogToBackend(
`[DEBUG-RESUME] 🧮 calculateGlobalChunkIndex - batchStartOffset:${this.currentBatchStartOffset}, batchesBefore:${batchesBefore}, chunksInPrev:${chunksInPreviousBatches}, chunkInBatch:${this.currentChunkIndexInBatch}, result:${result}`
);
}
return result;
}
/**
@@ -324,10 +373,14 @@ export class StreamingFileReader {
this.isFinished = false;
this.isReading = false;
this.currentBatch = null;
this.currentBatchStartOffset = 0;
this.currentChunkIndexInBatch = 0;
if (developmentEnv === "true") {
postLogToBackend(`[DEBUG] 🔄 StreamingFileReader reset`);
// 🔧 修复:reset时也要正确设置currentBatchStartOffset
this.currentBatchStartOffset = startOffset;
this.currentChunkIndexInBatch = 0; // 重置为0loadNextBatch会重新计算
if (developmentEnv === "development") {
postLogToBackend(
`[DEBUG] 🔄 StreamingFileReader reset - startOffset:${startOffset}`
);
}
}
+5 -5
View File
@@ -1,7 +1,7 @@
// Initiator flow: Join room; receive 'ready' event (this event is triggered by the socket server after a new recipient enters) -> createPeerConnection + createDataChannel -> createAndSendOffer
import BaseWebRTC, { WebRTCConfig } from "./webrtc_base";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment
const developmentEnv = process.env.NODE_ENV; // Development environment
export default class WebRTC_Initiator extends BaseWebRTC {
constructor(config: WebRTCConfig) {
@@ -16,7 +16,7 @@ export default class WebRTC_Initiator extends BaseWebRTC {
});
// Add listener for recipient's response
this.socket.on("recipient-ready", ({ peerId }) => {
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(
`[Initiator] Received recipient-ready from: ${peerId}`
);
@@ -32,7 +32,7 @@ export default class WebRTC_Initiator extends BaseWebRTC {
private async handleReady({ peerId }: { peerId: string }): Promise<void> {
// Recipient peerId
// this.log('log',`Received ready signal from peer ${peerId}`);
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(`Received ready signal from peer ${peerId}`);
await this.createPeerConnection(peerId);
await this.createDataChannel(peerId);
@@ -48,7 +48,7 @@ export default class WebRTC_Initiator extends BaseWebRTC {
from: string;
}): Promise<void> {
// this.log('log',`Handling answer from peer ${from}`);
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(`Handling answer from peer ${from}`);
const peerConnection = this.peerConnections.get(from);
if (!peerConnection) {
@@ -96,7 +96,7 @@ export default class WebRTC_Initiator extends BaseWebRTC {
// If it is the initiator, create and send an offer to the signaling server to negotiate a connection with the recipient.
private async createAndSendOffer(peerId: string): Promise<void> {
// this.log('log', `Creating and sending offer to ${peerId}`);
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(`createAndSendOffer for peerId: ${peerId}`);
const peerConnection = this.peerConnections.get(peerId);
if (!peerConnection) {
+11 -11
View File
@@ -2,7 +2,7 @@
import io, { Socket, ManagerOptions, SocketOptions } from "socket.io-client";
import { WakeLockManager } from "./wakeLockManager";
import { postLogToBackend } from "@/app/config/api";
const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment
const developmentEnv = process.env.NODE_ENV; // Development environment
export class WebRTCError extends Error {
constructor(message: string, public context?: Record<string, any>) {
@@ -131,7 +131,7 @@ export default class BaseWebRTC {
this.socket.on("disconnect", () => {
this.isInRoom = false;
this.isSocketDisconnected = true;
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(
`${this.peerId} disconnect on socket,isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`
);
@@ -153,7 +153,7 @@ export default class BaseWebRTC {
if (this.isSocketDisconnected && this.isPeerDisconnected && this.roomId) {
// Start reconnection only after both socket and P2P connections are disconnected
this.reconnectionInProgress = true;
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(
`Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}`
);
@@ -314,7 +314,7 @@ export default class BaseWebRTC {
disconnected: async () => {
await this.cleanupExistingConnection(peerId);
this.isPeerDisconnected = true;
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(`p2p disconnected, isInitiator:${this.isInitiator}`);
// Attempt to reconnect
this.attemptReconnection();
@@ -402,7 +402,7 @@ export default class BaseWebRTC {
};
dataChannel.onclose = () => {
if (developmentEnv === "true") {
if (developmentEnv === "development") {
postLogToBackend(`DataChannel closed for peer: ${peerId}`);
}
this.log("log", `Data channel with ${peerId} closed.`);
@@ -440,7 +440,7 @@ export default class BaseWebRTC {
roomId: this.roomId,
});
}
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(
`peerId:${this.socket.id} Successfully joined room: ${response.roomId},isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}`
);
@@ -448,7 +448,7 @@ export default class BaseWebRTC {
} else {
this.isInRoom = false;
this.roomId = null;
if (developmentEnv === "true")
if (developmentEnv === "development")
postLogToBackend(`Failed to join room,message:${response.message}`);
this.fireError("Failed to join room", { message: response.message });
reject(new Error(response.message));
@@ -499,10 +499,10 @@ export default class BaseWebRTC {
? data.byteLength
: 0;
if (developmentEnv === "true")
postLogToBackend(
`sendToPeer - type: ${dataType}, size: ${dataSize}, bufferedAmount: ${dataChannel.bufferedAmount}`
);
// if (developmentEnv === "development")
// postLogToBackend(
// `sendToPeer - type: ${dataType}, size: ${dataSize}, bufferedAmount: ${dataChannel.bufferedAmount}`
// );
dataChannel.send(data);
return true;