ferrix_lib/
utils.rs

1/* utils.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Utilities and helpers
22
23use std::{fmt::Display, path::Path};
24
25use anyhow::Result;
26use serde::{Deserialize, Serialize};
27
28#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy)]
29pub enum Size {
30    B(usize),
31    KB(f32),
32    MB(f32),
33    GB(f32),
34    TB(f32),
35    UnknownUnits(usize),
36    #[default]
37    None,
38}
39
40impl Size {
41    fn get_num(&self) -> Option<f32> {
42        match self {
43            Self::B(num) | Self::UnknownUnits(num) => Some(*num as f32),
44            Self::KB(num) | Self::MB(num) | Self::GB(num) | Self::TB(num) => Some(*num),
45            _ => None,
46        }
47    }
48
49    /// base: 2 or 10
50    pub fn round(&self, base: u8) -> Option<Self> {
51        if base != 2 && base != 10 {
52            panic!("Unknown base: {base} (supported values: 2 or 10)")
53        }
54
55        let mut size = *self;
56        let num = size.get_num();
57
58        if let None = num {
59            return None;
60        }
61        let mut num = num.unwrap();
62        let div = match base {
63            2 => 1024.,
64            10 => 1000.,
65            _ => panic!("Unknown base: {base} (supported values: 2 or 10)"), // unreachable branch
66        };
67
68        while num >= div {
69            if let Self::TB(_) = size {
70                break;
71            } else {
72                size = match size {
73                    Size::B(_) => {
74                        num /= div;
75                        Size::KB(num)
76                    }
77                    Size::KB(_) => {
78                        num /= div;
79                        Size::MB(num)
80                    }
81                    Size::MB(_) => {
82                        num /= div;
83                        Size::GB(num)
84                    }
85                    Size::GB(_) => {
86                        num /= div;
87                        Size::TB(num)
88                    }
89                    _ => size,
90                }
91            }
92        }
93        Some(size)
94    }
95
96    pub fn get_bytes10(&self) -> Option<usize> {
97        match self {
98            Self::B(b) => Some(*b),
99            Self::KB(kb) => Some((*kb * 10f32.powi(3)) as usize),
100            Self::MB(mb) => Some((*mb * 10f32.powi(6)) as usize),
101            Self::GB(gb) => Some((*gb * 10f32.powi(9)) as usize),
102            Self::TB(tb) => Some((*tb * 10f32.powi(12)) as usize),
103            _ => None,
104        }
105    }
106
107    pub fn get_bytes2(&self) -> Option<usize> {
108        match self {
109            Self::B(b) => Some(*b),
110            Self::KB(kb) => Some((*kb * 2f32.powi(10)) as usize),
111            Self::MB(mb) => Some((*mb * 2f32.powi(20)) as usize),
112            Self::GB(gb) => Some((*gb * 2f32.powi(30)) as usize),
113            Self::TB(tb) => Some((*tb * 2f32.powi(40)) as usize),
114            _ => None,
115        }
116    }
117}
118
119impl TryFrom<&str> for Size {
120    type Error = anyhow::Error;
121    fn try_from(value: &str) -> Result<Self, Self::Error> {
122        let mut items = value.split_whitespace();
123        Ok(match (items.next(), items.next()) {
124            (Some(num), Some(units)) => {
125                let num = num.parse::<f32>()?;
126                match units.to_lowercase().as_str() {
127                    "kb" | "kbytes" => Self::KB(num),
128                    "mb" | "mbytes" => Self::MB(num),
129                    "gb" | "gbytes" => Self::GB(num),
130                    "tb" | "tbytes" => Self::TB(num),
131                    _ => Self::B(num as usize),
132                }
133            }
134            (Some(num), None) => {
135                let num = num.parse::<usize>()?;
136                Self::UnknownUnits(num)
137            }
138            _ => Self::None,
139        })
140    }
141}
142
143impl Display for Size {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(
146            f,
147            "{}",
148            match self {
149                Size::B(n) => format!("{n} B"),
150                Size::KB(n) => format!("{n:.2} KB"),
151                Size::MB(n) => format!("{n:.2} MB"),
152                Size::GB(n) => format!("{n:.2} GB"),
153                Size::TB(n) => format!("{n:.2} TB"),
154                Size::UnknownUnits(n) => format!("{n} ??"),
155                Size::None => format!("None"),
156            }
157        )
158    }
159}
160
161pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
162    let c = std::fs::read_to_string(path)?.trim().to_string();
163    Ok(c)
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn get_bytes_test() {
172        let s1 = Size::B(1024);
173        assert_eq!(s1.get_bytes10().unwrap_or(0), 1024);
174        let s2 = Size::KB(1.);
175        assert_eq!(s2.get_bytes2().unwrap(), 1024);
176    }
177}