1use std::{
2 collections::BTreeMap,
3 path::PathBuf,
4 time::{SystemTime, UNIX_EPOCH},
5};
6
7use iroh_blobs::Hash;
8use serde::Serialize;
9
10use crate::fs::FileEntry;
11
12#[derive(Clone, Debug, Serialize)]
14#[non_exhaustive]
15pub struct StatOutput {
16 pub path: PathBuf,
17 pub size: u64,
18 pub modified: SystemTime,
19 pub hash: Option<Hash>,
20 pub available: bool,
21 pub peers: usize,
22 pub version_vector: BTreeMap<String, u64>,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
27#[non_exhaustive]
28pub enum StatFormat {
29 Human,
30 Terse,
31 Custom(String),
32}
33
34impl StatOutput {
35 #[must_use]
36 pub fn from_entry(entry: &FileEntry) -> Self {
37 Self {
38 path: entry.path.clone(),
39 size: entry.size,
40 modified: entry.modified,
41 hash: Some(entry.hash.into()),
42 available: true,
43 peers: 0,
44 version_vector: BTreeMap::new(),
45 }
46 }
47
48 #[must_use]
49 pub fn from_blob(entry: &FileEntry, hash: Hash, available: bool, peers: usize) -> Self {
50 Self {
51 hash: Some(hash),
52 available,
53 peers,
54 ..Self::from_entry(entry)
55 }
56 }
57
58 #[must_use]
59 pub const fn is_available(&self) -> bool {
60 self.available
61 }
62
63 #[must_use]
64 pub const fn blocks(&self) -> u64 {
65 self.size.saturating_add(511) >> 9
66 }
67
68 #[must_use]
69 pub fn display(&self, format: StatFormat) -> String {
70 match format {
71 StatFormat::Human => format!(
72 "Path: {}\nSize: {}\nBlocks: {}\nHash: {}\nAvailable: {}\nPeers: {}",
73 self.path.display(),
74 self.size,
75 self.blocks(),
76 self.hash.map_or_else(|| "unavailable".to_owned(), |hash| hash.to_hex()),
77 self.available,
78 self.peers
79 ),
80 StatFormat::Terse => format!(
81 "{}|{}|{}|{}|{}",
82 self.path.display(),
83 self.size,
84 self.blocks(),
85 self.hash.map_or_else(|| "-".to_owned(), |hash| hash.to_hex()),
86 self.peers
87 ),
88 StatFormat::Custom(template) => template
89 .replace("%n", &self.path.display().to_string())
90 .replace("%s", &self.size.to_string())
91 .replace("%b", &self.blocks().to_string())
92 .replace("%h", &self.hash.map_or_else(|| "-".to_owned(), |hash| hash.to_hex()))
93 .replace("%y", &self.modified_seconds())
94 .replace("%a", &self.available.to_string())
95 .replace("%p", &self.peers.to_string()),
96 }
97 }
98
99 fn modified_seconds(&self) -> String {
100 self.modified
101 .duration_since(UNIX_EPOCH)
102 .map_or_else(|_| "0".to_owned(), |duration| duration.as_secs().to_string())
103 }
104}