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; public ProgressMonitor(DownloadConfig config, List chunks) { this.fileName = config.getFileName(); this.totalSizeMB = config.getTotalSizeMB(); this.chunks = chunks; this.monitorDelayMs = 500; } @Override public void run() { double previousProgress = 0.0; 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; } double deltaMB = totalDownloadedMB - previousProgress; double speedMBps = deltaMB / (monitorDelayMs / 1000.0); previousProgress = totalDownloadedMB; double remainingMB = totalSizeMB - totalDownloadedMB; long etaSeconds = (speedMBps > 0) ? (long)(remainingMB / speedMBps) : 0; int barWidth = 30; int filled = (int)(percent / 100.0 * barWidth); filled = Math.min(filled, barWidth); String bar = "=".repeat(filled) + (filled < barWidth ? ">" : "") + " ".repeat(Math.max(0, barWidth - filled - 1)); System.out.printf("\r[%-30s] %5.1f%% | %5.1f/%d MB | Speed: %.2f MB/s | ETA: %ds | Chunks: %d/%d ", bar, percent, totalDownloadedMB, totalSizeMB, speedMBps, etaSeconds, completedChunks, chunks.size()); // System.out.printf( // "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", // fileName, // totalDownloadedMB, // (double) totalSizeMB, // percent, // completedChunks, // chunks.size() // ); if (completedChunks == chunks.size()) { System.out.println("download completed!"); break; } else { try { Thread.sleep(monitorDelayMs); } catch (InterruptedException e) { System.out.println("Progress monitor interrupted."); return; } } } } }