feat(fs): New filesystem module
Module that displays details about mounted filesystems, #84 Closes #153
This commit is contained in:
parent
ed5b7a508a
commit
9a0df75a91
@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
set(MODULES_LEFT "bspwm i3 mpd")
|
||||
set(MODULES_CENTER "")
|
||||
set(MODULES_CENTER "filesystem")
|
||||
set(MODULES_RIGHT "backlight volume memory cpu wlan eth battery temperature date powermenu")
|
||||
|
||||
# Strip disabled modules {{{
|
||||
|
@ -41,7 +41,7 @@ font-1 = unifont:size=6;-2
|
||||
font-2 = siji:pixelsize=10;0
|
||||
|
||||
modules-left = bspwm i3 mpd
|
||||
modules-center =
|
||||
modules-center = filesystem
|
||||
modules-right = backlight volume memory cpu wlan eth battery temperature date powermenu
|
||||
|
||||
tray-position = right
|
||||
@ -52,6 +52,24 @@ tray-padding = 4
|
||||
;wm-restack = bspwm
|
||||
|
||||
|
||||
[module/filesystem]
|
||||
type = internal/fs
|
||||
interval = 25
|
||||
|
||||
disk-0 = /
|
||||
disk-1 = /home
|
||||
disk-2 = /invalid/mountpoint
|
||||
|
||||
;fixed-values = true
|
||||
;spacing = 4
|
||||
|
||||
label-mounted = %mountpoint%: %percentage_free%
|
||||
|
||||
label-unmounted = %mountpoint%: not mounted
|
||||
label-unmounted-foreground = #55
|
||||
|
||||
|
||||
|
||||
[module/bspwm]
|
||||
type = internal/bspwm
|
||||
ws-icon-default = x
|
||||
|
@ -52,6 +52,24 @@ tray-padding = 4
|
||||
;wm-restack = bspwm
|
||||
|
||||
|
||||
[module/filesystem]
|
||||
type = internal/fs
|
||||
interval = 25
|
||||
|
||||
disk-0 = /
|
||||
disk-1 = /home
|
||||
disk-2 = /invalid/mountpoint
|
||||
|
||||
;fixed-values = true
|
||||
;spacing = 4
|
||||
|
||||
label-mounted = %mountpoint%: %percentage_free%
|
||||
|
||||
label-unmounted = %mountpoint%: not mounted
|
||||
label-unmounted-foreground = #55
|
||||
|
||||
|
||||
|
||||
[module/bspwm]
|
||||
type = internal/bspwm
|
||||
ws-icon-default = x
|
||||
|
77
include/modules/fs.hpp
Normal file
77
include/modules/fs.hpp
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "components/config.hpp"
|
||||
#include "config.hpp"
|
||||
#include "drawtypes/label.hpp"
|
||||
#include "drawtypes/progressbar.hpp"
|
||||
#include "drawtypes/ramp.hpp"
|
||||
#include "modules/meta.hpp"
|
||||
|
||||
LEMONBUDDY_NS
|
||||
|
||||
namespace modules {
|
||||
/**
|
||||
* Filesystem structure
|
||||
*/
|
||||
struct fs_disk {
|
||||
string mountpoint;
|
||||
bool mounted = false;
|
||||
|
||||
string type;
|
||||
string fsname;
|
||||
|
||||
unsigned long long bytes_free = 0;
|
||||
unsigned long long bytes_used = 0;
|
||||
unsigned long long bytes_total = 0;
|
||||
|
||||
float percentage_free = 0;
|
||||
float percentage_used = 0;
|
||||
|
||||
string percentage_free_s;
|
||||
string percentage_used_s;
|
||||
|
||||
explicit fs_disk(const string& mountpoint, bool mounted = false)
|
||||
: mountpoint(mountpoint), mounted(mounted) {}
|
||||
};
|
||||
|
||||
using fs_disk_t = unique_ptr<fs_disk>;
|
||||
|
||||
/**
|
||||
* Module used to display filesystem stats.
|
||||
*/
|
||||
class fs_module : public timer_module<fs_module> {
|
||||
public:
|
||||
using timer_module::timer_module;
|
||||
|
||||
void setup();
|
||||
bool update();
|
||||
string get_format() const;
|
||||
string get_output();
|
||||
bool build(builder* builder, string tag) const;
|
||||
|
||||
private:
|
||||
static constexpr auto FORMAT_MOUNTED = "format-mounted";
|
||||
static constexpr auto FORMAT_UNMOUNTED = "format-unmounted";
|
||||
static constexpr auto TAG_LABEL_MOUNTED = "<label-mounted>";
|
||||
static constexpr auto TAG_LABEL_UNMOUNTED = "<label-unmounted>";
|
||||
static constexpr auto TAG_BAR_USED = "<bar-used>";
|
||||
static constexpr auto TAG_BAR_FREE = "<bar-free>";
|
||||
static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
|
||||
|
||||
label_t m_labelmounted;
|
||||
label_t m_labelunmounted;
|
||||
progressbar_t m_barused;
|
||||
progressbar_t m_barfree;
|
||||
ramp_t m_rampcapacity;
|
||||
|
||||
vector<string> m_mounts;
|
||||
vector<fs_disk_t> m_disks;
|
||||
bool m_fixed = false;
|
||||
int m_spacing = 2;
|
||||
|
||||
// used while formatting output
|
||||
size_t m_index = 0;
|
||||
};
|
||||
}
|
||||
|
||||
LEMONBUDDY_NS_END
|
36
include/utils/mtab.hpp
Normal file
36
include/utils/mtab.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <mntent.h>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
LEMONBUDDY_NS
|
||||
|
||||
namespace mtab_util {
|
||||
/**
|
||||
* Wrapper for reading mtab entries
|
||||
*/
|
||||
class reader {
|
||||
public:
|
||||
explicit reader() {
|
||||
if ((m_ptr = setmntent("/etc/mtab", "r")) == nullptr) {
|
||||
throw system_error("Failed to read mtab");
|
||||
}
|
||||
}
|
||||
|
||||
~reader() {
|
||||
if (m_ptr != nullptr) {
|
||||
endmntent(m_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool next(mntent** dst) {
|
||||
return (*dst = getmntent(m_ptr)) != nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
FILE* m_ptr = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
LEMONBUDDY_NS_END
|
@ -28,6 +28,8 @@ namespace string_util {
|
||||
vector<string>& split_into(string s, char delim, vector<string>& container);
|
||||
vector<string> split(const string& s, char delim);
|
||||
size_t find_nth(string haystack, size_t pos, string needle, size_t nth);
|
||||
string floatval(float value, int decimals = 2, bool fixed = false, string locale = "");
|
||||
string filesize(unsigned long long bytes, int decimals = 2, bool fixed = false, string locale = "");
|
||||
string from_stream(const std::basic_ostream<char>& os);
|
||||
hash_type hash(string src);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "modules/counter.hpp"
|
||||
#include "modules/cpu.hpp"
|
||||
#include "modules/date.hpp"
|
||||
#include "modules/fs.hpp"
|
||||
#include "modules/memory.hpp"
|
||||
#include "modules/menu.hpp"
|
||||
#include "modules/script.hpp"
|
||||
@ -343,6 +344,8 @@ void controller::bootstrap_modules() {
|
||||
module.reset(new cpu_module(bar, m_log, m_conf, module_name));
|
||||
else if (type == "internal/date")
|
||||
module.reset(new date_module(bar, m_log, m_conf, module_name));
|
||||
else if (type == "internal/fs")
|
||||
module.reset(new fs_module(bar, m_log, m_conf, module_name));
|
||||
else if (type == "internal/memory")
|
||||
module.reset(new memory_module(bar, m_log, m_conf, module_name));
|
||||
else if (type == "internal/i3")
|
||||
|
150
src/modules/fs.cpp
Normal file
150
src/modules/fs.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "modules/fs.hpp"
|
||||
#include "utils/math.hpp"
|
||||
#include "utils/mtab.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
LEMONBUDDY_NS
|
||||
|
||||
namespace modules {
|
||||
/**
|
||||
* Bootstrap the module by reading config values and
|
||||
* setting up required components
|
||||
*/
|
||||
void fs_module::setup() {
|
||||
m_mounts = m_conf.get_list<string>(name(), "disk");
|
||||
m_fixed = m_conf.get<bool>(name(), "fixed-values", m_fixed);
|
||||
m_spacing = m_conf.get<int>(name(), "spacing", m_spacing);
|
||||
m_interval = chrono::duration<double>(m_conf.get<float>(name(), "interval", 30));
|
||||
|
||||
// Add formats and elements
|
||||
m_formatter->add(
|
||||
FORMAT_MOUNTED, TAG_LABEL_MOUNTED, {TAG_LABEL_MOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
|
||||
m_formatter->add(
|
||||
FORMAT_UNMOUNTED, TAG_LABEL_UNMOUNTED, {TAG_LABEL_UNMOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
|
||||
|
||||
if (m_formatter->has(TAG_LABEL_MOUNTED))
|
||||
m_labelmounted = load_optional_label(m_conf, name(), TAG_LABEL_MOUNTED, "%mountpoint% %percentage_free%");
|
||||
if (m_formatter->has(TAG_LABEL_UNMOUNTED))
|
||||
m_labelunmounted = load_optional_label(m_conf, name(), TAG_LABEL_UNMOUNTED, "%mountpoint% is not mounted");
|
||||
if (m_formatter->has(TAG_BAR_FREE))
|
||||
m_barfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
|
||||
if (m_formatter->has(TAG_BAR_USED))
|
||||
m_barused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
|
||||
if (m_formatter->has(TAG_RAMP_CAPACITY))
|
||||
m_rampcapacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update disk values by reading mtab entries
|
||||
*/
|
||||
bool fs_module::update() {
|
||||
m_disks.clear();
|
||||
|
||||
struct statvfs buffer;
|
||||
struct mntent* mount = nullptr;
|
||||
|
||||
for (auto&& mountpoint : m_mounts) {
|
||||
m_disks.emplace_back(new fs_disk{mountpoint, false});
|
||||
|
||||
if (statvfs(mountpoint.c_str(), &buffer) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto mtab = make_unique<mtab_util::reader>();
|
||||
auto& disk = m_disks.back();
|
||||
|
||||
while (mtab->next(&mount)) {
|
||||
if (strncmp(mount->mnt_dir, mountpoint.c_str(), strlen(mount->mnt_dir)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
disk->mounted = true;
|
||||
disk->mountpoint = mount->mnt_dir;
|
||||
disk->type = mount->mnt_type;
|
||||
disk->fsname = mount->mnt_fsname;
|
||||
|
||||
auto b_total = buffer.f_bsize * buffer.f_blocks;
|
||||
auto b_free = buffer.f_bsize * buffer.f_bfree;
|
||||
auto b_used = b_total - b_free;
|
||||
|
||||
disk->bytes_total = b_total;
|
||||
disk->bytes_free = b_free;
|
||||
disk->bytes_used = b_used;
|
||||
|
||||
disk->percentage_free = math_util::percentage<unsigned long long, float>(b_free, 0, b_total);
|
||||
disk->percentage_used = math_util::percentage<unsigned long long, float>(b_used, 0, b_total);
|
||||
|
||||
disk->percentage_free_s = string_util::floatval(disk->percentage_free, 2, m_fixed, m_bar.locale);
|
||||
disk->percentage_used_s = string_util::floatval(disk->percentage_used, 2, m_fixed, m_bar.locale);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the module output
|
||||
*/
|
||||
string fs_module::get_output() {
|
||||
string output;
|
||||
|
||||
for (m_index = 0; m_index < m_disks.size(); ++m_index) {
|
||||
if (!output.empty())
|
||||
m_builder->space(m_spacing);
|
||||
output += timer_module::get_output();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select format based on fs state
|
||||
*/
|
||||
string fs_module::get_format() const {
|
||||
return m_disks[m_index]->mounted ? FORMAT_MOUNTED : FORMAT_UNMOUNTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output content using configured format tags
|
||||
*/
|
||||
bool fs_module::build(builder* builder, string tag) const {
|
||||
auto& disk = m_disks[m_index];
|
||||
|
||||
if (tag == TAG_BAR_FREE) {
|
||||
builder->node(m_barfree->output(disk->percentage_free));
|
||||
} else if (tag == TAG_BAR_USED) {
|
||||
builder->node(m_barused->output(disk->percentage_used));
|
||||
} else if (tag == TAG_RAMP_CAPACITY) {
|
||||
builder->node(m_rampcapacity->get_by_percentage(disk->percentage_free));
|
||||
} else if (tag == TAG_LABEL_MOUNTED || tag == TAG_LABEL_UNMOUNTED) {
|
||||
label_t label;
|
||||
|
||||
if (tag == TAG_LABEL_MOUNTED)
|
||||
label = m_labelmounted->clone();
|
||||
else
|
||||
label = m_labelunmounted->clone();
|
||||
|
||||
label->reset_tokens();
|
||||
label->replace_token("%mountpoint%", disk->mountpoint);
|
||||
label->replace_token("%type%", disk->type);
|
||||
label->replace_token("%fsname%", disk->fsname);
|
||||
|
||||
label->replace_token("%percentage_free%", disk->percentage_free_s + "%");
|
||||
label->replace_token("%percentage_used%", disk->percentage_used_s + "%");
|
||||
|
||||
label->replace_token("%total%", string_util::filesize(disk->bytes_total, 1, m_fixed, m_bar.locale));
|
||||
label->replace_token("%free%", string_util::filesize(disk->bytes_free, 2, m_fixed, m_bar.locale));
|
||||
label->replace_token("%used%", string_util::filesize(disk->bytes_used, 2, m_fixed, m_bar.locale));
|
||||
|
||||
builder->node(label);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LEMONBUDDY_NS_END
|
@ -157,12 +157,48 @@ namespace string_util {
|
||||
return find_nth(haystack, found_pos + 1, needle, nth - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a float value string
|
||||
*/
|
||||
string floatval(float value, int decimals, bool fixed, string locale) {
|
||||
stringstream ss;
|
||||
ss.precision(decimals);
|
||||
if (!locale.empty())
|
||||
ss.imbue(std::locale(locale.c_str()));
|
||||
if (fixed)
|
||||
ss << std::fixed;
|
||||
ss << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a filesize string
|
||||
*/
|
||||
string filesize(unsigned long long bytes, int decimals, bool fixed, string locale) {
|
||||
vector<string> suffixes{"TB", "GB", "MB"};
|
||||
string suffix{"KB"};
|
||||
|
||||
while ((bytes /= 1000) > 999) {
|
||||
suffix = suffixes.back();
|
||||
suffixes.pop_back();
|
||||
}
|
||||
|
||||
stringstream ss;
|
||||
ss.precision(decimals);
|
||||
if (!locale.empty())
|
||||
ss.imbue(std::locale(locale.c_str()));
|
||||
if (fixed)
|
||||
ss << std::fixed;
|
||||
ss << bytes << " " << suffix;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resulting string from a ostream/
|
||||
*
|
||||
* Example usage:
|
||||
* @code cpp
|
||||
* string_util::from_stream(std::stringstream() << ...);
|
||||
* string_util::from_stream(stringstream() << ...);
|
||||
* @endcode
|
||||
*/
|
||||
string from_stream(const std::basic_ostream<char>& os) {
|
||||
|
Loading…
Reference in New Issue
Block a user