Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
f6d8e23
feat: define live frontend events
artrixdotdev Jul 24, 2026
9dca2cf
feat: add lag-aware event subscriptions
artrixdotdev Jul 24, 2026
949f3d5
feat: model engine lifecycle snapshots
artrixdotdev Jul 24, 2026
bd856b7
refactor: separate live views from snapshots
artrixdotdev Jul 24, 2026
7d8f0ca
feat: maintain live frontend state
artrixdotdev Jul 24, 2026
2658378
feat: attach live state to the engine
artrixdotdev Jul 24, 2026
8808a15
feat: attach live state to torrents
artrixdotdev Jul 24, 2026
dabbc99
feat: expose subscriptions on public handles
artrixdotdev Jul 24, 2026
818387c
feat: publish torrent removal events
artrixdotdev Jul 24, 2026
f710206
feat: publish torrent state transitions
artrixdotdev Jul 24, 2026
07504a1
feat: publish metadata resolution events
artrixdotdev Jul 24, 2026
cd9a575
feat: publish live progress events
artrixdotdev Jul 24, 2026
efcc681
feat: publish peer lifecycle events
artrixdotdev Jul 24, 2026
dc02096
feat: publish tracker announce events
artrixdotdev Jul 24, 2026
23a3320
feat: publish frontend health events
artrixdotdev Jul 24, 2026
44e6b94
feat: publish live torrent updates
artrixdotdev Jul 24, 2026
1886d7d
feat: add stateful frontend listeners
artrixdotdev Jul 24, 2026
e3825da
feat: resolve torrents through engine handles
artrixdotdev Jul 24, 2026
eaad2af
feat: define frontend command messages
artrixdotdev Jul 24, 2026
34985fa
refactor: return typed torrent handle errors
artrixdotdev Jul 24, 2026
fc6dc9d
feat: route typed torrent commands
artrixdotdev Jul 24, 2026
3214304
feat: route typed engine commands
artrixdotdev Jul 24, 2026
623edd7
refactor: isolate the test torrent constructor
artrixdotdev Jul 24, 2026
1e4bf3e
test: cover live frontend lifecycle
artrixdotdev Jul 24, 2026
d719a85
test: cover listener lag recovery
artrixdotdev Jul 24, 2026
da90187
test: protect tracker event credentials
artrixdotdev Jul 24, 2026
fba6d06
docs: add live frontend example
artrixdotdev Jul 24, 2026
5cdc465
docs: explain live frontend integration
artrixdotdev Jul 24, 2026
406ec94
refactor: reserve snapshots for persistence
artrixdotdev Jul 24, 2026
abeebe8
feat: restore torrents from snapshots
artrixdotdev Jul 24, 2026
0c39e49
feat: restore engines from snapshots
artrixdotdev Jul 24, 2026
4009993
docs: define snapshot persistence boundary
artrixdotdev Jul 24, 2026
ddb8122
test: await live view initialization
artrixdotdev Jul 24, 2026
157b431
fix: ignore updates after torrent removal
artrixdotdev Jul 24, 2026
e9f98aa
fix: reject inconsistent torrent snapshots
artrixdotdev Jul 24, 2026
55981de
docs: clarify live listener recovery
artrixdotdev Jul 24, 2026
716afb6
refactor: share torrent commands across handles
artrixdotdev Jul 24, 2026
6e15184
docs: demonstrate snapshot persistence
artrixdotdev Jul 24, 2026
0b98a49
refactor: generalize live frontend channels
artrixdotdev Jul 24, 2026
59a1322
feat: add hierarchical live handles
artrixdotdev Jul 24, 2026
d430600
refactor: simplify live frontend boundaries
artrixdotdev Jul 24, 2026
fcbdd4d
test: preserve terminal peer state
artrixdotdev Jul 24, 2026
6b109ab
fix: serialize live publisher updates
artrixdotdev Jul 24, 2026
7b705db
fix: clean up failed torrent restores
artrixdotdev Jul 24, 2026
b3e142e
fix: redact udp tracker credentials
artrixdotdev Jul 24, 2026
459abe8
test: verify restored storage configuration
artrixdotdev Jul 24, 2026
2a7f352
fix: retain restoring torrent updates
artrixdotdev Jul 24, 2026
53b90f5
fix: close live subscriptions with publishers
artrixdotdev Jul 24, 2026
d784c53
refactor: compose hierarchical live scopes
artrixdotdev Jul 24, 2026
9c58b57
fix: separate tracker identity from display labels
artrixdotdev Jul 24, 2026
fa8a4e1
fix: make peer disconnection terminal
artrixdotdev Jul 24, 2026
0f55236
feat: aggregate live torrent transfer metrics
artrixdotdev Jul 24, 2026
6e25683
refactor: centralize transactional snapshot restore
artrixdotdev Jul 24, 2026
aac1106
refactor: preserve typed actor communication errors
artrixdotdev Jul 24, 2026
ec660fe
test: verify terminal frontend reconciliation
artrixdotdev Jul 24, 2026
90d073d
fix: break actor frontend ownership cycles
artrixdotdev Jul 24, 2026
a7e9f60
refactor: isolate generic live publishing
artrixdotdev Jul 24, 2026
3952831
test: account for terminal peer views
artrixdotdev Jul 24, 2026
e7bc20d
fix: simplify snapshot metadata fallback
artrixdotdev Jul 24, 2026
1eadb44
refactor: complete frontend and restore invariants
artrixdotdev Jul 25, 2026
1b66696
docs: define adapter-neutral architecture invariants
artrixdotdev Jul 25, 2026
125dfa5
refactor: simplify frontend module and ownership layout
artrixdotdev Jul 25, 2026
204fa45
fix: keep torrent downloads progressing to completion
artrixdotdev Jul 25, 2026
8556610
docs: move architecture guides into rustdoc
artrixdotdev Jul 25, 2026
87dca15
docs: lead crate docs with download example
artrixdotdev Jul 25, 2026
fe279be
docs: make crate landing page a practical guide
artrixdotdev Jul 25, 2026
96c7821
docs: verify getting started workflows
artrixdotdev Jul 25, 2026
19c9b02
feat: expose peer and tracker transfer metrics
artrixdotdev Jul 26, 2026
7c42476
test: strengthen frontend scope lifecycle coverage
artrixdotdev Jul 26, 2026
588fd4b
chore: configure live frontend example tracing
artrixdotdev Jul 26, 2026
2a33eb2
refactor: shorten live hub naming
artrixdotdev Jul 26, 2026
f2e4950
refactor: rename frontend API to live
artrixdotdev Jul 26, 2026
0459651
fix: harden live lifecycle handling
artrixdotdev Jul 27, 2026
99d2538
feat: gate live runtime behind a default feature
artrixdotdev Jul 27, 2026
c7844ce
refactor: reduce live feature branching
artrixdotdev Jul 27, 2026
d0d5f53
refactor: streamline live feature maintenance
artrixdotdev Jul 27, 2026
ee773aa
refactor: group live-only methods
artrixdotdev Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
- name: 🔨 Build
run: cargo build --verbose

- name: 🪶 Build actor-only libtortillas
run: cargo check -p libtortillas --no-default-features

- name: Install latest nextest release
uses: taiki-e/install-action@nextest

Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ See our roadmap [here](https://github.com/users/artrixdotdev/projects/6).


### 📈 Future Plans
- Frontend TUI (Text User Interface)
- TUI (Text User Interface)

### ❌ Not Planned
- WebTorrent connections: Due to the lack of clear documentation and complex, undocumented protocols WebTorrent support is not currently planned.
Expand Down Expand Up @@ -70,7 +70,7 @@ Keep tests focused on one behavior, prefer deterministic fixtures with `include_

## 📦 Installation
### Tortillas
Tortillas is the frontend TUI (Text User Interface) application (what most people want)
Tortillas is the TUI (Text User Interface) application most people will use.

There are plans to publish tortillas to registries such as [crates.io](https://crates.io) and [the AUR](https://aur.archlinux.org). However, for now, you can install it from source using cargo:
```bash
Expand All @@ -81,12 +81,23 @@ This will install `tortillas` to your local Rust toolchain.


### Libtortillas
Libtortillas is the library that powers the frontend TUI application. It is a library that can be used to build your own frontend application or integrate with existing frontend applications.
Libtortillas is the engine behind the TUI. It can also be embedded in other
applications that need BitTorrent downloads, seeding, and observable progress.

```bash
cargo add --git https://github.com/artrixdotdev/tortillas libtortillas
```

The `live` feature is enabled by default and provides views, metrics, event
streams, and listener handles. Applications that only need actor-backed
commands and direct state queries can remove that projection and publication
overhead:

```toml
[dependencies]
libtortillas = { git = "https://github.com/artrixdotdev/tortillas", default-features = false }
```

#### Runtime Contract

`libtortillas` is a Tokio-first library. Applications that use it must run
Expand All @@ -96,11 +107,17 @@ For the Tortillas TUI, the binary should own a single application runtime,
typically through `#[tokio::main]`, and create `libtortillas::engine::Engine`
inside that runtime. UI rendering or terminal input that blocks should run on a
dedicated thread or, for bounded work, through Tokio blocking tasks, then send
commands into async engine tasks. Long-lived input loops should use a dedicated
application actions into async engine tasks. Long-lived input loops should use a dedicated
thread because `spawn_blocking` tasks cannot be aborted once they start. The
library does not currently support swapping in a different async runtime, HTTP
client, clock, listener, or storage executor.

With the default `live` feature, use listeners for current state and
incremental updates, and call `Engine` and `Torrent` methods for operations. Do
not poll persistence snapshots to drive a display. See the
[`libtortillas::live` API documentation](https://docs.rs/libtortillas/latest/libtortillas/live/) and the
[`live` example](crates/libtortillas/examples/live.rs).

## 🤝 Contributing

We welcome contributions! If you'd like to help improve `tortillas`, please check out our [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and tips.
Expand Down
24 changes: 24 additions & 0 deletions crates/libtortillas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ keywords = ["bittorrent", "p2p", "torrent"]
categories = ["network-programming", "concurrency"]
readme = "../../README.md"

[features]
default = ["live"]
live = ["dep:tokio-stream"]

[dependencies]
serde = { workspace = true }
Expand Down Expand Up @@ -39,8 +42,29 @@ kameo_actors = "^0.5"
dashmap = { version = "^6", features = ["serde"] }
bon = "^3.9"
tokio-util = "^0.7"
tokio-stream = { version = "^0.1", features = ["sync"], optional = true }

[dev-dependencies]
tracing-test = "0.2.6"
tracing-subscriber = { workspace = true }
serde_json = "^1"

[[example]]
name = "live"
required-features = ["live"]

[[test]]
name = "dht_network"
required-features = ["live"]

[[test]]
name = "engine_lifecycle"
required-features = ["live"]

[[test]]
name = "live"
required-features = ["live"]

[[test]]
name = "persistence"
required-features = ["live"]
85 changes: 85 additions & 0 deletions crates/libtortillas/examples/live.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::path::PathBuf;

use libtortillas::prelude::{
Engine, EngineEventKind, EventStreamError, TorrentEventKind, TorrentSource, TorrentState,
};
use tracing::{error, info, warn};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter("live=trace,off")
.init();
let mut args = std::env::args_os().skip(1).map(PathBuf::from);
let Some(torrent_path) = args.next() else {
error!("pass a .torrent file path and optional session path to run the example");
return Ok(());
};

println!("Torrent path: {:?}", torrent_path);
let session_path = args.next();

let engine = Engine::default();
let mut listener = engine.listener();
let event_task = tokio::spawn(async move {
loop {
match listener.recv().await {
Ok(event) => {
let view = listener.view();
info!(
sequence = event.sequence,
torrent_count = view.torrent_count(),
?event.kind,
"received an engine event"
);
if matches!(event.kind, EngineEventKind::Shutdown(_)) {
break;
}
}
Err(EventStreamError::Lagged(events)) => {
let view = listener.view();
warn!(
events,
torrent_count = view.torrent_count(),
"refreshing current state after lag"
);
}
Err(EventStreamError::Closed) => {
info!("engine event stream closed");
break;
}
}
}
});

let torrent = engine
.add_torrent(TorrentSource::torrent_file_path(torrent_path))
.await?;

let mut torrent_listener = torrent.listener();
torrent.pause().await?;
let paused = loop {
let event = torrent_listener.recv().await?;
if matches!(
event.kind,
TorrentEventKind::StateChanged {
current: TorrentState::Paused,
..
}
) {
break event;
}
};
info!(sequence = paused.sequence, ?paused.kind, "torrent paused");
torrent.start().await?;

tokio::signal::ctrl_c().await?;
if let Some(path) = session_path {
let snapshot = engine.snapshot().await?;
tokio::fs::write(&path, serde_json::to_vec_pretty(&snapshot)?).await?;
info!(?path, "saved resumable engine state");
}
engine.shutdown().await?;
event_task.await?;
Ok(())
}
81 changes: 0 additions & 81 deletions crates/libtortillas/src/ARCHITECTURE.md

This file was deleted.

Loading
Loading