cnx_contrib/widgets/
disk_usage.rs

1use anyhow::Result;
2use byte_unit::{Byte, ByteUnit};
3use cnx::text::{Attributes, Text};
4use cnx::widgets::{Widget, WidgetStream};
5use nix::sys::statvfs::statvfs;
6use std::time::Duration;
7use tokio::time;
8use tokio_stream::wrappers::IntervalStream;
9use tokio_stream::StreamExt;
10
11/// Represent Information about the mounted filesystem
12#[derive(Debug)]
13pub struct DiskInfo {
14    /// Total size of the filesystem
15    pub total: Byte,
16    /// Total used space of the filesystem
17    pub used: Byte,
18    /// Total free space of the filesystem
19    pub free: Byte,
20}
21
22impl DiskInfo {
23    fn new(path: &str) -> Result<Self> {
24        let stat = statvfs(path)?;
25        let total_size = stat.blocks() * stat.fragment_size();
26        let used = (stat.blocks() - stat.blocks_free()) * stat.fragment_size();
27        let available = stat.blocks_available() * stat.fragment_size();
28        let total = byte_unit::Byte::from_bytes(total_size as u128);
29        let used = byte_unit::Byte::from_bytes(used as u128);
30        let free: Byte = byte_unit::Byte::from_bytes(available as u128);
31
32        let disk_info = DiskInfo { total, used, free };
33        Ok(disk_info)
34    }
35}
36
37/// Disk usage widget to show current usage and remaining free space
38/// in the mounted filesystem.
39pub struct DiskUsage {
40    attr: Attributes,
41    path: String,
42    render: Option<Box<dyn Fn(DiskInfo) -> String>>,
43}
44
45impl DiskUsage {
46    /// Creates a new [`DiskUsage`] widget.
47    ///
48    /// Arguments
49    ///
50    /// * `attr` - Represents `Attributes` which controls properties like
51    /// `Font`, foreground and background color etc.
52    ///
53    /// * `path` - Pathname of any file within the mounted filesystem.
54
55    /// * `render` - We use the closure to control the way output is
56    /// displayed in the bar. [`DiskInfo`] represents the details
57    /// about the mounted filesystem.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// # #[macro_use]
63    /// # extern crate cnx;
64    /// #
65    /// # use cnx::*;
66    /// # use cnx::text::*;
67    /// # use cnx_contrib::widgets::disk_usage::*;
68    /// # use anyhow::Result;
69    /// #
70    /// # fn run() -> Result<()> {
71    /// let attr = Attributes {
72    ///     font: Font::new("SourceCodePro 21"),
73    ///     fg_color: Color::white(),
74    ///     bg_color: None,
75    ///     padding: Padding::new(8.0, 8.0, 0.0, 0.0),
76    /// };
77    ///
78    /// let mut cnx = Cnx::new(Position::Top);
79    /// cnx.add_widget(DiskUsage::new(attr, "/home".into(), None));
80    /// # Ok(())
81    /// # }
82    /// # fn main() { run().unwrap(); }
83    /// ```
84    pub fn new(
85        attr: Attributes,
86        path: String,
87        render: Option<Box<dyn Fn(DiskInfo) -> String>>,
88    ) -> Self {
89        Self { attr, render, path }
90    }
91
92    fn tick(&self) -> Result<Vec<Text>> {
93        let disk_info = DiskInfo::new(self.path.as_ref())?;
94        let disk_default_str = format!(
95            "Disk: {}/{}",
96            disk_info.used.get_adjusted_unit(ByteUnit::GiB).format(0),
97            disk_info.total.get_adjusted_unit(ByteUnit::GiB).format(0)
98        );
99
100        let text: String = self
101            .render
102            .as_ref()
103            .map_or(disk_default_str, |disk| (disk)(disk_info));
104        let texts = vec![Text {
105            attr: self.attr.clone(),
106            text,
107            stretch: false,
108            markup: true,
109        }];
110        Ok(texts)
111    }
112}
113
114impl Widget for DiskUsage {
115    fn into_stream(self: Box<Self>) -> Result<WidgetStream> {
116        let one_hour = Duration::from_secs(3600);
117        let interval = time::interval(one_hour);
118        let stream = IntervalStream::new(interval).map(move |_| self.tick());
119
120        Ok(Box::pin(stream))
121    }
122}