1use std::io::Write;
20
21use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
22
23use fud::resource::{ResourceStatus, ResourceType};
24
25const UNITS: [&str; 7] = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
26
27pub fn status_to_colorspec(status: &ResourceStatus) -> ColorSpec {
28 ColorSpec::new()
29 .set_fg(match status {
30 ResourceStatus::Downloading => Some(Color::Blue),
31 ResourceStatus::Seeding => Some(Color::Green),
32 ResourceStatus::Discovering => Some(Color::Magenta),
33 ResourceStatus::Incomplete => Some(Color::Red),
34 ResourceStatus::Verifying => Some(Color::Yellow),
35 })
36 .set_bold(true)
37 .clone()
38}
39
40pub fn type_to_colorspec(rtype: &ResourceType) -> ColorSpec {
41 ColorSpec::new()
42 .set_fg(match rtype {
43 ResourceType::File => Some(Color::Blue),
44 ResourceType::Directory => Some(Color::Magenta),
45 ResourceType::Unknown => None,
46 })
47 .set_bold(true)
48 .clone()
49}
50
51pub fn format_bytes(bytes: u64) -> String {
52 let mut size = bytes as f64;
53 let mut unit_index = 0;
54
55 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
56 size /= 1024.0;
57 unit_index += 1;
58 }
59
60 format!("{size:.1} {}", UNITS[unit_index])
61}
62
63pub fn format_progress_bytes(current: u64, total: u64) -> String {
64 let mut total = total as f64;
65 let mut unit_index = 0;
66
67 while total >= 1024.0 && unit_index < UNITS.len() - 1 {
68 total /= 1024.0;
69 unit_index += 1;
70 }
71
72 let current = (current as f64) / 1024_f64.powi(unit_index as i32);
73
74 format!("{current:.1}/{total:.1} {}", UNITS[unit_index])
75}
76
77pub fn format_duration(seconds: u64) -> String {
82 if seconds == 0 {
83 return "0s".to_string();
84 }
85
86 let units = [
87 (86400, "d"), (3600, "h"), (60, "m"), (1, "s"), ];
92
93 for (i, (unit_seconds, unit_symbol)) in units.iter().enumerate() {
94 if seconds >= *unit_seconds {
95 let first = seconds / unit_seconds;
96 let remaining = seconds % unit_seconds;
97
98 if remaining > 0 && i < units.len() - 1 {
99 let (next_unit_seconds, next_unit_symbol) = units[i + 1];
100 let second = remaining / next_unit_seconds;
101 return format!("{first}{unit_symbol}{second}{next_unit_symbol}");
102 }
103
104 return format!("{first}{unit_symbol}");
105 }
106 }
107
108 "0s".to_string()
109}
110
111#[derive(Debug)]
113pub struct TreeNode<K> {
114 pub key: K,
115 pub value: Option<String>,
116 pub color: Option<ColorSpec>,
117 pub children: Vec<TreeNode<K>>,
118}
119impl<K> TreeNode<K> {
120 pub fn key(key: K) -> Self {
122 Self { key, value: None, color: None, children: vec![] }
123 }
124 pub fn kv(key: K, value: String) -> Self {
126 Self { key, value: Some(value), color: None, children: vec![] }
127 }
128 pub fn kvc(key: K, value: String, color: ColorSpec) -> Self {
130 Self { key, value: Some(value), color: Some(color), children: vec![] }
131 }
132}
133
134pub fn print_tree<K: AsRef<str> + std::fmt::Display>(root: &str, items: &[TreeNode<K>]) {
135 fn print_node<K: AsRef<str> + std::fmt::Display>(
136 node: &TreeNode<K>,
137 is_last: bool,
138 prefix: &str,
139 ) {
140 let mut stdout = StandardStream::stdout(ColorChoice::Auto);
141
142 write!(&mut stdout, "{}{} {}", prefix, if is_last { "└─" } else { "├─" }, node.key)
143 .unwrap();
144
145 if let Some(value) = &node.value {
146 write!(&mut stdout, ": ").unwrap();
147 if let Some(spec) = &node.color {
148 stdout.set_color(spec).unwrap();
149 }
150 write!(&mut stdout, "{value}").unwrap();
151 stdout.reset().unwrap();
152 }
153
154 writeln!(&mut stdout).unwrap();
155
156 let new_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
157
158 for (i, child) in node.children.iter().enumerate() {
159 print_node(child, i == node.children.len() - 1, &new_prefix);
160 }
161 }
162
163 let mut stdout = StandardStream::stdout(ColorChoice::Auto);
164 stdout.set_color(ColorSpec::new().set_bold(true)).unwrap();
165 writeln!(&mut stdout, "{root}").unwrap();
166 stdout.reset().unwrap();
167
168 for (i, item) in items.iter().enumerate() {
169 print_node(item, i == items.len() - 1, "");
170 }
171}
172
173macro_rules! optional_value {
174 ($value:expr) => {
175 match $value {
176 0 => "?".to_string(),
177 x => x.to_string(),
178 }
179 };
180 ($value:expr, $formatter:expr) => {
181 match $value {
182 0 => "?".to_string(),
183 x => $formatter(x),
184 }
185 };
186}
187pub(crate) use optional_value;