Update docs, cleanup for 4.0.0 release
This commit is contained in:
parent
3160f06f65
commit
006d83d6a7
29 changed files with 206 additions and 116 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -1242,7 +1242,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.1",
|
||||
|
|
@ -1277,6 +1277,7 @@ dependencies = [
|
|||
"size_format",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-test",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -1310,7 +1311,7 @@ version = "2.2.1"
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-core"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
|
|
@ -1330,7 +1331,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-dht"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backoff",
|
||||
|
|
@ -1355,7 +1356,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-peer-protocol"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
|
@ -1960,14 +1961,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rqbit"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"console-subscriber",
|
||||
"futures",
|
||||
"librqbit",
|
||||
"librqbit-dht",
|
||||
"parking_lot",
|
||||
"parse_duration",
|
||||
"regex",
|
||||
|
|
@ -2461,6 +2461,19 @@ dependencies = [
|
|||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-test"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.10"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Access with http://localhost:3030/web/
|
|||
## Desktop app
|
||||
The desktop app is a [thin wrapper](https://github.com/ikatson/rqbit/blob/main/desktop/src-tauri/src/main.rs) on top of the Web UI frontend.
|
||||
|
||||
Download it in [Releases](https://github.com/ikatson/rqbit/releases) starting from [4.0.0-beta.3](https://github.com/ikatson/rqbit/releases/tag/v4.0.0-beta.3).
|
||||
Download it in [Releases](https://github.com/ikatson/rqbit/releases).
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "librqbit-dht"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
edition = "2021"
|
||||
description = "DHT implementation, used in rqbit torrent client."
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -34,7 +34,7 @@ indexmap = "2"
|
|||
dashmap = {version = "5.5.3", features = ["serde"]}
|
||||
|
||||
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
||||
librqbit-core = {path="../librqbit_core", version = "3.2.0"}
|
||||
librqbit-core = {path="../librqbit_core", version = "3.2.1"}
|
||||
chrono = {version = "0.4.31", features = ["serde"]}
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ impl PeerStore {
|
|||
Vec::new()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn garbage_collect_peers(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "librqbit"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
authors = ["Igor Katson <igor.katson@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "The main library used by rqbit torrent client. The binary is just a small wrapper on top of it."
|
||||
|
|
@ -24,11 +24,11 @@ rust-tls = ["reqwest/rustls-tls"]
|
|||
[dependencies]
|
||||
bencode = {path = "../bencode", default-features=false, package="librqbit-bencode", version="2.2.1"}
|
||||
buffers = {path = "../buffers", package="librqbit-buffers", version = "2.2.1"}
|
||||
librqbit-core = {path = "../librqbit_core", version = "3.2.0"}
|
||||
librqbit-core = {path = "../librqbit_core", version = "3.2.1"}
|
||||
clone_to_owned = {path = "../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
||||
peer_binary_protocol = {path = "../peer_binary_protocol", package="librqbit-peer-protocol", version = "3.2.0"}
|
||||
peer_binary_protocol = {path = "../peer_binary_protocol", package="librqbit-peer-protocol", version = "3.2.1"}
|
||||
sha1w = {path = "../sha1w", default-features=false, package="librqbit-sha1-wrapper", version="2.2.1"}
|
||||
dht = {path = "../dht", package="librqbit-dht", version="4.0.0-beta.3"}
|
||||
dht = {path = "../dht", package="librqbit-dht", version="4.0.0"}
|
||||
|
||||
tokio = {version = "1", features = ["macros", "rt-multi-thread"]}
|
||||
axum = {version = "0.7"}
|
||||
|
|
@ -67,3 +67,4 @@ serde_with = "3.4.0"
|
|||
[dev-dependencies]
|
||||
futures = {version = "0.3"}
|
||||
tracing-subscriber = "0.3"
|
||||
tokio-test = "0.4"
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
# librqbit
|
||||
|
||||
A fully featured, easy to use torrent downloading library used as a backbone of [rqbit](https://github.com/ikatson/rqbit) CLI.
|
||||
A fully featured, easy to use torrent downloading library used as a backbone of [rqbit](https://github.com/ikatson/rqbit).
|
||||
|
||||
## Basic example
|
||||
See [examples on GitHub](https://github.com/ikatson/rqbit/tree/main/crates/librqbit/examples).
|
||||
See [examples on GitHub](https://github.com/ikatson/rqbit/tree/main/crates/librqbit/examples).
|
||||
|
||||
## Documentation
|
||||
[librqbit at docs.rs](https://docs.rs/librqbit/latest/librqbit/)
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use librqbit::session::{AddTorrent, AddTorrentOptions, AddTorrentResponse, Session};
|
||||
use librqbit::{AddTorrent, AddTorrentOptions, AddTorrentResponse, Session};
|
||||
use tracing::info;
|
||||
|
||||
// This is ubuntu-21.04-live-server-amd64.iso.torrent
|
||||
|
|
@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
.expect("the first argument should be the output directory");
|
||||
|
||||
// Create the session
|
||||
let session = Session::new(output_dir.into(), Default::default())
|
||||
let session = Session::new(output_dir.into())
|
||||
.await
|
||||
.context("error creating session")?;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,15 +16,16 @@ use crate::{
|
|||
},
|
||||
torrent_state::{
|
||||
peer::stats::snapshot::{PeerStatsFilter, PeerStatsSnapshot},
|
||||
stats::{LiveStats, TorrentStats},
|
||||
ManagedTorrentHandle,
|
||||
},
|
||||
};
|
||||
|
||||
pub use crate::torrent_state::stats::{LiveStats, TorrentStats};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ApiError>;
|
||||
|
||||
// Library API for use in different web frameworks.
|
||||
// Contains all methods you might want to expose with (de)serializable inputs/outputs.
|
||||
/// Library API for use in different web frameworks.
|
||||
/// Contains all methods you might want to expose with (de)serializable inputs/outputs.
|
||||
pub struct Api {
|
||||
session: Arc<Session>,
|
||||
rust_log_reload_tx: Option<UnboundedSender<String>>,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ mod tests {
|
|||
async fn read_metainfo_from_dht() {
|
||||
init_logging();
|
||||
|
||||
let info_hash = Id20::from_str("cf3ea75e2ebbd30e0da6e6e215e2226bf35f2e33").unwrap();
|
||||
let info_hash = Id20::from_str("cab507494d02ebb1178b38f2e9d7be299c86b862").unwrap();
|
||||
let dht = DhtBuilder::new().await.unwrap();
|
||||
let peer_rx = dht.get_peers(info_hash).unwrap();
|
||||
let peer_id = generate_peer_id();
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ type ApiState = Arc<Api>;
|
|||
|
||||
use crate::api::Result;
|
||||
|
||||
// Public API
|
||||
/// An HTTP server for the API.
|
||||
#[derive(Clone)]
|
||||
pub struct HttpApi {
|
||||
inner: ApiState,
|
||||
|
|
@ -37,6 +37,8 @@ impl HttpApi {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run the HTTP server forever on the given address.
|
||||
/// If read_only is passed, no state-modifying methods will be exposed.
|
||||
pub async fn make_http_api_and_run(
|
||||
self,
|
||||
addr: SocketAddr,
|
||||
|
|
@ -267,11 +269,11 @@ impl HttpApi {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct OnlyFiles(Vec<usize>);
|
||||
pub struct InitialPeers(pub Vec<SocketAddr>);
|
||||
pub(crate) struct OnlyFiles(Vec<usize>);
|
||||
pub(crate) struct InitialPeers(pub Vec<SocketAddr>);
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct TorrentAddQueryParams {
|
||||
pub(crate) struct TorrentAddQueryParams {
|
||||
pub overwrite: Option<bool>,
|
||||
pub output_folder: Option<String>,
|
||||
pub sub_folder: Option<String>,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,52 @@
|
|||
//!
|
||||
//! This crate provides everything necessary to download [torrents](https://en.wikipedia.org/wiki/BitTorrent).
|
||||
//!
|
||||
//! # Quick usage example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use librqbit::*;
|
||||
//!
|
||||
//! tokio_test::block_on(async {
|
||||
//! let session = Session::new("/tmp/where-to-download".into()).await.unwrap();
|
||||
//! let managed_torrent_handle = session.add_torrent(
|
||||
//! AddTorrent::from_url("magnet:?xt=urn:btih:cab507494d02ebb1178b38f2e9d7be299c86b862"),
|
||||
//! None // options
|
||||
//! ).await.unwrap().into_handle().unwrap();
|
||||
//! managed_torrent_handle.wait_until_completed().await.unwrap();
|
||||
//! })
|
||||
//! ```
|
||||
//!
|
||||
//! # Overview
|
||||
//! The main type to start off with is [`Session`].
|
||||
//!
|
||||
//! It also proved useful to use the [`Api`] when building the rqbit desktop app, as it provides
|
||||
//! a facade that works with simple serializable types.
|
||||
|
||||
pub mod api;
|
||||
pub mod api_error;
|
||||
pub mod chunk_tracker;
|
||||
pub mod dht_utils;
|
||||
pub mod file_ops;
|
||||
mod api_error;
|
||||
mod chunk_tracker;
|
||||
mod dht_utils;
|
||||
mod file_ops;
|
||||
pub mod http_api;
|
||||
pub mod http_api_client;
|
||||
pub mod peer_connection;
|
||||
pub mod peer_info_reader;
|
||||
pub mod session;
|
||||
pub mod spawn_utils;
|
||||
pub mod torrent_state;
|
||||
pub mod tracker_comms;
|
||||
pub mod type_aliases;
|
||||
mod peer_connection;
|
||||
mod peer_info_reader;
|
||||
mod session;
|
||||
mod spawn_utils;
|
||||
mod torrent_state;
|
||||
mod tracker_comms;
|
||||
mod type_aliases;
|
||||
|
||||
pub use api::Api;
|
||||
pub use api_error::ApiError;
|
||||
pub use dht;
|
||||
pub use peer_connection::PeerConnectionOptions;
|
||||
pub use session::{
|
||||
AddTorrent, AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session, SessionOptions,
|
||||
SUPPORTED_SCHEMES,
|
||||
};
|
||||
pub use spawn_utils::spawn as librqbit_spawn;
|
||||
pub use torrent_state::{ManagedTorrent, ManagedTorrentState};
|
||||
|
||||
pub use buffers::*;
|
||||
pub use clone_to_owned::CloneToOwned;
|
||||
|
|
|
|||
|
|
@ -119,9 +119,7 @@ impl<H: PeerConnectionHandler> PeerConnection<H> {
|
|||
options: options.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
pub fn into_handler(self) -> H {
|
||||
self.handler
|
||||
}
|
||||
|
||||
pub async fn manage_peer(
|
||||
&self,
|
||||
mut outgoing_chan: tokio::sync::mpsc::UnboundedReceiver<WriterRequest>,
|
||||
|
|
|
|||
|
|
@ -183,24 +183,41 @@ fn compute_only_files<ByteBuf: AsRef<[u8]>>(
|
|||
Ok(only_files)
|
||||
}
|
||||
|
||||
/// Options for adding new torrents to the session.
|
||||
#[serde_as]
|
||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||
pub struct AddTorrentOptions {
|
||||
/// Start in paused state.
|
||||
#[serde(default)]
|
||||
pub paused: bool,
|
||||
/// A regex to only download files matching it.
|
||||
pub only_files_regex: Option<String>,
|
||||
/// An explicit list of file IDs to download.
|
||||
/// To see the file indices, run with "list_only".
|
||||
pub only_files: Option<Vec<usize>>,
|
||||
/// Allow writing on top of existing files, including when resuming a torrent.
|
||||
/// You probably want to set it, however for safety it's not default.
|
||||
#[serde(default)]
|
||||
pub overwrite: bool,
|
||||
/// Only list the files in the torrent without starting it.
|
||||
#[serde(default)]
|
||||
pub list_only: bool,
|
||||
/// The output folder for the torrent. If not set, the session's default one will be used.
|
||||
pub output_folder: Option<String>,
|
||||
/// Sub-folder within session's default output folder. Will error if "output_folder" if also set.
|
||||
/// By default, multi-torrent files are downloaded to a sub-folder.
|
||||
pub sub_folder: Option<String>,
|
||||
/// Peer connection options, timeouts etc. If not set, session's defaults will be used.
|
||||
pub peer_opts: Option<PeerConnectionOptions>,
|
||||
|
||||
/// Force a refresh interval for polling trackers.
|
||||
#[serde_as(as = "Option<serde_with::DurationSeconds>")]
|
||||
pub force_tracker_interval: Option<Duration>,
|
||||
|
||||
/// Initial peers to start of with.
|
||||
pub initial_peers: Option<Vec<SocketAddr>>,
|
||||
// This is used to restore the session.
|
||||
|
||||
/// This is used to restore the session from serialized state.
|
||||
#[serde(skip)]
|
||||
pub preferred_id: Option<usize>,
|
||||
}
|
||||
|
|
@ -220,6 +237,16 @@ pub enum AddTorrentResponse {
|
|||
Added(TorrentId, ManagedTorrentHandle),
|
||||
}
|
||||
|
||||
impl AddTorrentResponse {
|
||||
pub fn into_handle(self) -> Option<ManagedTorrentHandle> {
|
||||
match self {
|
||||
Self::AlreadyManaged(_, handle) => Some(handle),
|
||||
Self::ListOnly(_) => None,
|
||||
Self::Added(_, handle) => Some(handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_local_file_including_stdin(filename: &str) -> anyhow::Result<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
if filename == "-" {
|
||||
|
|
@ -276,25 +303,36 @@ impl<'a> AddTorrent<'a> {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct SessionOptions {
|
||||
/// Turn on to disable DHT.
|
||||
pub disable_dht: bool,
|
||||
/// Turn on to disable DHT persistence. By default it will re-use stored DHT
|
||||
/// configuration, including the port it listens on.
|
||||
pub disable_dht_persistence: bool,
|
||||
pub persistence: bool,
|
||||
pub persistence_filename: Option<PathBuf>,
|
||||
/// Pass in to configure DHT persistence filename. This can be used to run multiple
|
||||
/// librqbit instances at a time.
|
||||
pub dht_config: Option<PersistentDhtConfig>,
|
||||
|
||||
/// Turn on to dump session contents into a file periodically, so that on next start
|
||||
/// all remembered torrents will continue where they left off.
|
||||
pub persistence: bool,
|
||||
/// The filename for persistence. By default uses an OS-specific folder.
|
||||
pub persistence_filename: Option<PathBuf>,
|
||||
|
||||
/// The peer ID to use. If not specified, a random one will be generated.
|
||||
pub peer_id: Option<Id20>,
|
||||
/// Configure default peer connection options. Can be overriden per torrent.
|
||||
pub peer_opts: Option<PeerConnectionOptions>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub async fn new(
|
||||
output_folder: PathBuf,
|
||||
spawner: BlockingSpawner,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
Self::new_with_opts(output_folder, spawner, SessionOptions::default()).await
|
||||
/// Create a new session. The passed in folder will be used as a default unless overriden per torrent.
|
||||
pub async fn new(output_folder: PathBuf) -> anyhow::Result<Arc<Self>> {
|
||||
Self::new_with_opts(output_folder, SessionOptions::default()).await
|
||||
}
|
||||
|
||||
/// Create a new session with options.
|
||||
pub async fn new_with_opts(
|
||||
output_folder: PathBuf,
|
||||
spawner: BlockingSpawner,
|
||||
opts: SessionOptions,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
let peer_id = opts.peer_id.unwrap_or_else(generate_peer_id);
|
||||
|
|
@ -316,6 +354,7 @@ impl Session {
|
|||
.data_dir()
|
||||
.join("session.json"),
|
||||
};
|
||||
let spawner = BlockingSpawner::default();
|
||||
let session = Arc::new(Self {
|
||||
persistence_filename,
|
||||
peer_id,
|
||||
|
|
@ -474,6 +513,7 @@ impl Session {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a callback given the currently managed torrents.
|
||||
pub fn with_torrents<R>(
|
||||
&self,
|
||||
callback: impl Fn(&mut dyn Iterator<Item = (TorrentId, &ManagedTorrentHandle)>) -> R,
|
||||
|
|
@ -481,6 +521,7 @@ impl Session {
|
|||
callback(&mut self.db.read().torrents.iter().map(|(id, t)| (*id, t)))
|
||||
}
|
||||
|
||||
/// Add a torrent to the session.
|
||||
pub async fn add_torrent(
|
||||
&self,
|
||||
add: AddTorrent<'_>,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
/// Spawn a future inside a tracing span, while logging it's start,
|
||||
/// finish and periodically logging if it's still alive.
|
||||
pub fn spawn(
|
||||
_name: &str,
|
||||
span: tracing::Span,
|
||||
|
|
@ -7,7 +9,7 @@ pub fn spawn(
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BlockingSpawner {
|
||||
pub(crate) struct BlockingSpawner {
|
||||
allow_tokio_block_in_place: bool,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ pub struct ManagedTorrentInfo {
|
|||
pub info: TorrentMetaV1Info<ByteString>,
|
||||
pub info_hash: Id20,
|
||||
pub out_dir: PathBuf,
|
||||
pub spawner: BlockingSpawner,
|
||||
pub(crate) spawner: BlockingSpawner,
|
||||
pub trackers: HashSet<Url>,
|
||||
pub peer_id: Id20,
|
||||
pub lengths: Lengths,
|
||||
|
|
@ -120,7 +120,10 @@ impl ManagedTorrent {
|
|||
f(&mut self.locked.write().state)
|
||||
}
|
||||
|
||||
pub fn with_chunk_tracker<R>(&self, f: impl FnOnce(&ChunkTracker) -> R) -> anyhow::Result<R> {
|
||||
pub(crate) fn with_chunk_tracker<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&ChunkTracker) -> R,
|
||||
) -> anyhow::Result<R> {
|
||||
let g = self.locked.read();
|
||||
match &g.state {
|
||||
ManagedTorrentState::Paused(p) => Ok(f(&p.chunk_tracker)),
|
||||
|
|
@ -132,6 +135,7 @@ impl ManagedTorrent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the live state if the torrent is live.
|
||||
pub fn live(&self) -> Option<Arc<TorrentStateLive>> {
|
||||
let g = self.locked.read();
|
||||
match &g.state {
|
||||
|
|
@ -164,7 +168,7 @@ impl ManagedTorrent {
|
|||
g.state = ManagedTorrentState::Error(error)
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
pub(crate) fn start(
|
||||
self: &Arc<Self>,
|
||||
initial_peers: Vec<SocketAddr>,
|
||||
peer_rx: Option<RequestPeersStream>,
|
||||
|
|
@ -309,6 +313,7 @@ impl ManagedTorrent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Pause the torrent if it's live.
|
||||
pub fn pause(&self) -> anyhow::Result<()> {
|
||||
let mut g = self.locked.write();
|
||||
match &g.state {
|
||||
|
|
@ -330,6 +335,7 @@ impl ManagedTorrent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get stats.
|
||||
pub fn stats(&self) -> TorrentStats {
|
||||
let mut resp = TorrentStats {
|
||||
total_bytes: self.info().lengths.total_length(),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ use librqbit_core::id20::Id20;
|
|||
#[derive(Clone, Copy)]
|
||||
pub enum TrackerRequestEvent {
|
||||
Started,
|
||||
#[allow(dead_code)]
|
||||
Stopped,
|
||||
#[allow(dead_code)]
|
||||
Completed,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { API } from "./http-api";
|
|||
ReactDOM.createRoot(document.getElementById('app') as HTMLInputElement).render(
|
||||
<StrictMode>
|
||||
<APIContext.Provider value={API}>
|
||||
<RqbitWebUI title="rqbit web UI - version 4.0.0-beta.3" />
|
||||
<RqbitWebUI title="rqbit web UI - version 4.0.0" />
|
||||
</APIContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "librqbit-core"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
edition = "2021"
|
||||
description = "Important utilities used throughout librqbit useful for working with torrents."
|
||||
license = "Apache-2.0"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::{cmp::Ordering, str::FromStr};
|
|||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
/// A 20-byte hash used throughout librqbit, for torrent info hashes, peer ids etc.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Id20(pub [u8; 20]);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ use anyhow::Context;
|
|||
|
||||
use crate::id20::Id20;
|
||||
|
||||
/// A parsed magnet link.
|
||||
pub struct Magnet {
|
||||
pub info_hash: Id20,
|
||||
pub trackers: Vec<String>,
|
||||
}
|
||||
|
||||
impl Magnet {
|
||||
/// Parse a magnet link.
|
||||
pub fn parse(url: &str) -> anyhow::Result<Magnet> {
|
||||
let url = url::Url::parse(url).context("magnet link must be a valid URL")?;
|
||||
if url.scheme() != "magnet" {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use tracing::{error, trace, Instrument};
|
||||
|
||||
/// Spawns a future with tracing instrumentation.
|
||||
pub fn spawn(
|
||||
span: tracing::Span,
|
||||
fut: impl std::future::Future<Output = anyhow::Result<()>> + Send + 'static,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct ProgressSnapshot {
|
|||
instant: Instant,
|
||||
}
|
||||
|
||||
/// Estimates download speed in a sliding time window.
|
||||
pub struct SpeedEstimator {
|
||||
latest_per_second_snapshots: Mutex<VecDeque<ProgressSnapshot>>,
|
||||
download_bytes_per_second: AtomicU64,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use crate::id20::Id20;
|
|||
pub type TorrentMetaV1Borrowed<'a> = TorrentMetaV1<ByteBuf<'a>>;
|
||||
pub type TorrentMetaV1Owned = TorrentMetaV1<ByteString>;
|
||||
|
||||
/// Parse torrent metainfo from bytes.
|
||||
pub fn torrent_from_bytes<'de, ByteBuf: Deserialize<'de>>(
|
||||
buf: &'de [u8],
|
||||
) -> anyhow::Result<TorrentMetaV1<ByteBuf>> {
|
||||
|
|
@ -25,6 +26,7 @@ pub fn torrent_from_bytes<'de, ByteBuf: Deserialize<'de>>(
|
|||
Ok(t)
|
||||
}
|
||||
|
||||
/// A parsed .torrent file.
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct TorrentMetaV1<BufType> {
|
||||
pub announce: BufType,
|
||||
|
|
@ -51,6 +53,7 @@ impl<BufType> TorrentMetaV1<BufType> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Main torrent information, shared by .torrent files and magnet link contents.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TorrentMetaV1Info<BufType> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "librqbit-peer-protocol"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
edition = "2021"
|
||||
description = "Protocol for working with torrent peers. Used in rqbit torrent client."
|
||||
license = "Apache-2.0"
|
||||
|
|
@ -23,6 +23,6 @@ byteorder = "1"
|
|||
buffers = {path="../buffers", package="librqbit-buffers", version = "2.2.1"}
|
||||
bencode = {path = "../bencode", default-features=false, package="librqbit-bencode", version="2.2.1"}
|
||||
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
||||
librqbit-core = {path="../librqbit_core", version = "3.2.0"}
|
||||
librqbit-core = {path="../librqbit_core", version = "3.2.1"}
|
||||
bitvec = "1"
|
||||
anyhow = "1"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rqbit"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
authors = ["Igor Katson <igor.katson@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A bittorrent command line client and server."
|
||||
|
|
@ -23,8 +23,7 @@ default-tls = ["librqbit/default-tls"]
|
|||
rust-tls = ["librqbit/rust-tls"]
|
||||
|
||||
[dependencies]
|
||||
librqbit = {path="../librqbit", default-features=false, version = "4.0.0-beta.3"}
|
||||
dht = {path="../dht", package="librqbit-dht", version="4.0.0-beta.3"}
|
||||
librqbit = {path="../librqbit", default-features=false, version = "4.0.0"}
|
||||
tokio = {version = "1", features = ["macros", "rt-multi-thread"]}
|
||||
console-subscriber = {version = "0.2", optional = true}
|
||||
anyhow = "1"
|
||||
|
|
|
|||
|
|
@ -3,16 +3,9 @@ use std::{io::LineWriter, net::SocketAddr, path::PathBuf, sync::Arc, time::Durat
|
|||
use anyhow::Context;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use librqbit::{
|
||||
api::ApiAddTorrentResponse,
|
||||
http_api::HttpApi,
|
||||
http_api_client,
|
||||
peer_connection::PeerConnectionOptions,
|
||||
session::{
|
||||
AddTorrent, AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session,
|
||||
SessionOptions,
|
||||
},
|
||||
spawn_utils::{spawn, BlockingSpawner},
|
||||
torrent_state::ManagedTorrentState,
|
||||
api::ApiAddTorrentResponse, http_api::HttpApi, http_api_client, librqbit_spawn, AddTorrent,
|
||||
AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, ManagedTorrentState,
|
||||
PeerConnectionOptions, Session, SessionOptions,
|
||||
};
|
||||
use size_format::SizeFormatterBinary as SF;
|
||||
use tracing::{error, error_span, info, trace_span, warn};
|
||||
|
|
@ -230,7 +223,7 @@ fn init_logging(opts: &Opts) -> tokio::sync::mpsc::UnboundedSender<String> {
|
|||
}
|
||||
|
||||
let (reload_tx, mut reload_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
spawn(
|
||||
librqbit_spawn(
|
||||
"fmt_filter_reloader",
|
||||
error_span!("fmt_filter_reloader"),
|
||||
async move {
|
||||
|
|
@ -278,21 +271,15 @@ fn _start_deadlock_detector_thread() {
|
|||
fn main() -> anyhow::Result<()> {
|
||||
let opts = Opts::parse();
|
||||
|
||||
let (mut rt_builder, spawner) = match opts.single_thread_runtime {
|
||||
true => (
|
||||
tokio::runtime::Builder::new_current_thread(),
|
||||
BlockingSpawner::new(false),
|
||||
),
|
||||
false => (
|
||||
{
|
||||
let mut b = tokio::runtime::Builder::new_multi_thread();
|
||||
if let Some(e) = opts.worker_threads {
|
||||
b.worker_threads(e);
|
||||
}
|
||||
b
|
||||
},
|
||||
BlockingSpawner::new(true),
|
||||
),
|
||||
let mut rt_builder = match opts.single_thread_runtime {
|
||||
true => tokio::runtime::Builder::new_current_thread(),
|
||||
false => {
|
||||
let mut b = tokio::runtime::Builder::new_multi_thread();
|
||||
if let Some(e) = opts.worker_threads {
|
||||
b.worker_threads(e);
|
||||
}
|
||||
b
|
||||
}
|
||||
};
|
||||
|
||||
let rt = rt_builder
|
||||
|
|
@ -306,10 +293,10 @@ fn main() -> anyhow::Result<()> {
|
|||
.max_blocking_threads(8)
|
||||
.build()?;
|
||||
|
||||
rt.block_on(async_main(opts, spawner))
|
||||
rt.block_on(async_main(opts))
|
||||
}
|
||||
|
||||
async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()> {
|
||||
async fn async_main(opts: Opts) -> anyhow::Result<()> {
|
||||
let logging_reload_tx = init_logging(&opts);
|
||||
|
||||
let mut sopts = SessionOptions {
|
||||
|
|
@ -384,14 +371,11 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
|
|||
sopts.persistence = !start_opts.disable_persistence;
|
||||
sopts.persistence_filename =
|
||||
start_opts.persistence_filename.clone().map(PathBuf::from);
|
||||
let session = Session::new_with_opts(
|
||||
PathBuf::from(&start_opts.output_folder),
|
||||
spawner,
|
||||
sopts,
|
||||
)
|
||||
.await
|
||||
.context("error initializing rqbit session")?;
|
||||
spawn(
|
||||
let session =
|
||||
Session::new_with_opts(PathBuf::from(&start_opts.output_folder), sopts)
|
||||
.await
|
||||
.context("error initializing rqbit session")?;
|
||||
librqbit_spawn(
|
||||
"stats_printer",
|
||||
trace_span!("stats_printer"),
|
||||
stats_printer(session.clone()),
|
||||
|
|
@ -464,19 +448,18 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
|
|||
.context(
|
||||
"output_folder is required if can't connect to an existing server",
|
||||
)?,
|
||||
spawner,
|
||||
sopts,
|
||||
)
|
||||
.await
|
||||
.context("error initializing rqbit session")?;
|
||||
spawn(
|
||||
librqbit_spawn(
|
||||
"stats_printer",
|
||||
trace_span!("stats_printer"),
|
||||
stats_printer(session.clone()),
|
||||
);
|
||||
let http_api = HttpApi::new(session.clone(), Some(logging_reload_tx));
|
||||
let http_api_listen_addr = opts.http_api_listen_addr;
|
||||
spawn(
|
||||
librqbit_spawn(
|
||||
"http_api",
|
||||
error_span!("http_api"),
|
||||
http_api
|
||||
|
|
|
|||
8
desktop/src-tauri/Cargo.lock
generated
8
desktop/src-tauri/Cargo.lock
generated
|
|
@ -1856,7 +1856,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
|
@ -1920,7 +1920,7 @@ version = "2.2.1"
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-core"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"directories",
|
||||
|
|
@ -1939,7 +1939,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-dht"
|
||||
version = "4.0.0-beta.3"
|
||||
version = "4.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backoff",
|
||||
|
|
@ -1963,7 +1963,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librqbit-peer-protocol"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ use anyhow::Context;
|
|||
use http::StatusCode;
|
||||
use librqbit::{
|
||||
api::{
|
||||
Api, ApiAddTorrentResponse, EmptyJsonResponse, TorrentDetailsResponse, TorrentListResponse,
|
||||
ApiAddTorrentResponse, EmptyJsonResponse, TorrentDetailsResponse, TorrentListResponse,
|
||||
TorrentStats,
|
||||
},
|
||||
api_error::ApiError,
|
||||
session::AddTorrentOptions,
|
||||
torrent_state::stats::TorrentStats,
|
||||
AddTorrent, AddTorrentOptions, Api, ApiError, Session, SessionOptions,
|
||||
};
|
||||
|
||||
struct State {
|
||||
|
|
@ -29,7 +28,7 @@ async fn torrent_create_from_url(
|
|||
) -> Result<ApiAddTorrentResponse, ApiError> {
|
||||
state
|
||||
.api
|
||||
.api_add_torrent(librqbit::session::AddTorrent::Url(url.into()), opts)
|
||||
.api_add_torrent(AddTorrent::Url(url.into()), opts)
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -46,10 +45,7 @@ async fn torrent_create_from_base64_file(
|
|||
.map_err(|e| ApiError::new_from_anyhow(StatusCode::BAD_REQUEST, e))?;
|
||||
state
|
||||
.api
|
||||
.api_add_torrent(
|
||||
librqbit::session::AddTorrent::TorrentFileBytes(bytes.into()),
|
||||
opts,
|
||||
)
|
||||
.api_add_torrent(AddTorrent::TorrentFileBytes(bytes.into()), opts)
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -110,10 +106,9 @@ async fn start_session() {
|
|||
.expect("download_dir()")
|
||||
.to_path_buf();
|
||||
|
||||
let s = librqbit::session::Session::new_with_opts(
|
||||
let session = Session::new_with_opts(
|
||||
download_folder,
|
||||
Default::default(),
|
||||
librqbit::session::SessionOptions {
|
||||
SessionOptions {
|
||||
disable_dht: false,
|
||||
disable_dht_persistence: false,
|
||||
persistence: true,
|
||||
|
|
@ -123,7 +118,7 @@ async fn start_session() {
|
|||
.await
|
||||
.expect("couldn't set up librqbit session");
|
||||
|
||||
let api = Api::new(s, None);
|
||||
let api = Api::new(session, None);
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(State { api })
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { API } from "./api";
|
|||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<APIContext.Provider value={API}>
|
||||
<RqbitWebUI title="Rqbit Desktop v4.0.0-beta.3" />
|
||||
<RqbitWebUI title="Rqbit Desktop v4.0.0" />
|
||||
</APIContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue