Files
dht/dht-search/src/api/response.rs
T

182 lines
6.1 KiB
Rust

// 负责定义稳定的 HTTP 响应模型和领域对象转换边界
use dht_search::domain::{Availability, Heat, TorrentFile, TorrentRecord};
use serde::Serialize;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize)]
pub(crate) struct StatusResponse {
pub(crate) status: &'static str,
}
#[derive(Debug, Serialize)]
pub(crate) struct ErrorResponse {
pub(crate) error: String,
}
#[derive(Debug, Serialize)]
pub(crate) struct StatsResponse {
pub(crate) nodes: usize,
pub(crate) udp_tx_packets: u64,
pub(crate) find_node_queries: u64,
pub(crate) peer_lookup_queries: u64,
pub(crate) peer_lookup_preferred_succeeded: u64,
pub(crate) peer_lookup_fallbacks: u64,
pub(crate) sample_candidate_queue: usize,
pub(crate) sample_candidate_queue_capacity: usize,
pub(crate) sample_candidates_routed: u64,
pub(crate) sample_candidates_fallback: u64,
pub(crate) sample_queries: u64,
pub(crate) sample_direct_queries: u64,
pub(crate) sample_snapshot_queries: u64,
pub(crate) sample_responses: u64,
pub(crate) sample_direct_responses: u64,
pub(crate) sample_snapshot_responses: u64,
pub(crate) sample_timeouts: u64,
pub(crate) sampled_hashes: u64,
pub(crate) sampled_hashes_filtered: u64,
pub(crate) sampled_hashes_duplicate: u64,
pub(crate) sampled_hashes_dropped: u64,
pub(crate) metadata_peer_attempts: u64,
pub(crate) metadata_in_flight: usize,
pub(crate) metadata_ok: u64,
pub(crate) metadata_ok_from_announce: u64,
pub(crate) metadata_ok_from_sample_direct: u64,
pub(crate) metadata_ok_from_sample_snapshot: u64,
pub(crate) metadata_ok_from_active_lookup: u64,
pub(crate) metadata_failed: u64,
pub(crate) metadata_filtered: u64,
pub(crate) metadata_filtered_too_large: u64,
pub(crate) metadata_filtered_invalid_info_hash: u64,
pub(crate) metadata_filtered_empty_name: u64,
pub(crate) metadata_filtered_name_too_long: u64,
pub(crate) metadata_filtered_invalid_name: u64,
pub(crate) metadata_filtered_empty_file_list: u64,
pub(crate) metadata_filtered_too_many_files: u64,
pub(crate) metadata_filtered_empty_path: u64,
pub(crate) metadata_filtered_path_too_long: u64,
pub(crate) metadata_filtered_path_too_deep: u64,
pub(crate) metadata_filtered_invalid_path: u64,
pub(crate) metadata_filtered_size_overflow: u64,
pub(crate) metadata_filtered_total_size_mismatch: u64,
pub(crate) persistence_accepted: u64,
pub(crate) persistence_inserted: u64,
pub(crate) persistence_updated: u64,
pub(crate) persistence_rejected_full: u64,
pub(crate) persistence_queue: usize,
pub(crate) indexed_documents: u64,
pub(crate) verification_queue: u64,
pub(crate) verification_accepted: u64,
pub(crate) verification_deduplicated: u64,
pub(crate) verification_rejected_full: u64,
pub(crate) verification_started: u64,
pub(crate) verification_succeeded: u64,
pub(crate) verification_failed: u64,
pub(crate) verification_peers_discovered: u64,
pub(crate) verification_handshakes_succeeded: u64,
}
#[derive(Debug, Serialize)]
pub(crate) struct TorrentResponse {
pub(crate) info_hash: String,
pub(crate) magnet_link: String,
pub(crate) name: String,
pub(crate) total_size: u64,
pub(crate) file_count: usize,
pub(crate) file_offset: usize,
pub(crate) file_limit: usize,
pub(crate) files: Vec<TorrentFile>,
pub(crate) piece_length: u64,
pub(crate) content_key: String,
pub(crate) first_seen: u64,
pub(crate) last_seen: u64,
pub(crate) seen_count: u64,
pub(crate) heat: Heat,
pub(crate) availability: Availability,
}
#[derive(Debug, Serialize)]
pub(crate) struct ContentVariantsResponse {
pub(crate) content_key: String,
pub(crate) total: u64,
pub(crate) offset: usize,
pub(crate) limit: usize,
pub(crate) variants: Vec<TorrentVariantResponse>,
}
#[derive(Debug, Serialize)]
pub(crate) struct TorrentVariantResponse {
pub(crate) info_hash: String,
pub(crate) magnet_link: String,
pub(crate) name: String,
pub(crate) total_size: u64,
pub(crate) file_count: usize,
pub(crate) first_seen: u64,
pub(crate) last_seen: u64,
pub(crate) seen_count: u64,
pub(crate) heat: Heat,
pub(crate) availability: Availability,
}
impl From<TorrentRecord> for TorrentVariantResponse {
fn from(record: TorrentRecord) -> Self {
let info_hash = record.info_hash.to_string();
let heat = record.heat(unix_timestamp());
Self {
magnet_link: format!("magnet:?xt=urn:btih:{info_hash}"),
info_hash,
name: record.name,
total_size: record.total_size,
file_count: record.files.len(),
first_seen: record.first_seen,
last_seen: record.last_seen,
seen_count: record.seen_count,
heat,
availability: record.availability,
}
}
}
impl TorrentResponse {
pub(crate) fn from_record(
record: TorrentRecord,
file_offset: usize,
file_limit: usize,
) -> Self {
let info_hash = record.info_hash.to_string();
let heat = record.heat(unix_timestamp());
let file_count = record.files.len();
let file_offset = file_offset.min(file_count);
let files = record
.files
.into_iter()
.skip(file_offset)
.take(file_limit)
.collect();
Self {
magnet_link: format!("magnet:?xt=urn:btih:{info_hash}"),
info_hash,
name: record.name,
total_size: record.total_size,
file_count,
file_offset,
file_limit,
files,
piece_length: record.piece_length,
content_key: hex::encode(record.content_key),
first_seen: record.first_seen,
last_seen: record.last_seen,
seen_count: record.seen_count,
heat,
availability: record.availability,
}
}
}
fn unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}