1use serde::{Deserialize, Serialize};
8#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Client {
12 #[serde(default)]
13 pub ip: String,
14 #[serde(default)]
15 pub lat: String,
16 #[serde(default)]
17 pub lon: String,
18 #[serde(default)]
19 pub isp: String,
20 #[serde(default)]
21 pub country: String,
22 #[serde(default)]
23 pub isprating: String,
24 #[serde(default)]
25 pub rating: String,
26 #[serde(default)]
27 pub ispdlavg: String,
28 #[serde(default)]
29 pub ispulavg: String,
30 #[serde(default)]
31 pub loggedin: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Server {
36 pub id: u32,
37 pub sponsor: String,
38 pub name: String,
39 pub country: String,
40 pub lat: f64,
41 pub lon: f64,
42 pub url: String,
43 #[serde(default)]
44 pub d: f64,
45 #[serde(default)]
46 pub latency: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Config {
51 pub client: Client,
52 pub ignore_servers: Vec<u32>,
53 pub sizes: Sizes,
54 pub counts: Counts,
55 pub threads: Threads,
56 pub length: Length,
57 pub upload_max: usize,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Sizes {
62 pub upload: Vec<usize>,
63 pub download: Vec<usize>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Counts {
68 pub upload: usize,
69 pub download: usize,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Threads {
74 pub upload: usize,
75 pub download: usize,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Length {
80 pub upload: u64,
81 pub download: u64,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SpeedtestResults {
86 pub download: f64,
87 pub upload: f64,
88 pub ping: f64,
89 pub server: Server,
90 pub timestamp: String,
91 pub bytes_received: u64,
92 pub bytes_sent: u64,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub share: Option<String>,
95 pub client: Client,
96}
97
98impl SpeedtestResults {
99 pub fn new(client: Client, server: Server) -> Self {
100 Self {
101 download: 0.0,
102 upload: 0.0,
103 ping: 0.0,
104 server,
105 timestamp: chrono::Utc::now().to_rfc3339(),
106 bytes_received: 0,
107 bytes_sent: 0,
108 share: None,
109 client,
110 }
111 }
112
113 pub fn to_csv(&self, delimiter: char) -> crate::error::Result<String> {
114 let mut wtr = csv::WriterBuilder::new()
115 .delimiter(delimiter as u8)
116 .from_writer(vec![]);
117
118 wtr.write_record(&[
119 self.server.id.to_string(),
120 self.server.sponsor.clone(),
121 self.server.name.clone(),
122 self.timestamp.clone(),
123 format!("{:.2}", self.server.d),
124 format!("{:.3}", self.ping),
125 format!("{:.2}", self.download),
126 format!("{:.2}", self.upload),
127 self.share.clone().unwrap_or_default(),
128 self.client.ip.clone(),
129 ])?;
130
131 let inner = wtr.into_inner().map_err(|e| {
132 crate::error::SpeedtestError::Csv(csv::Error::from(std::io::Error::new(
133 std::io::ErrorKind::Other,
134 format!("Failed to finalize CSV: {}", e)
135 )))
136 })?;
137 Ok(String::from_utf8_lossy(&inner).to_string())
138 }
139
140 pub fn csv_header(delimiter: char) -> crate::error::Result<String> {
141 let mut wtr = csv::WriterBuilder::new()
142 .delimiter(delimiter as u8)
143 .from_writer(vec![]);
144
145 wtr.write_record(&[
146 "Server ID",
147 "Sponsor",
148 "Server Name",
149 "Timestamp",
150 "Distance",
151 "Ping",
152 "Download",
153 "Upload",
154 "Share",
155 "IP Address",
156 ])?;
157
158 let inner = wtr.into_inner().map_err(|e| {
159 crate::error::SpeedtestError::Csv(csv::Error::from(std::io::Error::new(
160 std::io::ErrorKind::Other,
161 format!("Failed to finalize CSV: {}", e)
162 )))
163 })?;
164 Ok(String::from_utf8_lossy(&inner).to_string())
165 }
166
167 pub fn to_json(&self, pretty: bool) -> crate::error::Result<String> {
168 if pretty {
169 Ok(serde_json::to_string_pretty(self)?)
170 } else {
171 Ok(serde_json::to_string(self)?)
172 }
173 }
174}