add detailed peer listing in webserver
This commit is contained in:
parent
e86e2c367f
commit
ad369d1d22
|
@ -210,11 +210,13 @@ impl UDPTracker {
|
|||
let client_addr = SocketAddr::new(remote_addr.ip(), packet.port);
|
||||
let info_hash = packet.info_hash.into();
|
||||
|
||||
let peer_id: &tracker::PeerId = tracker::PeerId::from_array(&packet.peer_id);
|
||||
|
||||
match self
|
||||
.tracker
|
||||
.update_torrent_and_get_stats(
|
||||
&info_hash,
|
||||
&packet.peer_id,
|
||||
peer_id,
|
||||
&client_addr,
|
||||
packet.uploaded,
|
||||
packet.downloaded,
|
||||
|
|
120
src/tracker.rs
120
src/tracker.rs
|
@ -25,16 +25,21 @@ pub enum TrackerMode {
|
|||
PrivateMode,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TorrentPeer {
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct TorrentPeer {
|
||||
ip: std::net::SocketAddr,
|
||||
uploaded: u64,
|
||||
downloaded: u64,
|
||||
left: u64,
|
||||
event: Events,
|
||||
#[serde(serialize_with = "ser_instant")]
|
||||
updated: std::time::Instant,
|
||||
}
|
||||
|
||||
fn ser_instant<S: serde::Serializer>(inst: &std::time::Instant, ser: S) -> Result<S::Ok, S::Error> {
|
||||
ser.serialize_u64(inst.elapsed().as_millis() as u64)
|
||||
}
|
||||
|
||||
#[derive(Ord, PartialEq, Eq, Clone)]
|
||||
pub struct InfoHash {
|
||||
info_hash: [u8; 20],
|
||||
|
@ -128,7 +133,112 @@ impl<'de> serde::de::Deserialize<'de> for InfoHash {
|
|||
}
|
||||
}
|
||||
|
||||
pub type PeerId = [u8; 20];
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq)]
|
||||
pub struct PeerId([u8; 20]);
|
||||
impl PeerId {
|
||||
pub fn from_array(v: &[u8; 20]) -> &PeerId {
|
||||
unsafe {
|
||||
// This is safe since PeerId's repr is transparent and content's are identical. PeerId == [0u8; 20]
|
||||
core::mem::transmute(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_client_name(&self) -> Option<&'static str> {
|
||||
if self.0[0] == b'M' {
|
||||
return Some("BitTorrent");
|
||||
}
|
||||
if self.0[0] == b'-' {
|
||||
let name = match &self.0[1..3] {
|
||||
b"AG" => "Ares",
|
||||
b"A~" => "Ares",
|
||||
b"AR" => "Arctic",
|
||||
b"AV" => "Avicora",
|
||||
b"AX" => "BitPump",
|
||||
b"AZ" => "Azureus",
|
||||
b"BB" => "BitBuddy",
|
||||
b"BC" => "BitComet",
|
||||
b"BF" => "Bitflu",
|
||||
b"BG" => "BTG (uses Rasterbar libtorrent)",
|
||||
b"BR" => "BitRocket",
|
||||
b"BS" => "BTSlave",
|
||||
b"BX" => "~Bittorrent X",
|
||||
b"CD" => "Enhanced CTorrent",
|
||||
b"CT" => "CTorrent",
|
||||
b"DE" => "DelugeTorrent",
|
||||
b"DP" => "Propagate Data Client",
|
||||
b"EB" => "EBit",
|
||||
b"ES" => "electric sheep",
|
||||
b"FT" => "FoxTorrent",
|
||||
b"FW" => "FrostWire",
|
||||
b"FX" => "Freebox BitTorrent",
|
||||
b"GS" => "GSTorrent",
|
||||
b"HL" => "Halite",
|
||||
b"HN" => "Hydranode",
|
||||
b"KG" => "KGet",
|
||||
b"KT" => "KTorrent",
|
||||
b"LH" => "LH-ABC",
|
||||
b"LP" => "Lphant",
|
||||
b"LT" => "libtorrent",
|
||||
b"lt" => "libTorrent",
|
||||
b"LW" => "LimeWire",
|
||||
b"MO" => "MonoTorrent",
|
||||
b"MP" => "MooPolice",
|
||||
b"MR" => "Miro",
|
||||
b"MT" => "MoonlightTorrent",
|
||||
b"NX" => "Net Transport",
|
||||
b"PD" => "Pando",
|
||||
b"qB" => "qBittorrent",
|
||||
b"QD" => "QQDownload",
|
||||
b"QT" => "Qt 4 Torrent example",
|
||||
b"RT" => "Retriever",
|
||||
b"S~" => "Shareaza alpha/beta",
|
||||
b"SB" => "~Swiftbit",
|
||||
b"SS" => "SwarmScope",
|
||||
b"ST" => "SymTorrent",
|
||||
b"st" => "sharktorrent",
|
||||
b"SZ" => "Shareaza",
|
||||
b"TN" => "TorrentDotNET",
|
||||
b"TR" => "Transmission",
|
||||
b"TS" => "Torrentstorm",
|
||||
b"TT" => "TuoTu",
|
||||
b"UL" => "uLeecher!",
|
||||
b"UT" => "µTorrent",
|
||||
b"UW" => "µTorrent Web",
|
||||
b"VG" => "Vagaa",
|
||||
b"WD" => "WebTorrent Desktop",
|
||||
b"WT" => "BitLet",
|
||||
b"WW" => "WebTorrent",
|
||||
b"WY" => "FireTorrent",
|
||||
b"XL" => "Xunlei",
|
||||
b"XT" => "XanTorrent",
|
||||
b"XX" => "Xtorrent",
|
||||
b"ZT" => "ZipTorrent",
|
||||
_ => return None,
|
||||
};
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Serialize for PeerId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: serde::Serializer {
|
||||
let mut tmp = [0u8; 40];
|
||||
binascii::bin2hex(&self.0, &mut tmp).unwrap();
|
||||
let id = std::str::from_utf8(&tmp).ok();
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PeerIdInfo<'a> {
|
||||
id: Option<&'a str>,
|
||||
client: Option<&'a str>,
|
||||
}
|
||||
|
||||
let obj = PeerIdInfo { id, client: self.get_client_name() };
|
||||
obj.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct TorrentEntry {
|
||||
|
@ -208,6 +318,10 @@ impl TorrentEntry {
|
|||
list
|
||||
}
|
||||
|
||||
pub fn get_peers_iter(&self) -> impl Iterator<Item=(&PeerId, &TorrentPeer)> {
|
||||
self.peers.iter()
|
||||
}
|
||||
|
||||
pub fn get_stats(&self) -> (u32, u32, u32) {
|
||||
let leechers = (self.peers.len() as u32) - self.seeders;
|
||||
(self.seeders, self.completed, leechers)
|
||||
|
|
|
@ -31,6 +31,11 @@ struct TorrentEntry<'a> {
|
|||
info_hash: &'a InfoHash,
|
||||
#[serde(flatten)]
|
||||
data: &'a crate::tracker::TorrentEntry,
|
||||
seeders: u32,
|
||||
leechers: u32,
|
||||
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
peers: Option<Vec<(crate::tracker::PeerId, crate::tracker::TorrentPeer)>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -101,7 +106,10 @@ pub fn build_server(
|
|||
let db = tracker.get_database().await;
|
||||
let results: Vec<_> = db
|
||||
.iter()
|
||||
.map(|(k, v)| TorrentEntry { info_hash: k, data: v })
|
||||
.map(|(k, v)| {
|
||||
let (seeders, _, leechers) = v.get_stats();
|
||||
TorrentEntry { info_hash: k, data: v, seeders, leechers, peers: None }
|
||||
})
|
||||
.skip(offset as usize)
|
||||
.take(limit as usize)
|
||||
.collect();
|
||||
|
@ -113,7 +121,7 @@ pub fn build_server(
|
|||
let t2 = tracker.clone();
|
||||
// view_torrent_info -> GET /t/:infohash HTTP/*
|
||||
let view_torrent_info = filters::method::get()
|
||||
.and(filters::path::param())
|
||||
.and(filters::path::param()).and(filters::path::end())
|
||||
.map(move |info_hash: InfoHash| {
|
||||
let tracker = t2.clone();
|
||||
(info_hash, tracker)
|
||||
|
@ -125,10 +133,22 @@ pub fn build_server(
|
|||
Some(v) => v,
|
||||
None => return Err(warp::reject::reject()),
|
||||
};
|
||||
let (seeders, _, leechers) = info.get_stats();
|
||||
|
||||
let peers: Vec<_> = info
|
||||
.get_peers_iter()
|
||||
.take(1000)
|
||||
.map(|(peer_id, peer_info)| {
|
||||
(peer_id.clone(), peer_info.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(reply::json(&TorrentEntry {
|
||||
info_hash: &info_hash,
|
||||
data: info,
|
||||
seeders,
|
||||
leechers,
|
||||
peers: Some(peers),
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
@ -136,7 +156,7 @@ pub fn build_server(
|
|||
// DELETE /t/:info_hash
|
||||
let t3 = tracker.clone();
|
||||
let delete_torrent = filters::method::delete()
|
||||
.and(filters::path::param())
|
||||
.and(filters::path::param()).and(filters::path::end())
|
||||
.map(move |info_hash: InfoHash| {
|
||||
let tracker = t3.clone();
|
||||
(info_hash, tracker)
|
||||
|
@ -160,7 +180,7 @@ pub fn build_server(
|
|||
// add_torrent/alter: POST /t/:info_hash
|
||||
// (optional) BODY: json: {"is_flagged": boolean}
|
||||
let change_torrent = filters::method::post()
|
||||
.and(filters::path::param())
|
||||
.and(filters::path::param()).and(filters::path::end())
|
||||
.and(filters::body::content_length_limit(4096))
|
||||
.and(filters::body::json())
|
||||
.map(move |info_hash: InfoHash, body: Option<TorrentFlag>| {
|
||||
|
|
Loading…
Reference in a new issue