From 7d00f0750f50c1c45a71f24f2d04a9584fb358d6 Mon Sep 17 00:00:00 2001 From: yggverse Date: Tue, 8 Jul 2025 01:41:06 +0300 Subject: [PATCH] implement binary info-hash api --- crates/udp/src/config.rs | 10 +++++++ crates/udp/src/swarm.rs | 65 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/crates/udp/src/config.rs b/crates/udp/src/config.rs index 3851b31..46c5e38 100644 --- a/crates/udp/src/config.rs +++ b/crates/udp/src/config.rs @@ -189,6 +189,13 @@ pub struct StatisticsConfig { pub json_info_hash_ipv4_file_path: PathBuf, /// Path to dump JSON info-hash IPv6 to pub json_info_hash_ipv6_file_path: PathBuf, + /// Save statistics as binary to a file + /// * this option is recommended for SSD as compares previous file before overwrite + pub write_bin_to_file: bool, + /// Path to dump binary info-hash IPv4 to + pub bin_info_hash_ipv4_file_path: PathBuf, + /// Path to dump binary info-hash IPv6 to + pub bin_info_hash_ipv6_file_path: PathBuf, /// Run a prometheus endpoint #[cfg(feature = "prometheus")] pub run_prometheus_endpoint: bool, @@ -232,6 +239,9 @@ impl Default for StatisticsConfig { write_json_to_file: false, json_info_hash_ipv4_file_path: "tmp/info_hash_v4.json".into(), json_info_hash_ipv6_file_path: "tmp/info_hash_v6.json".into(), + write_bin_to_file: false, + bin_info_hash_ipv4_file_path: "tmp/info_hash_v4.bin".into(), + bin_info_hash_ipv6_file_path: "tmp/info_hash_v6.bin".into(), #[cfg(feature = "prometheus")] run_prometheus_endpoint: false, #[cfg(feature = "prometheus")] diff --git a/crates/udp/src/swarm.rs b/crates/udp/src/swarm.rs index 1496540..d78a7b2 100644 --- a/crates/udp/src/swarm.rs +++ b/crates/udp/src/swarm.rs @@ -136,14 +136,14 @@ impl TorrentMaps { } if config.statistics.write_json_to_file { + use anyhow::{Context, Result}; fn save_to_file( path: &std::path::PathBuf, info_hashes: &Vec, - ) -> anyhow::Result<()> { - let mut file = - anyhow::Context::with_context(std::fs::File::create(path), || { - format!("File path: {}", path.to_string_lossy()) - })?; + ) -> Result<()> { + let mut file = Context::with_context(std::fs::File::create(path), || { + format!("File path: {}", path.to_string_lossy()) + })?; write!(file, "[")?; if !info_hashes.is_empty() { write!(file, "\"{}\"", info_hashes[0])?; @@ -153,7 +153,7 @@ impl TorrentMaps { } } } - write!(file, "]")?; // @TODO serialize with serde_json? + write!(file, "]")?; Ok(()) } if config.network.ipv4_active() { @@ -171,6 +171,59 @@ impl TorrentMaps { } } } + + if config.statistics.write_bin_to_file { + use anyhow::{Context, Result}; + use std::{fs::File, io::Read, path::PathBuf}; + /// Prevent extra write operations by compare the file content is same + fn is_same(path: &PathBuf, info_hashes: &Vec) -> Result { + if !std::fs::exists(path)? { + return Ok(false); + } + let mut t = 0; + let mut f = File::open(path)?; + loop { + let mut b = vec![0, 20]; + let n = f.read_to_end(&mut b)?; + if n == 0 { + break; + } + if info_hashes.iter().any(|i| i.0 != b[..n]) { + return Ok(false); + } + t += 1 + } + Ok(t == info_hashes.len()) + } + /// Dump `InfoHash` index to file + fn save_to_file(path: &PathBuf, info_hashes: &Vec) -> Result<()> { + if is_same(path, info_hashes)? { + return Ok(()); + } + let mut f = Context::with_context(File::create(path), || { + format!("File path: {}", path.to_string_lossy()) + })?; + for i in info_hashes { + f.write_all(&i.0)?; + f.write_all(b"\n")? + } + Ok(()) + } + if config.network.ipv4_active() { + if let Err(err) = + save_to_file(&config.statistics.bin_info_hash_ipv4_file_path, &ipv4.3) + { + ::log::error!("Couldn't dump IPv4 info-hash table to file: {:#}", err) + } + } + if config.network.ipv6_active() { + if let Err(err) = + save_to_file(&config.statistics.bin_info_hash_ipv6_file_path, &ipv6.3) + { + ::log::error!("Couldn't dump IPv6 info-hash table to file: {:#}", err) + } + } + } } } }