Skip to content

Commit

Permalink
V0.0.3 (#28)
Browse files Browse the repository at this point in the history
* fixed logging, updated README

* daemon cli flag showing stats

* published library on crates.io

* fix UI constraints

* added rustfmt config

* updated cargo.toml and removed unused deps

* bump versions
  • Loading branch information
gabrieldemian committed Nov 11, 2023
1 parent cda2054 commit cacf9a7
Show file tree
Hide file tree
Showing 34 changed files with 1,589 additions and 2,197 deletions.
703 changes: 105 additions & 598 deletions Cargo.lock

Large diffs are not rendered by default.

121 changes: 80 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,78 @@
# Vincenzo
Vincenzo is a BitTorrent client with vim-like keybindings and a terminal based UI. Torrents can be downloaded using the CLI or UI.
Vincenzo is a BitTorrent client with vim-like keybindings and a terminal based UI.

[![Latest Version](https://img.shields.io/crates/v/vincenzo.svg)](https://crates.io/crates/vincenzo) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg)

![image](tape.gif)

## Introduction
Vincenzo aims to be a fast, lightweight, and multi-platform client.

Another goal is for users to be able to use the [library](crates/vincenzo) to create any other kind of software, that is powered by the BitTorrent protocol.

The official UI binary is very niched, targeting a very specific type of user: someone who loves using the terminal, and vim keybindings. Altough users could create other UIs using the library.

Vincenzo offers 3 binaries and 1 library:

- [vcz](crates/vcz) - Main binary with both UI and daemon.
- [vcz_ui](crates/vcz_ui) - UI binary.
- [vczd](crates/vcz_daemon) - Daemon binary.
- [vincenzo](crates/vincenzo) - Library

## Features
- Terminal based UI <br />
- Vim-like keybindings <br />
- Multi-platform <br />
- Detached Daemon for the UI <br />
- Magnet links support <br />
- UDP connections with trackers, TCP connections with peers <br />
- Multithreaded. One OS thread specific for I/O <br />

## Structure
Vincenzo offers 3 binaries: one for the daemon, one for the UI, and another one
for both. Most people will use the latter.

`crates/vcz` - Main binary with both UI and Daemon. <br />
`crates/vincenzo` - Library. <br />
`crates/vcz_ui` - UI binary. <br />
`crates/vcz_daemon` - Daemon binary. <br />
- Multi-platform.
- Multithreaded. One OS thread specific for I/O.
- Async I/O with tokio.
- Communication with daemon using CLI flags, TCP messages or remotely via UI binary.
- Detached daemon from the UI.
- Support for magnet links.

## How to use
An example on how to download a torrent using the CLI. Please use the "--help" flag to read the descriptions of the CLI flags.
Downloading a torrent using the main binary (the flags are optional and could be omitted in favour of the configuration file).

```bash
vcz -d "/tmp/btr" -m "<insert magnet link here>" -q
vcz -d "/tmp/download_dir" -m "<magnet link here>" -q
```

## Configuration
The binaries read a toml config file.
It is located at the default config folder of your OS.
- Linux: ~/.config/vincenzo/config.toml
- Windows: C:\Users\Alice\AppData\Roaming\Vincenzo\config.toml
- MacOS: /Users/Alice/Library/Application Support/Vincenzo/config.toml

### Default config file:
```toml
download_dir = "/home/alice/Downloads"
# default
daemon_addr = "127.0.0.1:3030"
```

## Configuration File
During the first startup, a default configuration file is created.
The configuration file is located at the default config folder of your OS. At the moment, the only configuration option is: `download_dir`
Linux: ~/.config/vincenzo/config.toml
Windows: C:\Users\Alice\AppData\Roaming\Vincenzo\config.toml
macOS: /Users/Alice/Library/Application Support/Vincenzo/config.toml
## Daemon and UI binaries
Users can control the Daemon by using CLI flags that work as messages.

Let's say on one terminal you initiate the daemon: `vczd`. Or spawn as a background process so you can do everything on one terminal: `vczd &`.

And you open a second terminal to send messages to the daemon, add a torrent: `vczd -m "magnet:..."` and then print the stats to stdout `vczd --stats`.

You can also run the UI binary (maybe remotely from another machine) to control the Daemon: `vcz_ui --daemon-addr 127.0.0.1:3030`.

<details>
<summary>CLI flags of Daemon</summary>

```
Usage: vczd [OPTIONS]
Options:
--daemon-addr <DAEMON_ADDR> The Daemon will accept TCP connections on this address
-d, --download-dir <DOWNLOAD_DIR> The directory in which torrents will be downloaded
-m, --magnet <MAGNET> Download a torrent using it's magnet link, wrapped in quotes
-q, --quit-after-complete If the program should quit after all torrents are fully downloaded
-s, --stats Print all torrent status on stdout
-h, --help Print help
-V, --version Print version
```
</details>

## Supported BEPs
- [BEP 0003](http://www.bittorrent.org/beps/bep_0003.html) - The BitTorrent Protocol Specification
Expand All @@ -43,19 +82,19 @@ macOS: /Users/Alice/Library/Application Support/Vincenzo/config.toml
- [BEP 0023](http://www.bittorrent.org/beps/bep_0023.html) - Tracker Returns Compact Peer Lists

## Roadmap
[x] - Initial version of UI. <br />
[x] - Download pipelining. <br />
[x] - Endgame mode. <br />
[x] - Pause and resume torrents. <br />
[x] - Separate main binary into 3 binaries (ui, daemon, and both). <br />
[ ] - Use a buffered I/O strategy to reduce the number of writes on disk. <br />
[ ] - Choking algorithm. <br />
[ ] - Anti-snubbing. <br />
[ ] - Resume torrent download from a file. <br />
[ ] - Change piece selection strategy. <br />
[ ] - Select files to download. <br />
[ ] - Support streaming of videos/music on MPV. <br />
[ ] - ... <br />

## Tests
This program is well-tested and I'm always improving the tests.
- [x] Initial version of UI. <br />
- [x] Download pipelining. <br />
- [x] Endgame mode. <br />
- [x] Pause and resume torrents. <br />
- [x] Separate main binary into 3 binaries and 1 library. <br />
- [x] Cache bytes to reduce the number of writes on disk. <br />
- [x] Change piece selection strategy. <br />
- [ ] Choking algorithm. <br />
- [ ] Anti-snubbing. <br />
- [ ] Resume torrent download from a file. <br />
- [ ] Select files to download. <br />
- [ ] Support streaming of videos/music on MPV. <br />

## Donations
I'm working on this alone, if you enjoy my work, please consider a donation [here](https://www.glombardo.dev/sponsor).

8 changes: 4 additions & 4 deletions crates/vcz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vcz"
version = "0.0.1"
version = "0.0.3"
edition = "2021"

[[bin]]
Expand All @@ -13,9 +13,7 @@ path = "src/main.rs"
bendy = { version = "0.3.3", features = ["std"] }
bytes = "1.4.0"
clap = {version = "4.3.4", features = ["derive"]}
console-subscriber = "0.1.9"
crossterm = { version = "0.27.0", features = ["event-stream"] }
decurse = "0.0.4"
directories = "5.0.1"
futures = "0.3.28"
hashbrown = "0.14.0"
Expand All @@ -31,7 +29,9 @@ tokio = { version = "1.32.0", features = ["rt", "fs", "tracing", "time", "macros
tokio-util = { version = "0.7.8", features = ["codec"] }
toml = "0.8.0"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
tracing-subscriber = { version = "0.3.17", features = ["time"] }
urlencoding = "2.1.3"
vincenzo = { path = "../vincenzo" }
vcz_ui = { path = "../vcz_ui" }
tracing-appender = "0.2.2"
time = "0.3.30"
59 changes: 33 additions & 26 deletions crates/vcz/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
use clap::Parser;
use tokio::{runtime::Runtime, spawn, sync::mpsc};

use tracing::debug;
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
use vincenzo::daemon::Daemon;
use vincenzo::error::Error;
use vincenzo::{config::Config, daemon::Args};
use tracing::{debug, Level};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::{fmt::time::OffsetTime, FmtSubscriber};
use vincenzo::{
config::Config, daemon::{Args, Daemon}, error::Error
};

use vcz_ui::{UIMsg, UI};

#[tokio::main]
async fn main() -> Result<(), Error> {
let console_layer = console_subscriber::spawn();
let r = tracing_subscriber::registry();
r.with(console_layer);
let tmp = std::env::temp_dir();
let time = std::time::SystemTime::now();
let timestamp =
time.duration_since(std::time::UNIX_EPOCH).unwrap().as_millis();

let file_appender = RollingFileAppender::new(
Rotation::NEVER,
tmp,
format!("vcz-{timestamp}.log"),
);
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("../../log.txt")
.expect("Failed to open log file");
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::DEBUG)
.with_writer(non_blocking)
.with_timer(OffsetTime::new(
time::UtcOffset::current_local_offset()
.unwrap_or(time::UtcOffset::UTC),
time::format_description::parse(
"[year]-[month]-[day] [hour]:[minute]:[second]",
)
.unwrap(),
))
.with_ansi(false)
.finish();

tracing_subscriber::fmt()
.with_env_filter("tokio=trace,runtime=trace")
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.with_writer(file)
.compact()
.with_file(false)
.without_time()
.init();
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");

let args = Args::parse();
let config = Config::load().await.unwrap();

let download_dir = args.download_dir.unwrap_or(config.download_dir.clone());
let daemon_addr = args.daemon_addr.unwrap_or(
config
.daemon_addr
.unwrap_or("127.0.0.1:3030".parse().unwrap()),
config.daemon_addr.unwrap_or("127.0.0.1:3030".parse().unwrap()),
);

let mut daemon = Daemon::new(download_dir);
Expand Down
4 changes: 1 addition & 3 deletions crates/vcz_daemon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vcz_daemon"
version = "0.0.2"
version = "0.0.3"
edition = "2021"

[[bin]]
Expand All @@ -13,8 +13,6 @@ path = "src/main.rs"
bendy = { version = "0.3.3", features = ["std"] }
bytes = "1.4.0"
clap = {version = "4.3.4", features = ["derive"]}
console-subscriber = "0.1.9"
decurse = "0.0.4"
directories = "5.0.1"
futures = "0.3.28"
hashbrown = "0.14.0"
Expand Down
51 changes: 28 additions & 23 deletions crates/vcz_daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ use clap::Parser;
use futures::SinkExt;
use tokio::net::{TcpListener, TcpStream};
use tokio_util::codec::Framed;
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
use vincenzo::{
config::Config,
daemon::{Args, Daemon},
daemon_wire::{DaemonCodec, Message},
config::Config, daemon::{Args, Daemon}, daemon_wire::{DaemonCodec, Message}
};
mod args;

Expand All @@ -22,26 +21,20 @@ async fn main() {
listen = args.daemon_addr;
}

let is_daemon_running = TcpListener::bind(listen.unwrap_or("127.0.0.1:3030".parse().unwrap()))
.await
.is_err();
let is_daemon_running =
TcpListener::bind(listen.unwrap_or(Daemon::DEFAULT_LISTENER))
.await
.is_err();

// if the daemon is already running,
// we dont want to run it again,
// we just want to run the CLI flags.
// if the daemon is not running, run it
if !is_daemon_running {
let console_layer = console_subscriber::spawn();
let r = tracing_subscriber::registry();
r.with(console_layer);

tracing_subscriber::fmt()
.with_env_filter("tokio=trace,runtime=trace")
.with_max_level(tracing::Level::INFO)
.with_target(false)
.compact()
.with_file(false)
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO)
.without_time()
.init();
.finish();

tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");

let mut daemon = Daemon::new(download_dir);

Expand All @@ -52,14 +45,26 @@ async fn main() {
daemon.run().await.unwrap();
}

// send messages to daemon depending on the flags passed
let socket = TcpStream::connect(listen.unwrap_or("127.0.0.1:3030".parse().unwrap()))
// Now that the daemon is running on a process,
// the user can send commands using CLI flags,
// using a different terminal, and we want
// to listen to these flags and send messages to Daemon.
//
// 1. Create a TCP connection to Daemon
let socket = TcpStream::connect(listen.unwrap_or(Daemon::DEFAULT_LISTENER))
.await
.unwrap();

let mut socket = Framed::new(socket, DaemonCodec);

// 2. Fire the corresponding message of a CLI flag.
//
// add a a new torrent to Daemon
if let Some(magnet) = args.magnet {
socket.send(Message::NewTorrent(magnet)).await.unwrap();
}

if args.stats {
socket.send(Message::PrintTorrentStatus).await.unwrap();
}
}
4 changes: 2 additions & 2 deletions crates/vcz_ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vcz_ui"
version = "0.0.2"
version = "0.0.3"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -12,7 +12,7 @@ futures = "0.3.28"
hashbrown = "0.14.0"
magnet-url = "2.0.0"
rand = "0.8.5"
ratatui = { version = "0.22.0", features = ["all-widgets"] }
ratatui = { version = "0.24.0", features = ["all-widgets"] }
serde = { version = "1.0.185", features = ["derive"] }
thiserror = "1.0.47"
tokio = { version = "1.32.0", features = ["rt", "fs", "tracing", "time", "macros", "rt-multi-thread", "sync", "io-std", "io-util", "net"] }
Expand Down
Loading

0 comments on commit cacf9a7

Please sign in to comment.