1use std::{
20 collections::HashSet,
21 path::{Path, PathBuf},
22};
23use tinyjson::JsonValue;
24
25use darkfi::{
26 geode::{hash_to_string, ChunkedStorage, MAX_CHUNK_SIZE},
27 rpc::util::json_map,
28 Error, Result,
29};
30
31use crate::FileSelection;
32
33#[derive(Clone, Debug)]
34pub enum ResourceStatus {
35 Downloading,
36 Seeding,
37 Discovering,
38 Incomplete,
39 Verifying,
40}
41
42impl ResourceStatus {
43 pub fn as_str(&self) -> &str {
44 match self {
45 ResourceStatus::Downloading => "downloading",
46 ResourceStatus::Seeding => "seeding",
47 ResourceStatus::Discovering => "discovering",
48 ResourceStatus::Incomplete => "incomplete",
49 ResourceStatus::Verifying => "verifying",
50 }
51 }
52 fn from_str(s: &str) -> Result<Self> {
53 match s {
54 "downloading" => Ok(ResourceStatus::Downloading),
55 "seeding" => Ok(ResourceStatus::Seeding),
56 "discovering" => Ok(ResourceStatus::Discovering),
57 "incomplete" => Ok(ResourceStatus::Incomplete),
58 "verifying" => Ok(ResourceStatus::Verifying),
59 _ => Err(Error::Custom("Invalid resource status".to_string())),
60 }
61 }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub enum ResourceType {
66 Unknown,
67 File,
68 Directory,
69}
70
71impl ResourceType {
72 pub fn as_str(&self) -> &str {
73 match self {
74 ResourceType::Unknown => "unknown",
75 ResourceType::File => "file",
76 ResourceType::Directory => "directory",
77 }
78 }
79 fn from_str(s: &str) -> Result<Self> {
80 match s {
81 "unknown" => Ok(ResourceType::Unknown),
82 "file" => Ok(ResourceType::File),
83 "directory" => Ok(ResourceType::Directory),
84 _ => Err(Error::Custom("Invalid resource type".to_string())),
85 }
86 }
87}
88
89#[derive(Clone, Debug)]
92pub struct Resource {
93 pub hash: blake3::Hash,
95 pub rtype: ResourceType,
97 pub path: PathBuf,
99 pub status: ResourceStatus,
101 pub file_selection: FileSelection,
103
104 pub total_chunks_count: u64,
106 pub target_chunks_count: u64,
108 pub total_chunks_downloaded: u64,
110 pub target_chunks_downloaded: u64,
113
114 pub total_bytes_size: u64,
116 pub target_bytes_size: u64,
118 pub total_bytes_downloaded: u64,
120 pub target_bytes_downloaded: u64,
123
124 pub speeds: Vec<f64>,
126}
127
128impl Resource {
129 pub fn new(
130 hash: blake3::Hash,
131 rtype: ResourceType,
132 path: &Path,
133 status: ResourceStatus,
134 file_selection: FileSelection,
135 ) -> Self {
136 Self {
137 hash,
138 rtype,
139 path: path.to_path_buf(),
140 status,
141 file_selection,
142 total_chunks_count: 0,
143 target_chunks_count: 0,
144 total_chunks_downloaded: 0,
145 target_chunks_downloaded: 0,
146 total_bytes_size: 0,
147 target_bytes_size: 0,
148 total_bytes_downloaded: 0,
149 target_bytes_downloaded: 0,
150 speeds: vec![],
151 }
152 }
153
154 pub fn get_eta(&self) -> u64 {
156 if self.speeds.is_empty() {
157 return 0
158 }
159
160 let remaining_chunks = self.target_chunks_count - self.target_chunks_downloaded;
161 let mean_speed = self.speeds.iter().sum::<f64>() / self.speeds.len() as f64;
162
163 ((remaining_chunks * MAX_CHUNK_SIZE as u64) as f64 / mean_speed) as u64
164 }
165
166 pub fn get_selected_files(&self, chunked: &ChunkedStorage) -> Vec<PathBuf> {
168 match &self.file_selection {
169 FileSelection::Set(files) => files
170 .iter()
171 .map(|file| self.path.join(file))
172 .filter(|abs| chunked.get_files().iter().any(|(f, _)| f == abs))
173 .collect(),
174 FileSelection::All => chunked.get_files().iter().map(|(f, _)| f.clone()).collect(),
175 }
176 }
177
178 pub fn get_selected_chunks(&self, chunked: &ChunkedStorage) -> HashSet<blake3::Hash> {
180 match &self.file_selection {
181 FileSelection::Set(files) => {
182 let mut chunks = HashSet::new();
183 for file in files {
184 chunks.extend(chunked.get_chunks_of_file(&self.path.join(file)));
185 }
186 chunks
187 }
188 FileSelection::All => chunked.iter().cloned().map(|(hash, _)| hash).collect(),
189 }
190 }
191
192 pub fn get_selected_bytes(&self, chunked: &ChunkedStorage, chunk: &[u8]) -> usize {
194 let file_set = if let FileSelection::Set(files) = &self.file_selection {
196 files
197 } else {
198 return chunk.len();
199 };
200
201 let chunk_hash = blake3::hash(chunk);
202 let chunk_index = match chunked.iter().position(|(h, _)| *h == chunk_hash) {
203 Some(index) => index,
204 None => {
205 return 0;
206 }
207 };
208
209 let files = chunked.get_files();
210 let chunk_length = chunk.len();
211 let position = (chunk_index as u64) * (MAX_CHUNK_SIZE as u64);
212 let mut total_selected_bytes = 0;
213
214 let mut file_index = 0;
216 let mut file_start_pos = 0;
217
218 while file_index < files.len() {
219 if file_start_pos + files[file_index].1 > position {
220 break;
221 }
222 file_start_pos += files[file_index].1;
223 file_index += 1;
224 }
225
226 if file_index >= files.len() {
227 return 0;
229 }
230
231 let end_position = position + chunk_length as u64;
233
234 while file_index < files.len() {
236 let (file_path, file_size) = &files[file_index];
237 let file_end_pos = file_start_pos + *file_size;
238
239 if let Ok(rel_file_path) = file_path.strip_prefix(&self.path) {
241 if file_set.contains(rel_file_path) {
242 let overlap_start = position.max(file_start_pos);
244 let overlap_end = end_position.min(file_end_pos);
245
246 if overlap_start < overlap_end {
247 total_selected_bytes += (overlap_end - overlap_start) as usize;
248 }
249 }
250 }
251
252 file_start_pos += *file_size;
254 file_index += 1;
255
256 if file_start_pos >= end_position {
258 break;
259 }
260 }
261
262 total_selected_bytes
263 }
264}
265
266impl From<Resource> for JsonValue {
267 fn from(rs: Resource) -> JsonValue {
268 json_map([
269 ("hash", JsonValue::String(hash_to_string(&rs.hash))),
270 ("type", JsonValue::String(rs.rtype.as_str().to_string())),
271 (
272 "path",
273 JsonValue::String(match rs.path.clone().into_os_string().into_string() {
274 Ok(path) => path,
275 Err(_) => "".to_string(),
276 }),
277 ),
278 ("status", JsonValue::String(rs.status.as_str().to_string())),
279 ("total_chunks_count", JsonValue::Number(rs.total_chunks_count as f64)),
280 ("target_chunks_count", JsonValue::Number(rs.target_chunks_count as f64)),
281 ("total_chunks_downloaded", JsonValue::Number(rs.total_chunks_downloaded as f64)),
282 ("target_chunks_downloaded", JsonValue::Number(rs.target_chunks_downloaded as f64)),
283 ("total_bytes_size", JsonValue::Number(rs.total_bytes_size as f64)),
284 ("target_bytes_size", JsonValue::Number(rs.target_bytes_size as f64)),
285 ("total_bytes_downloaded", JsonValue::Number(rs.total_bytes_downloaded as f64)),
286 ("target_bytes_downloaded", JsonValue::Number(rs.target_bytes_downloaded as f64)),
287 ("speeds", JsonValue::Array(rs.speeds.into_iter().map(JsonValue::Number).collect())),
288 ])
289 }
290}
291
292impl From<JsonValue> for Resource {
293 fn from(value: JsonValue) -> Self {
294 let mut hash_buf = vec![];
295 let _ = bs58::decode(value["hash"].get::<String>().unwrap().as_str()).onto(&mut hash_buf);
296 let mut hash_buf_arr = [0u8; 32];
297 hash_buf_arr.copy_from_slice(&hash_buf);
298 let hash = blake3::Hash::from_bytes(hash_buf_arr);
299
300 let rtype = ResourceType::from_str(value["type"].get::<String>().unwrap()).unwrap();
301 let path = PathBuf::from(value["path"].get::<String>().unwrap());
302 let status = ResourceStatus::from_str(value["status"].get::<String>().unwrap()).unwrap();
303
304 let total_chunks_count = *value["total_chunks_count"].get::<f64>().unwrap() as u64;
305 let target_chunks_count = *value["target_chunks_count"].get::<f64>().unwrap() as u64;
306 let total_chunks_downloaded =
307 *value["total_chunks_downloaded"].get::<f64>().unwrap() as u64;
308 let target_chunks_downloaded =
309 *value["target_chunks_downloaded"].get::<f64>().unwrap() as u64;
310 let total_bytes_size = *value["total_bytes_size"].get::<f64>().unwrap() as u64;
311 let target_bytes_size = *value["target_bytes_size"].get::<f64>().unwrap() as u64;
312 let total_bytes_downloaded = *value["total_bytes_downloaded"].get::<f64>().unwrap() as u64;
313 let target_bytes_downloaded =
314 *value["target_bytes_downloaded"].get::<f64>().unwrap() as u64;
315
316 let speeds = value["speeds"]
317 .get::<Vec<JsonValue>>()
318 .unwrap()
319 .iter()
320 .map(|s| *s.get::<f64>().unwrap())
321 .collect::<Vec<f64>>();
322
323 Resource {
324 hash,
325 rtype,
326 path,
327 status,
328 file_selection: FileSelection::All, total_chunks_count,
330 target_chunks_count,
331 total_chunks_downloaded,
332 target_chunks_downloaded,
333 total_bytes_size,
334 target_bytes_size,
335 total_bytes_downloaded,
336 target_bytes_downloaded,
337 speeds,
338 }
339 }
340}