From 0efc999c350ea588d60e9c6b88749f5e390ef01d Mon Sep 17 00:00:00 2001 From: Sadeghizad Date: Fri, 29 May 2026 19:03:42 +0330 Subject: [PATCH] (init): Starting the project --- .gitignore | 40 ++++++ .idea/.gitignore | 10 ++ .idea/encodings.xml | 7 + .idea/misc.xml | 14 ++ .idea/vcs.xml | 6 + README.md | 185 +++++++++++++++++++++++++ pom.xml | 17 +++ src/main/java/ChunkStatus.java | 108 +++++++++++++++ src/main/java/ChunkUtils.java | 46 ++++++ src/main/java/ConfigReader.java | 85 ++++++++++++ src/main/java/DownloadConfig.java | 70 ++++++++++ src/main/java/DownloadWorker.java | 43 ++++++ src/main/java/Main.java | 107 ++++++++++++++ src/main/java/ProgressMonitor.java | 69 +++++++++ src/main/resources/download_config.txt | 7 + 15 files changed, 814 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/ChunkStatus.java create mode 100644 src/main/java/ChunkUtils.java create mode 100644 src/main/java/ConfigReader.java create mode 100644 src/main/java/DownloadConfig.java create mode 100644 src/main/java/DownloadWorker.java create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/ProgressMonitor.java create mode 100644 src/main/resources/download_config.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c00c91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +out/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..214403a --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# Eighth Assignment: Multithreading Basics + +## Table of contents +- [Introduction](#introduction) +- [Objectives 🎯](#objectives-) +- [Theoretical Questions πŸ“](#theoretical-questions-) +- [Practical Questions πŸ’»](#practical-questions-) +- [Evaluation βš–οΈ](#evaluation-) +- [Submission βŒ›](#submission-) +- [Additional Resources πŸ“š](#additional-resources-) + +## Important Note: +This project is configured as a **Maven project**. If you're opening this project in an IDE (like IntelliJ IDEA or VS Code), please ensure you import it as a Maven project so that dependencies and build settings are recognized automatically. + +To run the project from the command line, use: +```bash +mvn compile +mvn exec:java -Dexec.mainClass="Main" +``` + +## Introduction +Welcome to your Eighth Advanced Programming (AP) Assignment. This project is divided into two main sections: + +1. **Theoretical Questions**: Analyze key multithreading concepts (Start vs Run, Daemon threads, and Lambdas). +2. **Practical Questions**: Implement a **Simulated Download Manager**. You will use Java Threads to simulate downloading a file in multiple chunks concurrently. + +> **⚠️ Note:** This is a **local simulation only**. There is no actual network activity, URL connection, or socket programming involved. The goal is to practice thread management and state observation. + +## Objectives 🎯 + +By completing this assignment, you will: +- Apply **multithreading** basics using the `Thread` class and `Runnable` interface. +- Understand how to manage multiple worker threads performing independent tasks. +- Implement a monitor thread to observe the progress of other threads. +- Practice using `start()` and `join()` for thread lifecycle management. + +## Theoretical Questions πŸ“ +**Note: Please answer these questions in a Markdown file (Report.md) and place it in the root directory of your fork. Include code or screenshots where you see fit.** + +### 1. `start()` vs `run()` + +```java +public class StartVsRun { + static class MyRunnable implements Runnable { + public void run() { + System.out.println("Running in: " + Thread.currentThread().getName()); + } + } + public static void main(String[] args) throws InterruptedException { + Thread t1 = new Thread(new MyRunnable(), "Thread-1"); + System.out.println("Calling run()"); + t1.run(); + Thread.sleep(100); + + Thread t2 = new Thread(new MyRunnable(), "Thread-2"); + System.out.println("Calling start()"); + t2.start(); + } +} +``` + +**Questions:** + +- What output do you get from the program? Why? + +- What’s the difference in behavior between calling `start()` and `run()`? + +--- + +### 2. Daemon Threads + +```java +public class DaemonExample { + static class DaemonRunnable implements Runnable { + public void run() { + for(int i = 0; i < 20; i++) { + System.out.println("Daemon thread running..."); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + //[Handling Exception...] + } + } + } + } + public static void main(String[] args) { + Thread thread = new Thread(new DaemonRunnable()); + thread.setDaemon(true); + thread.start(); + System.out.println("Main thread ends."); + } +} +``` + +**Questions:** +- What output do you get from the program? Why? + +- What happens if you remove `thread.setDaemon(true)`? + +- What are some real-life use cases of daemon threads? + + +--- + +### 3. A shorter way to create threads + +```java +public class ThreadDemo { + public static void main(String[] args) { + Thread thread = new Thread(() -> { + System.out.println("Thread is running using a ...!"); + }); + + thread.start(); + } +} +``` + +**Questions:** +- What output do you get from the program? + +- What is the `() -> { ... }` syntax called? + +- How is this code different from creating a class that extends `Thread` or implements `Runnable`? + + +## Practical Questions πŸ’» + +### Simulated Download Manager +You are tasked with completing a skeleton project for a download manager. The application reads a configuration file, splits a "file" into several chunks, and assigns each chunk to a dedicated worker thread. + +#### πŸ— Project Structure +- `src/main/resources/download_config.txt`: Contains simulation parameters (file size, chunk count, delays). +- `DownloadWorker.java`: The logic for simulating a chunk download (needs implementation). +- `ProgressMonitor.java`: A thread that periodically prints the total progress (needs implementation). +- `Main.java`: The entry point that initializes chunks, starts threads, and waits for completion. +- `ChunkStatus.java`: Data class holding the state of individual chunks. + +#### πŸ›  What You Need to Do +In the provided source code, look for **`// TODO`** comments. You must: + +1. **Implement `DownloadWorker`**: + - Record start/end times for each chunk. + - Use a loop to simulate progress based on the random delays and step sizes provided in the config. + - Update the shared `ChunkStatus` object so the monitor can see progress. +2. **Implement `ProgressMonitor`**: + - Periodically calculate the total downloaded megabytes across all chunks. + - Exit gracefully once all chunks are marked as completed. +3. **Complete `Main`**: + - Properly instantiate and `start()` the worker threads and the monitor thread. + - Use `join()` to ensure the main thread waits for all workers to finish before printing the final report. + +#### βš™οΈ Configuration +The simulation behavior is controlled by `src/main/resources/download_config.txt`. You can modify these values to test different scenarios (e.g., more chunks or faster/slower speeds). + +--- + +## Evaluation βš–οΈ + +Your work will be evaluated based on: + +- **Thread Management**: Correct use of `start()` and `join()`. +- **Simulation Logic**: Correct implementation of the loops and random delays in the worker threads. +- **Thread Safety**: Following the constraint of each worker only writing to its own assigned object. +- **Code Quality**: Readable code and proper use of Java conventions. + +**Total: 500 points** +- 🧠 Theoretical Questions – 150 points +- πŸ’» Practical Task (Download Manager) – 350 points + +## Submission βŒ› + +1. Add your mentor as a contributor to the project. +2. Create a `develop` branch for implementing features. +3. Use Git for regular code commits. +4. Push your code and the answers file (Report.md) to the remote repository. +5. Submit a pull request to merge the `develop` branch with `main`. + +**Deadline:** **Friday, June 5** (15th of Khordad) + +## Additional Resources πŸ“š + +- [Java Concurrency and Multithreading](https://jenkov.com/tutorials/java-concurrency/index.html) +- [Creating and Starting Java Threads](https://jenkov.com/tutorials/java-concurrency/creating-and-starting-threads.html) +- [Thread.join() explained](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join--) \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c63e596 --- /dev/null +++ b/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + org + simulated-download-manager + 1.0-SNAPSHOT + Simulated Download Manager + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/src/main/java/ChunkStatus.java b/src/main/java/ChunkStatus.java new file mode 100644 index 0000000..3e902dc --- /dev/null +++ b/src/main/java/ChunkStatus.java @@ -0,0 +1,108 @@ +// Educational simplification: +// each worker writes only to its own ChunkStatus, +// and the monitor only reads chunk states. +// volatile is used here to make progress updates more visible across threads. + +/** + * Represents the state and download progress of a single file chunk. + * Each worker thread updates its own ChunkStatus, while the monitor thread reads it. + */ +public class ChunkStatus { + private final int chunkId; + private final double chunkSizeMB; + private volatile double downloadedMB; + private volatile boolean completed; + private volatile long startTimeMs; + private volatile long endTimeMs; + + /** + * Initializes a chunk with its unique ID and total allocated size. + * Progress-related fields are initialized to default values. + */ + public ChunkStatus(int chunkId, double chunkSizeMB) { + this.chunkId = chunkId; + this.chunkSizeMB = chunkSizeMB; + this.downloadedMB = 0.0; + this.completed = false; + this.startTimeMs = 0; + this.endTimeMs = 0; + } + + // Getters and Setters + public int getChunkId() { + return chunkId; + } + + public double getChunkSizeMB() { + return chunkSizeMB; + } + + public double getDownloadedMB() { + return downloadedMB; + } + + public void setDownloadedMB(double downloadedMB) { + // Guard to prevent downloaded size exceeding actual chunk size + if (downloadedMB >= this.chunkSizeMB) { + this.downloadedMB = this.chunkSizeMB; + } else { + this.downloadedMB = downloadedMB; + } + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public long getStartTimeMs() { + return startTimeMs; + } + + public void setStartTimeMs(long startTimeMs) { + this.startTimeMs = startTimeMs; + } + + public long getEndTimeMs() { + return endTimeMs; + } + + public void setEndTimeMs(long endTimeMs) { + this.endTimeMs = endTimeMs; + } + + /** + * Helper method to calculate the duration of this specific chunk's download. + * Returns 0 if the chunk hasn't started or finished yet. + */ + public long getDownloadDurationMs() { + if (startTimeMs > 0 && endTimeMs > startTimeMs) { + return endTimeMs - startTimeMs; + } else if (startTimeMs > 0 && !completed) { + return System.currentTimeMillis() - startTimeMs; + } + return 0; + } + + /** + * Helper method to calculate the download percentage of this chunk. + */ + public double getProgressPercentage() { + if (chunkSizeMB == 0) return 100.0; + return (downloadedMB / chunkSizeMB) * 100.0; + } + + @Override + public String toString() { + return String.format("Chunk #%d: %.1f/%.1f MB (%.1f%%)%s", + chunkId, + downloadedMB, + chunkSizeMB, + getProgressPercentage(), + completed ? " [Completed]" : "" + ); + } +} diff --git a/src/main/java/ChunkUtils.java b/src/main/java/ChunkUtils.java new file mode 100644 index 0000000..8a4a07c --- /dev/null +++ b/src/main/java/ChunkUtils.java @@ -0,0 +1,46 @@ +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods related to chunk creation. + */ +public class ChunkUtils { + + private ChunkUtils() { + // Utility class + } + + /** + * Splits the total file size into a list of chunks. + * Chunk sizes are as even as possible. + * If the size is not exactly divisible, the last chunk gets the remainder. + * + * @param totalSizeMB total file size in MB + * @param chunkCount number of chunks + * @return list of ChunkStatus objects + */ + public static List createChunks(int totalSizeMB, int chunkCount) { + List chunks = new ArrayList<>(); + + if (totalSizeMB <= 0 || chunkCount <= 0) { + return chunks; + } + + int baseChunkSize = totalSizeMB / chunkCount; + int remainder = totalSizeMB % chunkCount; + + for (int i = 0; i < chunkCount; i++) { + int chunkSize = baseChunkSize; + + if (i == chunkCount - 1) { + chunkSize += remainder; + } + + // Create one ChunkStatus object for each chunk. + // chunkId is the chunk number, and chunkSize is this chunk's total size. + chunks.add(new ChunkStatus(i + 1, chunkSize)); + } + + return chunks; + } +} diff --git a/src/main/java/ConfigReader.java b/src/main/java/ConfigReader.java new file mode 100644 index 0000000..5754060 --- /dev/null +++ b/src/main/java/ConfigReader.java @@ -0,0 +1,85 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class ConfigReader { + + public static DownloadConfig readConfig(String fileName) { + InputStream inputStream = ConfigReader.class.getClassLoader().getResourceAsStream(fileName); + + if (inputStream == null) { + throw new IllegalArgumentException("Config file not found in resources: " + fileName); + } + + String configFileName = null; + int totalSizeMB = 0; + int chunkCount = 0; + int minStepDelayMs = 0; + int maxStepDelayMs = 0; + double minStepDownloadMB = 0; + double maxStepDownloadMB = 0; + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + + if (line.isEmpty()) { + continue; + } + + String[] parts = line.split("=", 2); + if (parts.length != 2) { + continue; + } + + String key = parts[0].trim(); + String value = parts[1].trim(); + + switch (key) { + case "fileName": + configFileName = value; + break; + case "totalSizeMB": + totalSizeMB = Integer.parseInt(value); + break; + case "chunkCount": + chunkCount = Integer.parseInt(value); + break; + case "minStepDelayMs": + minStepDelayMs = Integer.parseInt(value); + break; + case "maxStepDelayMs": + maxStepDelayMs = Integer.parseInt(value); + break; + case "minStepDownloadMB": + minStepDownloadMB = Double.parseDouble(value); + break; + case "maxStepDownloadMB": + maxStepDownloadMB = Double.parseDouble(value); + break; + default: + // Ignore unknown keys to keep parsing simple + break; + } + } + + } catch (IOException e) { + throw new RuntimeException("Error reading config file: " + fileName, e); + } + + return new DownloadConfig( + configFileName, + totalSizeMB, + chunkCount, + minStepDelayMs, + maxStepDelayMs, + minStepDownloadMB, + maxStepDownloadMB + ); + } +} diff --git a/src/main/java/DownloadConfig.java b/src/main/java/DownloadConfig.java new file mode 100644 index 0000000..dc1ba3a --- /dev/null +++ b/src/main/java/DownloadConfig.java @@ -0,0 +1,70 @@ +/** + * Represents the configuration parameters for the simulated download manager. + * This class is immutable to ensure thread-safety when shared among worker threads. + */ +public class DownloadConfig { + private final String fileName; + private final int totalSizeMB; + private final int chunkCount; + private final int minStepDelayMs; + private final int maxStepDelayMs; + private final double minStepDownloadMB; + private final double maxStepDownloadMB; + + /** + * Constructs a new DownloadConfig with specified simulation parameters. + */ + public DownloadConfig(String fileName, int totalSizeMB, int chunkCount, + int minStepDelayMs, int maxStepDelayMs, + double minStepDownloadMB, double maxStepDownloadMB) { + this.fileName = fileName; + this.totalSizeMB = totalSizeMB; + this.chunkCount = chunkCount; + this.minStepDelayMs = minStepDelayMs; + this.maxStepDelayMs = maxStepDelayMs; + this.minStepDownloadMB = minStepDownloadMB; + this.maxStepDownloadMB = maxStepDownloadMB; + } + + // Getters + public String getFileName() { + return fileName; + } + + public int getTotalSizeMB() { + return totalSizeMB; + } + + public int getChunkCount() { + return chunkCount; + } + + public int getMinStepDelayMs() { + return minStepDelayMs; + } + + public int getMaxStepDelayMs() { + return maxStepDelayMs; + } + + public double getMinStepDownloadMB() { + return minStepDownloadMB; + } + + public double getMaxStepDownloadMB() { + return maxStepDownloadMB; + } + + @Override + public String toString() { + return "DownloadConfig{" + + "fileName='" + fileName + '\'' + + ", totalSizeMB=" + totalSizeMB + + ", chunkCount=" + chunkCount + + ", minStepDelayMs=" + minStepDelayMs + + ", maxStepDelayMs=" + maxStepDelayMs + + ", minStepDownloadMB=" + minStepDownloadMB + + ", maxStepDownloadMB=" + maxStepDownloadMB + + '}'; + } +} diff --git a/src/main/java/DownloadWorker.java b/src/main/java/DownloadWorker.java new file mode 100644 index 0000000..546de2d --- /dev/null +++ b/src/main/java/DownloadWorker.java @@ -0,0 +1,43 @@ +import java.util.Random; + +/** + * Simulates downloading a single chunk of a file. + * + *

This class is intentionally provided as a skeleton for students. + * The main multithreading and simulation logic should be completed + * in the run() method.

+ */ +public class DownloadWorker implements Runnable { + + private final ChunkStatus chunkStatus; + private final DownloadConfig config; + private final Random random; + + public DownloadWorker(ChunkStatus chunkStatus, DownloadConfig config) { + this.chunkStatus = chunkStatus; + this.config = config; + this.random = new Random(); + } + + @Override + public void run() { + // TODO: Record the chunk start time in chunkStatus. + double downloaded = 0.0; + + // TODO: Print a message that this chunk has started downloading. + + 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. + } + + // TODO: Mark the chunk as completed. + // TODO: Record the chunk end time in chunkStatus. + // TODO: Print a message that this chunk has finished downloading. + } + +} diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000..82bd239 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,107 @@ +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + System.out.println("=== Simulated Download Manager ==="); + + // 1. Read config + DownloadConfig config; + try { + config = ConfigReader.readConfig("download_config.txt"); + } catch (Exception e) { + System.out.println("Failed to read configuration: " + e.getMessage()); + return; + } + + System.out.println("File name: " + config.getFileName()); + System.out.println("Total size (MB): " + config.getTotalSizeMB()); + System.out.println("Chunk count: " + config.getChunkCount()); + System.out.println(); + + // 2. Create chunks + List chunks = ChunkUtils.createChunks( + config.getTotalSizeMB(), + config.getChunkCount() + ); + + // 3. Create worker threads + List workerThreads = new ArrayList<>(); + + for (ChunkStatus chunk : chunks) { + DownloadWorker worker = new DownloadWorker(chunk, config); + 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. + } + + // 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(); + + // 5. Start worker threads + // TODO: + // Start each worker thread in workerThreads. + // Use a loop and call start() on each thread. + + // 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(); + // } + + // 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. + + // NOTE: + // this final report may show 0 progress because no worker has actually run yet. + // Until students complete the thread start/join TODOs above, + + // 7. Print final report + System.out.println(); + System.out.println("=== Final Report ==="); + + int completedChunks = 0; + double downloadedMB = 0.0; + + for (ChunkStatus chunk : chunks) { + downloadedMB += chunk.getDownloadedMB(); + + if (chunk.isCompleted()) { + completedChunks++; + } + + System.out.println( + "Chunk " + chunk.getChunkId() + + ": " + chunk.getDownloadedMB() + + "/" + chunk.getChunkSizeMB() + + " MB" + ); + } + + System.out.println(); + System.out.println("Completed chunks: " + completedChunks + "/" + chunks.size()); + System.out.println("Downloaded total: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB"); + System.out.println("Simulation finished."); + } +} diff --git a/src/main/java/ProgressMonitor.java b/src/main/java/ProgressMonitor.java new file mode 100644 index 0000000..475c0e0 --- /dev/null +++ b/src/main/java/ProgressMonitor.java @@ -0,0 +1,69 @@ +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() { + // TODO: + // Repeatedly check chunk progress until all chunks are completed. + // In each loop: + // 1. Read the downloaded size from every chunk + // 2. Add all downloaded amounts to totalDownloadedMB + // 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 + + + 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; + } + + System.out.printf( + "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", + fileName, + totalDownloadedMB, + (double) totalSizeMB, + percent, + completedChunks, + chunks.size() + ); + + + // TODO: + // If all chunks are completed, print a final message and exit the loop + + try { + Thread.sleep(monitorDelayMs); + } catch (InterruptedException e) { + System.out.println("Progress monitor interrupted."); + return; + } + } + } +} diff --git a/src/main/resources/download_config.txt b/src/main/resources/download_config.txt new file mode 100644 index 0000000..6ddd6ca --- /dev/null +++ b/src/main/resources/download_config.txt @@ -0,0 +1,7 @@ +fileName=movie.mkv +totalSizeMB=120 +chunkCount=6 +minStepDelayMs=80 +maxStepDelayMs=200 +minStepDownloadMB=2 +maxStepDownloadMB=6 \ No newline at end of file