From 8d71e2814eeb206038d3921af448c3c368248aa3 Mon Sep 17 00:00:00 2001 From: ZahraSadatMirvakili Date: Wed, 3 Jun 2026 00:03:54 +0330 Subject: [PATCH] basic multithreading --- src/main/java/DownloadWorker.java | 43 +++++++++++++----- src/main/java/Main.java | 39 +++++++--------- src/main/java/ProgressMonitor.java | 73 ++++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/main/java/DownloadWorker.java b/src/main/java/DownloadWorker.java index 546de2d..ec573cc 100644 --- a/src/main/java/DownloadWorker.java +++ b/src/main/java/DownloadWorker.java @@ -21,23 +21,42 @@ public class DownloadWorker implements Runnable { @Override public void run() { - // TODO: Record the chunk start time in chunkStatus. - double downloaded = 0.0; + long startTime = System.currentTimeMillis(); + chunkStatus.setStartTimeMs(startTime); - // TODO: Print a message that this chunk has started downloading. + double downloaded = 0.0; + int chunkId = chunkStatus.getChunkId(); + double chunkSize = chunkStatus.getChunkSizeMB(); + + System.out.println("[ Worker - " + chunkId +" ] Started downloading chunk " + chunkId +" (" + chunkSize + ") "); while (downloaded < chunkStatus.getChunkSizeMB()) { - // TODO: Generate a random sleep delay between min and max delay. - // TODO: Sleep for that delay. - // TODO: Generate a random download amount for this step. - // TODO: Increase downloaded, but do not go beyond chunk size. - // TODO: Save the updated downloaded value into chunkStatus. - // TODO: Optionally print step-by-step progress. + int delayMs = config.getMinStepDelayMs() + random.nextInt(config.getMaxStepDelayMs()-config.getMinStepDelayMs()+1); + + try { + Thread.sleep(delayMs); + }catch (InterruptedException e){ + System.out.println("[ Worker - " + chunkId +" ] interrupted "); + return; + } + + double stepSize = config.getMinStepDownloadMB() + random.nextDouble() * (config.getMaxStepDownloadMB() - config.getMinStepDownloadMB()); + + downloaded = Math.min(chunkSize, downloaded + stepSize); + + chunkStatus.setDownloadedMB(downloaded); + + System.out.println("[ Worker - " + chunkId +" ] Chunk " + chunkId + ": " + Math.round(downloaded*10)/10.0 + "/" + chunkSize + "(" + Math.round((downloaded/chunkSize)*1000)/10.0 + ")"); } - // TODO: Mark the chunk as completed. - // TODO: Record the chunk end time in chunkStatus. - // TODO: Print a message that this chunk has finished downloading. + chunkStatus.setCompleted(true); + + long endTime = System.currentTimeMillis(); + chunkStatus.setEndTimeMs(endTime); + + long duration = endTime - startTime; + + System.out.println("[ Worker - " + chunkId +" ]Finished downloading chunk " + chunkId + "in" + (duration/1000.0) + "seconds"); } } diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 82bd239..402b2a0 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -33,46 +33,39 @@ public class Main { Thread workerThread = new Thread(worker, "Worker-" + chunk.getChunkId()); workerThreads.add(workerThread); - - // TODO: - // Students may print helpful debug information here, - // for example which chunk is assigned to which worker thread. + System.out.println("Assigned chunk " + chunk.getChunkId() + "to thread: " + workerThread.getName()); } // 4. Create and start monitor thread ProgressMonitor monitor = new ProgressMonitor(config, chunks); Thread monitorThread = new Thread(monitor, "Progress-Monitor"); - // TODO: - // Start the monitor thread before starting the workers - // so that progress can be displayed while downloading happens. - // - // Example idea: - // monitorThread.start(); + monitorThread.start(); // 5. Start worker threads - // TODO: - // Start each worker thread in workerThreads. - // Use a loop and call start() on each thread. + for (Thread thread : workerThreads){ + thread.start(); + } // 6. Wait for workers to finish - // TODO: - // Wait for all worker threads to complete by calling join(). - // This should be done inside a try-catch block for InterruptedException. - // - // Hint: - // for (Thread thread : workerThreads) { - // thread.join(); - // } + for (Thread thread : workerThreads) { + try{ + thread.join(); + }catch (InterruptedException e){ + System.out.println("Main thread interrupted while waiting for" + thread.getName());} + } - // TODO: // After all workers finish, the monitor thread may also need to stop. // Depending on how ProgressMonitor is implemented, students may: // - wait for it to finish on its own, or // - add a stopping mechanism in ProgressMonitor later. // // If your monitor finishes automatically, you may join it here. - + try{ + monitorThread.join(); + }catch (InterruptedException e){ + System.out.println("Main thread interrupted while waiting for monitor"); + } // NOTE: // this final report may show 0 progress because no worker has actually run yet. // Until students complete the thread start/join TODOs above, diff --git a/src/main/java/ProgressMonitor.java b/src/main/java/ProgressMonitor.java index 475c0e0..0a5f217 100644 --- a/src/main/java/ProgressMonitor.java +++ b/src/main/java/ProgressMonitor.java @@ -6,17 +6,21 @@ public class ProgressMonitor implements Runnable { private final int totalSizeMB; private final List chunks; private final long monitorDelayMs; + private double previousTotalDownloadedMB; + private long previousTimeMs; + public ProgressMonitor(DownloadConfig config, List chunks) { this.fileName = config.getFileName(); this.totalSizeMB = config.getTotalSizeMB(); this.chunks = chunks; this.monitorDelayMs = 500; + this.previousTotalDownloadedMB = 0.0; + this.previousTimeMs = System.currentTimeMillis(); } @Override public void run() { - // TODO: // Repeatedly check chunk progress until all chunks are completed. // In each loop: // 1. Read the downloaded size from every chunk @@ -24,9 +28,7 @@ public class ProgressMonitor implements Runnable { // 3. Count completed chunks // 4. Print a progress message // 5. If completedChunks == chunks.size(), print a final monitor message and stop - // 6. Otherwise sleep for monitorDelayMs and continue - - + // 6. Otherwise, sleep for monitorDelayMs and continue while (true) { double totalDownloadedMB = 0.0; int completedChunks = 0; @@ -44,19 +46,56 @@ public class ProgressMonitor implements Runnable { percent = (totalDownloadedMB * 100.0) / totalSizeMB; } + long currentTimeMs = System.currentTimeMillis(); + long timeDiffMs = currentTimeMs - previousTimeMs; + double downloadedDiff = totalDownloadedMB - previousTotalDownloadedMB; + + double speedMBps = 0.0; + if (timeDiffMs > 0){ + speedMBps = (downloadedDiff * 1000.0) / timeDiffMs; + } + + double etaSeconds = 0.0; + if (speedMBps > 0 && totalDownloadedMB < totalSizeMB){ + double remainingMB =totalSizeMB - totalDownloadedMB; + etaSeconds = remainingMB / speedMBps; + } + + previousTotalDownloadedMB = totalDownloadedMB; + previousTimeMs = currentTimeMs; + + + int barWidth = 50; + int filledWidth = (int)(barWidth * percent / 100.0); + + System.out.print("["); + for (int i = 0; i < barWidth; i++) { + if (i < filledWidth){ + System.out.print("="); + }else if (i == filledWidth && percent< 100){ + System.out.print(">"); + }else { + System.out.print(" "); + } + } + System.out.print("]"); + System.out.printf( - "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", + "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n , ETA: %s", fileName, totalDownloadedMB, (double) totalSizeMB, percent, completedChunks, - chunks.size() + chunks.size(), + speedMBps, + formatETA(etaSeconds) ); - - // TODO: - // If all chunks are completed, print a final message and exit the loop + if (completedChunks == chunks.size()){ + System.out.println("All chunks have been downloaded successfully!"); + break; + } try { Thread.sleep(monitorDelayMs); @@ -66,4 +105,20 @@ public class ProgressMonitor implements Runnable { } } } + private String formatETA(double etaSeconds){ + if (etaSeconds <= 0){ + return "Calculating ..."; + } + int hours = (int)(etaSeconds/3600); + int minutes = (int)((etaSeconds%3600)/60); + int secondes = (int)(etaSeconds %60); + + if (hours > 0){ + return String.format("%dh %dm %ds" , hours, minutes, secondes); + }else if(minutes > 0){ + return String.format("%dm %ds" , minutes, secondes); + }else{ + return String.format("%ds", secondes); + } + } }