import { SpeedCalculator } from "@/lib/speedCalculator"; import { StateManager } from "./StateManager"; import { postLogToBackend } from "@/app/config/api"; const developmentEnv = process.env.NODE_ENV; /** * ๐Ÿš€ Progress callback type definition */ export type ProgressCallback = ( fileId: string, progress: number, speed: number ) => void; /** * ๐Ÿš€ Progress tracker * Responsible for file and folder progress calculation, speed statistics, and callback triggering */ export class ProgressTracker { private speedCalculator = new SpeedCalculator(); constructor(private stateManager: StateManager) {} /** * ๐ŸŽฏ Update file transfer progress */ async updateFileProgress( byteLength: number, fileId: string, fileSize: number, peerId: string, wasActuallySent: boolean = true ): Promise { 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); // 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); 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, fileProgress: Record, peerId: string ): Promise { const folderMeta = this.stateManager.getFolderMeta(folderName); if (!folderMeta) { postLogToBackend(`[DEBUG] โš ๏ธ Folder metadata not found: ${folderName}`); return; } // Calculate total folder progress let totalSentBytes = 0; folderMeta.fileIds.forEach((fileId) => { totalSentBytes += this.stateManager.getFileBytesSent(peerId, fileId); }); const progress = folderMeta.totalSize > 0 ? totalSentBytes / folderMeta.totalSize : 0; const speed = this.speedCalculator.getSendSpeed(peerId); // Trigger folder progress callback this.triggerProgressCallback(peerId, folderName, progress, speed); postLogToBackend( `[DEBUG] ๐Ÿ“ Folder progress - ${folderName}: ${(progress * 100).toFixed( 2 )}%, speed: ${speed.toFixed(2)} KB/s, bytes: ${totalSentBytes}/${ folderMeta.totalSize }` ); } /** * ๐ŸŽฏ Set progress callback function */ setProgressCallback(callback: ProgressCallback, peerId: string): void { this.stateManager.updatePeerState(peerId, { progressCallback: callback }); } /** * ๐ŸŽฏ Trigger progress callback */ private triggerProgressCallback( peerId: string, fileId: string, progress: number, speed: number ): void { const peerState = this.stateManager.getPeerState(peerId); if (peerState.progressCallback) { try { peerState.progressCallback(fileId, progress, speed); } catch (error) { postLogToBackend( `[DEBUG] โŒ Progress callback error - fileId: ${fileId}, error: ${error}` ); } } } /** * ๐ŸŽฏ Calculate current transfer speed */ getCurrentSpeed(peerId: string): number { return this.speedCalculator.getSendSpeed(peerId); } /** * ๐ŸŽฏ Complete file transfer progress (set to 100%) */ completeFileProgress(fileId: string, peerId: string): void { this.triggerProgressCallback(peerId, fileId, 1.0, 0); postLogToBackend(`[DEBUG] โœ… File progress completed: ${fileId}`); } /** * ๐ŸŽฏ Complete folder transfer progress (set to 100%) */ completeFolderProgress(folderName: string, peerId: string): void { this.triggerProgressCallback(peerId, folderName, 1.0, 0); postLogToBackend(`[DEBUG] โœ… Folder progress completed: ${folderName}`); } /** * ๐Ÿ“Š 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; }); return { peerId, currentSpeed, totalBytesSent, activeTransfers: Object.keys(peerState.totalBytesSent).length, currentFolderName: peerState.currentFolderName, isSending: peerState.isSending, hasProgressCallback: !!peerState.progressCallback, }; } /** * ๐Ÿ“Š Get detailed folder progress information */ getFolderProgressDetails(folderName: string, peerId: string) { const folderMeta = this.stateManager.getFolderMeta(folderName); if (!folderMeta) return null; const fileProgresses: Record< string, { sent: number; total: number; progress: number } > = {}; let totalSent = 0; folderMeta.fileIds.forEach((fileId) => { const sent = this.stateManager.getFileBytesSent(peerId, fileId); // 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] = { sent, total, progress: total > 0 ? sent / total : 0, }; }); return { folderName, totalSize: folderMeta.totalSize, totalSent, overallProgress: folderMeta.totalSize > 0 ? totalSent / folderMeta.totalSize : 0, fileCount: folderMeta.fileIds.length, fileProgresses, }; } /** * ๐Ÿงน Clean up progress tracking resources */ cleanup(): void { // SpeedCalculator internally automatically cleans up expired data if (developmentEnv === "development") postLogToBackend("[DEBUG] ๐Ÿงน ProgressTracker cleaned up"); } }