Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`fluent_uri::Uri<String>` instead of a `url::Url` previously
- all error types with an `InvalidURL` variant now have `fluent_uri::ParseError`
as source instead of `url::ParseError` previously
- `TrackerScheme` variant `UDP` has been renamed `Udp` to be more consistent with
other variants, and other rust types
- `TrackerScheme` no longer derives de/serialize because that's not actually
used in torrent files


### Added

Expand All @@ -25,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added new `MagnetLinkError` variants to be more precise about what's wrong with
a parsed magnet link.
- `MagnetLink::trackers` lists the trackers in the magnet link
- `TrackerScheme` and `Tracker` implement `FromStr`

### Fixed

- `Tracker` (de)serialization implementation is now custom and only uses the
inner URL instead of trying to (de)serialize all fields which was wrong

## Version 0.3.2 (2025-08-29)

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ sha1 = "0.10"
sha256 = "1.5"
rustc-hex = "2.1"
serde = { version = "1", features = [ "derive" ] }
fluent-uri = "0.4"
fluent-uri = { version = "0.4", features = [ "serde" ] }

[dev-dependencies]
serde_json = "1"
Expand Down
115 changes: 68 additions & 47 deletions src/tracker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use fluent_uri::{ParseError as UriParseError, Uri};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use std::str::FromStr;

/// A source of peers. Can be a [`Tracker`](crate::tracker::Tracker) or a decentralized source.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
Expand All @@ -10,27 +13,32 @@ pub enum PeerSource {
}

/// A centralized variant of a [`Peersource`](crate::tracker::PeerSource).
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct Tracker {
scheme: TrackerScheme,
url: Uri<String>,
}

impl Tracker {
pub fn new_http(uri: &str) -> Result<Self, UriParseError> {
let uri = Uri::parse(uri)?;
Ok(Self {
scheme: TrackerScheme::Http,
url: uri.into(),
})
/// Generate a new Tracker from a given string URL.
pub fn new(url: &str) -> Result<Tracker, TrackerError> {
let url = Uri::parse(url.to_string())?;
Tracker::from_url(&url)
}

pub fn new_udp(_uri: &str) -> Result<Self, UriParseError> {
unimplemented!();
/// Generate a new Tracker from a parsed URL.
///
/// Will fail if scheme is not "http", "https", "wss" or "udp".
pub fn from_url(url: &Uri<String>) -> Result<Tracker, TrackerError> {
Ok(Tracker {
scheme: TrackerScheme::from_str(url.scheme().as_str())?,
url: url.clone(),
})
}

pub fn new_ws(_uri: &str) -> Result<Self, UriParseError> {
unimplemented!();
/// Turns a centralized Tracker into a wider PeerSource
pub fn to_peer_source(&self) -> PeerSource {
PeerSource::from_tracker(self)
}

pub fn scheme(&self) -> &TrackerScheme {
Expand All @@ -42,12 +50,59 @@ impl Tracker {
}
}

impl<'de> Deserialize<'de> for Tracker {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Tracker::new(&s).map_err(serde::de::Error::custom)
}
}

impl Serialize for Tracker {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// serializer.serialize(&self.url)
self.url.serialize(serializer)
}
}

impl FromStr for Tracker {
type Err = TrackerError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}

/// A protocol used by a [`Tracker`](crate::tracker::Tracker).
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
///
/// Does not implement Serialize/Deserialize because it's actually not in the
/// torrent data. It is constructed from the parsed tracker URLs contained in
/// the torrent data.
#[derive(Clone, Debug, PartialEq)]
pub enum TrackerScheme {
Websocket,
Http,
UDP,
Udp,
}

impl FromStr for TrackerScheme {
type Err = TrackerError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http" | "https" => Ok(Self::Http),
"ws" => Ok(Self::Websocket),
"udp" => Ok(Self::Udp),
_ => Err(TrackerError::InvalidScheme {
scheme: s.to_string(),
}),
}
}
}

/// Error occurred during parsing a [`Tracker`](crate::tracker::Tracker).
Expand Down Expand Up @@ -103,40 +158,6 @@ impl PeerSource {
}
}

impl Tracker {
/// Generate a new Tracker from a given string URL.
pub fn new(url: &str) -> Result<Tracker, TrackerError> {
let url = Uri::parse(url.to_string())?;
Tracker::from_url(&url)
}

/// Generate a new Tracker from a parsed URL.
///
/// Will fail if scheme is not "http", "https", "wss" or "udp".
pub fn from_url(url: &Uri<String>) -> Result<Tracker, TrackerError> {
let scheme = match url.scheme().as_str() {
"http" | "https" => TrackerScheme::Http,
"wss" => TrackerScheme::Websocket,
"udp" => TrackerScheme::UDP,
_ => {
return Err(TrackerError::InvalidScheme {
scheme: url.scheme().to_string(),
});
}
};

Ok(Tracker {
scheme,
url: url.clone(),
})
}

/// Turns a centralized Tracker into a wider PeerSource
pub fn to_peer_source(&self) -> PeerSource {
PeerSource::from_tracker(self)
}
}

/// Turn a backend-specific tracker struct into an agnostic [`Tracker`](crate::tracker::Tracker).
pub trait TryIntoTracker {
fn try_into_tracker(&self) -> Result<Tracker, TrackerError>;
Expand Down
Loading