Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Dank-del committed Aug 20, 2024
0 parents commit b011ca4
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 0 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Miru Release

on:
release:
types: [created]

jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: ubuntu-latest
output_name: eru_linux
- os: macos-latest
output_name: eru_macos
- os: windows-latest
output_name: eru_windows.exe

steps:
- uses: actions/checkout@v2

- name: Install dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev
- name: Install dependencies (macOS)
if: matrix.os == 'macos-latest'
run: |
brew install curl
- name: Install dependencies (Windows)
if: matrix.os == 'windows-latest'
run: |
vcpkg install curl:x64-windows
vcpkg integrate install
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Release

- name: Build
run: cmake --build ${{github.workspace}}/build --config Release

- name: Rename binary
run: |
if [ "${{ matrix.os }}" == "windows-latest" ]; then
mv ${{github.workspace}}/build/src/Release/eru.exe ${{github.workspace}}/${{ matrix.output_name }}
else
mv ${{github.workspace}}/build/src/eru ${{github.workspace}}/${{ matrix.output_name }}
fi
shell: bash

- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{github.workspace}}/${{ matrix.output_name }}
asset_name: ${{ matrix.output_name }}
asset_content_type: application/octet-stream
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Prerequisites
*.d

# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod
*.smod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app


/build/*
30 changes: 30 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.14)
project(file_downloader VERSION 1.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

FetchContent_Declare(
cpr
GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 1.10.x
)
FetchContent_MakeAvailable(cpr)

FetchContent_Declare(
cli11
GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
GIT_TAG v2.3.2
)
FetchContent_MakeAvailable(cli11)

FetchContent_Declare(
indicators
GIT_REPOSITORY https://github.com/p-ranav/indicators.git
GIT_TAG v2.3
)
FetchContent_MakeAvailable(indicators)

add_subdirectory(src)
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Eru: Multi-threaded File Downloader

Eru is a simple and efficient multi-threaded file downloader designed to accelerate your download speeds. Written in C++, Eru leverages multiple threads to download file chunks concurrently, potentially increasing download speeds significantly. I made this as I had a simple requirement and did not want to buy IDM or pirate it.

## Features

- Multi-threaded downloading
- Progress bar with real-time updates
- Automatic filename detection from URL
- Customizable number of download threads
- Supports both HTTP and HTTPS
- Displays download speed and estimated time remaining
- Automatic merging of downloaded chunks

## Requirements

- C++17 compatible compiler
- CMake 3.14 or higher
- libcurl
- CLI11
- indicators

## Building from Source

1. Clone the repository:
```
git clone https://github.com/Dank-del/eru.git
cd eru
```

2. Create a build directory and navigate to it:
```
mkdir build && cd build
```

3. Configure the project with CMake:
```
cmake ..
```

4. Build the project:
```
cmake --build .
```

## Usage

After building, you can run Eru using the following command:

```
./src/eru --url <download_url> [options]
```

### Options

- `-u, --url <url>`: Specify the URL of the file to download (required)
- `-o, --output <filename>`: Set the output filename (optional, auto-detected if not specified)
- `-t, --threads <number>`: Set the number of download threads (default: 4)
- `-a, --about`: Display information about Eru
- `-h, --help`: Show help message

### Examples

1. Download a file using default settings:
```
./src/eru --url https://example.com/large_file.zip
```

2. Download a file with a custom output name and 8 threads:
```
./src/eru --url https://example.com/large_file.zip --output my_file.zip --threads 8
```

3. Display information about Eru:
```
./src/eru --about
```

## Contributing

Contributions to Eru are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Author

Sayan Biswas
- Email: [email protected]

## Acknowledgments

- [CPR](https://github.com/libcpr/cpr) for HTTP requests
- [CLI11](https://github.com/CLIUtils/CLI11) for command-line parsing
- [indicators](https://github.com/p-ranav/indicators) for progress bars
9 changes: 9 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_library(downloader STATIC
downloader.cpp
downloader.h
)

target_link_libraries(downloader PUBLIC cpr::cpr indicators::indicators)

add_executable(eru main.cpp)
target_link_libraries(eru PRIVATE downloader CLI11::CLI11 indicators::indicators)
134 changes: 134 additions & 0 deletions src/downloader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include "downloader.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <cmath>
#include <chrono>
#include <iomanip>
#include <cpr/cpr.h>
#include <indicators/progress_bar.hpp>

std::mutex console_mutex;
std::atomic<size_t> total_progress(0);

std::string get_filename_from_url(const std::string &url)
{
size_t pos = url.find_last_of("/");
if (pos != std::string::npos)
{
return url.substr(pos + 1);
}
return "downloaded_file";
}

std::string format_size(size_t size)
{
double size_mb = static_cast<double>(size) / (1024 * 1024);
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << size_mb;
return ss.str();
}

void Downloader::download_file(const std::string &url, const std::optional<std::string> &output_file, int num_threads)
{
cpr::Response r = cpr::Head(cpr::Url{url});
if (r.status_code != 200)
{
std::cerr << "Failed to get file information. Status code: " << r.status_code << std::endl;
return;
}

size_t file_size = std::stoull(r.header["Content-Length"]);
size_t chunk_size = std::ceil(static_cast<double>(file_size) / num_threads);

std::string filename = output_file.value_or(get_filename_from_url(url));
std::cout << "Downloading to: " << filename << std::endl;
std::cout << "File size: " << format_size(file_size) << " MB" << std::endl;

indicators::ProgressBar progress_bar{
indicators::option::BarWidth{50},
indicators::option::Start{"["},
indicators::option::Fill{"="},
indicators::option::Lead{">"},
indicators::option::Remainder{" "},
indicators::option::End{"]"},
indicators::option::ForegroundColor{indicators::Color::green},
indicators::option::ShowElapsedTime{true},
indicators::option::ShowRemainingTime{true},
indicators::option::FontStyles{std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}};

std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i)
{
size_t start = i * chunk_size;
size_t end = (i == num_threads - 1) ? file_size - 1 : (i + 1) * chunk_size - 1;
threads.emplace_back(&Downloader::download_chunk, url, start, end, filename, i);
}

while (total_progress < file_size)
{
size_t current_progress = total_progress.load();
progress_bar.set_progress(100.0 * current_progress / file_size);
std::cout << "\rDownloaded: " << format_size(current_progress) << " MB / " << format_size(file_size) << " MB" << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

for (auto &t : threads)
{
t.join();
}

progress_bar.set_progress(100);
std::cout << "\nDownload complete. Merging chunks..." << std::endl;

merge_chunks(filename, num_threads);

std::cout << "File saved as: " << filename << std::endl;
}

void Downloader::download_chunk(const std::string &url, size_t start, size_t end, const std::string &output_file, int chunk_id)
{
std::string chunk_file = output_file + ".part" + std::to_string(chunk_id);
std::ofstream out(chunk_file, std::ios::binary);

cpr::Response r = cpr::Get(cpr::Url{url},
cpr::Header{{"Range", "bytes=" + std::to_string(start) + "-" + std::to_string(end)}},
cpr::WriteCallback([&](const std::string &data, intptr_t)
{
out.write(data.c_str(), data.length());
total_progress += data.length();
return true; }));

out.close();

if (r.status_code != 206)
{
std::lock_guard<std::mutex> lock(console_mutex);
std::cerr << "Failed to download chunk " << chunk_id << ". Status code: " << r.status_code << std::endl;
}
}

void Downloader::merge_chunks(const std::string &output_file, int num_chunks)
{
std::ofstream out(output_file, std::ios::binary);

for (int i = 0; i < num_chunks; ++i)
{
std::string chunk_file = output_file + ".part" + std::to_string(i);
std::ifstream in(chunk_file, std::ios::binary);
out << in.rdbuf();
in.close();
std::remove(chunk_file.c_str());
}

out.close();
}

bool Downloader::test_connection(const std::string &url)
{
cpr::Response r = cpr::Head(cpr::Url{url});
return r.status_code == 200;
}
15 changes: 15 additions & 0 deletions src/downloader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <string>
#include <optional>

class Downloader
{
public:
static void download_file(const std::string &url, const std::optional<std::string> &output_file, int num_threads);
static bool test_connection(const std::string &url);

private:
static void download_chunk(const std::string &url, size_t start, size_t end, const std::string &output_file, int chunk_id);
static void merge_chunks(const std::string &output_file, int num_chunks);
};
Loading

0 comments on commit b011ca4

Please sign in to comment.