gosh_dl/protocol/
types.rs

1//! Core protocol types
2//!
3//! Fundamental types used throughout the protocol.
4
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8/// Unique identifier for a download
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct DownloadId(Uuid);
11
12impl DownloadId {
13    /// Create a new random download ID
14    pub fn new() -> Self {
15        Self(Uuid::new_v4())
16    }
17
18    /// Create from an existing UUID
19    pub fn from_uuid(uuid: Uuid) -> Self {
20        Self(uuid)
21    }
22
23    /// Get the underlying UUID
24    pub fn as_uuid(&self) -> &Uuid {
25        &self.0
26    }
27
28    /// Generate an aria2-compatible GID (16-char hex string)
29    /// This is for backwards compatibility with existing frontend
30    pub fn to_gid(&self) -> String {
31        hex::encode(&self.0.as_bytes()[0..8])
32    }
33
34    /// Try to parse from a GID string
35    pub fn from_gid(gid: &str) -> Option<Self> {
36        if gid.len() != 16 {
37            return None;
38        }
39        let bytes = hex::decode(gid).ok()?;
40        if bytes.len() != 8 {
41            return None;
42        }
43        // Create a UUID with the GID bytes + zeros
44        let mut uuid_bytes = [0u8; 16];
45        uuid_bytes[0..8].copy_from_slice(&bytes);
46        Some(Self(Uuid::from_bytes(uuid_bytes)))
47    }
48}
49
50impl Default for DownloadId {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl std::fmt::Display for DownloadId {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "{}", self.to_gid())
59    }
60}
61
62/// Type of download
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "lowercase")]
65pub enum DownloadKind {
66    /// HTTP/HTTPS download
67    Http,
68    /// BitTorrent download from .torrent file
69    Torrent,
70    /// BitTorrent download from magnet URI
71    Magnet,
72}
73
74/// Current state of a download
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(tag = "state", rename_all = "lowercase")]
77pub enum DownloadState {
78    /// Waiting in queue
79    Queued,
80    /// Connecting to server/peers
81    Connecting,
82    /// Actively downloading
83    Downloading,
84    /// Seeding (torrent only)
85    Seeding,
86    /// Paused by user
87    Paused,
88    /// Successfully completed
89    Completed,
90    /// Failed with error
91    Error {
92        kind: String,
93        message: String,
94        retryable: bool,
95    },
96}
97
98impl DownloadState {
99    /// Check if download is active (downloading or seeding)
100    pub fn is_active(&self) -> bool {
101        matches!(self, Self::Downloading | Self::Seeding | Self::Connecting)
102    }
103
104    /// Check if download is finished (completed or error)
105    pub fn is_finished(&self) -> bool {
106        matches!(self, Self::Completed | Self::Error { .. })
107    }
108
109    /// Convert to aria2-compatible status string
110    pub fn to_aria2_status(&self) -> &'static str {
111        match self {
112            Self::Queued => "waiting",
113            Self::Connecting => "active",
114            Self::Downloading => "active",
115            Self::Seeding => "active",
116            Self::Paused => "paused",
117            Self::Completed => "complete",
118            Self::Error { .. } => "error",
119        }
120    }
121}
122
123/// Progress information for a download
124#[derive(Debug, Clone, Default, Serialize, Deserialize)]
125pub struct DownloadProgress {
126    /// Total size in bytes (may be unknown initially)
127    pub total_size: Option<u64>,
128    /// Bytes downloaded so far
129    pub completed_size: u64,
130    /// Current download speed in bytes/sec
131    pub download_speed: u64,
132    /// Current upload speed in bytes/sec (torrent only)
133    pub upload_speed: u64,
134    /// Number of active connections
135    pub connections: u32,
136    /// Number of seeders (torrent only)
137    pub seeders: u32,
138    /// Number of peers (torrent only)
139    pub peers: u32,
140    /// Estimated time remaining in seconds
141    pub eta_seconds: Option<u64>,
142}
143
144impl DownloadProgress {
145    /// Calculate progress percentage (0.0 - 100.0)
146    pub fn percentage(&self) -> f64 {
147        match self.total_size {
148            Some(total) if total > 0 => (self.completed_size as f64 / total as f64) * 100.0,
149            _ => 0.0,
150        }
151    }
152}
153
154// Helper for hex encoding (used by DownloadId)
155mod hex {
156    pub fn encode(bytes: &[u8]) -> String {
157        bytes.iter().map(|b| format!("{:02x}", b)).collect()
158    }
159
160    pub fn decode(s: &str) -> Result<Vec<u8>, ()> {
161        if !s.len().is_multiple_of(2) {
162            return Err(());
163        }
164        (0..s.len())
165            .step_by(2)
166            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|_| ()))
167            .collect()
168    }
169}