import java.util.List; public class ProgressMonitor implements Runnable { private final String fileName; private final int totalSizeMB; private final List chunks; private final long monitorDelayMs; private final long startTimeMs; public ProgressMonitor(DownloadConfig config, List chunks) { this.fileName = config.getFileName(); this.totalSizeMB = config.getTotalSizeMB(); this.chunks = chunks; this.monitorDelayMs = 500; this.startTimeMs = System.currentTimeMillis(); } @Override public void run() { System.out.println("=== Download Progress ==="); while (true) { double totalDownloadedMB = 0.0; int completedChunks = 0; for (ChunkStatus chunk : chunks) { totalDownloadedMB += chunk.getDownloadedMB(); if (chunk.isCompleted()) { completedChunks++; } } double percent = 0.0; if (totalSizeMB > 0) { percent = (totalDownloadedMB * 100.0) / totalSizeMB; } long elapsedMs = System.currentTimeMillis() - startTimeMs; double elapsedSec = elapsedMs / 1000.0; double speedMBps = (elapsedSec > 0) ? (totalDownloadedMB / elapsedSec) : 0.0; double remainingMB = totalSizeMB - totalDownloadedMB; double etaSec = (speedMBps > 0) ? (remainingMB / speedMBps) : 0.0; int barLength = 30; int filled = (int) (percent / 100.0 * barLength); StringBuilder bar = new StringBuilder("["); for (int i = 0; i < barLength; i++) { bar.append(i < filled ? "=" : " "); } bar.append("]"); System.out.printf("\r%s %s %.1f/%.1f MB (%.1f%%) | Speed: %.1f MB/s | ETA: %.0fs | Chunks: %d/%d", bar.toString(), fileName, totalDownloadedMB, (double) totalSizeMB, percent, speedMBps, etaSec, completedChunks, chunks.size() ); if (completedChunks == chunks.size()) { System.out.println("Monitor: All chunks completed."); break; } try { Thread.sleep(monitorDelayMs); } catch (InterruptedException e) { System.out.println("Progress monitor interrupted."); return; } } } }