]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-tools/src/format.rs
add pbs-tools subcrate
[proxmox-backup.git] / pbs-tools / src / format.rs
1 use anyhow::{Error};
2 use serde_json::Value;
3
4 pub fn strip_server_file_extension(name: &str) -> String {
5 if name.ends_with(".didx") || name.ends_with(".fidx") || name.ends_with(".blob") {
6 name[..name.len()-5].to_owned()
7 } else {
8 name.to_owned() // should not happen
9 }
10 }
11
12 pub fn render_backup_file_list(files: &[String]) -> String {
13 let mut files: Vec<String> = files.iter()
14 .map(|v| strip_server_file_extension(&v))
15 .collect();
16
17 files.sort();
18
19 crate::str::join(&files, ' ')
20 }
21
22 pub fn render_epoch(value: &Value, _record: &Value) -> Result<String, Error> {
23 if value.is_null() { return Ok(String::new()); }
24 let text = match value.as_i64() {
25 Some(epoch) => {
26 if let Ok(epoch_string) = proxmox::tools::time::strftime_local("%c", epoch as i64) {
27 epoch_string
28 } else {
29 epoch.to_string()
30 }
31 },
32 None => {
33 value.to_string()
34 }
35 };
36 Ok(text)
37 }
38
39 pub fn render_task_status(value: &Value, record: &Value) -> Result<String, Error> {
40 if record["endtime"].is_null() {
41 Ok(value.as_str().unwrap_or("running").to_string())
42 } else {
43 Ok(value.as_str().unwrap_or("unknown").to_string())
44 }
45 }
46
47 pub fn render_bool_with_default_true(value: &Value, _record: &Value) -> Result<String, Error> {
48 let value = value.as_bool().unwrap_or(true);
49 Ok((if value { "1" } else { "0" }).to_string())
50 }
51
52 pub fn render_bytes_human_readable(value: &Value, _record: &Value) -> Result<String, Error> {
53 if value.is_null() { return Ok(String::new()); }
54 let text = match value.as_u64() {
55 Some(bytes) => {
56 HumanByte::from(bytes).to_string()
57 }
58 None => {
59 value.to_string()
60 }
61 };
62 Ok(text)
63 }
64
65 pub struct HumanByte {
66 b: usize,
67 }
68 impl std::fmt::Display for HumanByte {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 if self.b < 1024 {
71 return write!(f, "{} B", self.b);
72 }
73 let kb: f64 = self.b as f64 / 1024.0;
74 if kb < 1024.0 {
75 return write!(f, "{:.2} KiB", kb);
76 }
77 let mb: f64 = kb / 1024.0;
78 if mb < 1024.0 {
79 return write!(f, "{:.2} MiB", mb);
80 }
81 let gb: f64 = mb / 1024.0;
82 if gb < 1024.0 {
83 return write!(f, "{:.2} GiB", gb);
84 }
85 let tb: f64 = gb / 1024.0;
86 if tb < 1024.0 {
87 return write!(f, "{:.2} TiB", tb);
88 }
89 let pb: f64 = tb / 1024.0;
90 return write!(f, "{:.2} PiB", pb);
91 }
92 }
93 impl From<usize> for HumanByte {
94 fn from(v: usize) -> Self {
95 HumanByte { b: v }
96 }
97 }
98 impl From<u64> for HumanByte {
99 fn from(v: u64) -> Self {
100 HumanByte { b: v as usize }
101 }
102 }
103
104 pub fn as_fingerprint(bytes: &[u8]) -> String {
105 proxmox::tools::digest_to_hex(bytes)
106 .as_bytes()
107 .chunks(2)
108 .map(|v| std::str::from_utf8(v).unwrap())
109 .collect::<Vec<&str>>().join(":")
110 }
111
112 pub mod bytes_as_fingerprint {
113 use serde::{Deserialize, Serializer, Deserializer};
114
115 pub fn serialize<S>(
116 bytes: &[u8; 32],
117 serializer: S,
118 ) -> Result<S::Ok, S::Error>
119 where
120 S: Serializer,
121 {
122 let s = super::as_fingerprint(bytes);
123 serializer.serialize_str(&s)
124 }
125
126 pub fn deserialize<'de, D>(
127 deserializer: D,
128 ) -> Result<[u8; 32], D::Error>
129 where
130 D: Deserializer<'de>,
131 {
132 let mut s = String::deserialize(deserializer)?;
133 s.retain(|c| c != ':');
134 proxmox::tools::hex_to_digest(&s).map_err(serde::de::Error::custom)
135 }
136 }
137
138 #[test]
139 fn correct_byte_convert() {
140 fn convert(b: usize) -> String {
141 HumanByte::from(b).to_string()
142 }
143 assert_eq!(convert(1023), "1023 B");
144 assert_eq!(convert(1<<10), "1.00 KiB");
145 assert_eq!(convert(1<<20), "1.00 MiB");
146 assert_eq!(convert((1<<30) + 103 * (1<<20)), "1.10 GiB");
147 assert_eq!(convert((2<<50) + 500 * (1<<40)), "2.49 PiB");
148 }