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_LEFT "bspwm i3 mpd")
|
||||||
set(MODULES_CENTER "")
|
set(MODULES_CENTER "filesystem")
|
||||||
set(MODULES_RIGHT "backlight volume memory cpu wlan eth battery temperature date powermenu")
|
set(MODULES_RIGHT "backlight volume memory cpu wlan eth battery temperature date powermenu")
|
||||||
|
|
||||||
# Strip disabled modules {{{
|
# Strip disabled modules {{{
|
||||||
|
@ -41,7 +41,7 @@ font-1 = unifont:size=6;-2
|
|||||||
font-2 = siji:pixelsize=10;0
|
font-2 = siji:pixelsize=10;0
|
||||||
|
|
||||||
modules-left = bspwm i3 mpd
|
modules-left = bspwm i3 mpd
|
||||||
modules-center =
|
modules-center = filesystem
|
||||||
modules-right = backlight volume memory cpu wlan eth battery temperature date powermenu
|
modules-right = backlight volume memory cpu wlan eth battery temperature date powermenu
|
||||||
|
|
||||||
tray-position = right
|
tray-position = right
|
||||||
@ -52,6 +52,24 @@ tray-padding = 4
|
|||||||
;wm-restack = bspwm
|
;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]
|
[module/bspwm]
|
||||||
type = internal/bspwm
|
type = internal/bspwm
|
||||||
ws-icon-default = x
|
ws-icon-default = x
|
||||||
|
@ -52,6 +52,24 @@ tray-padding = 4
|
|||||||
;wm-restack = bspwm
|
;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]
|
[module/bspwm]
|
||||||
type = internal/bspwm
|
type = internal/bspwm
|
||||||
ws-icon-default = x
|
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_into(string s, char delim, vector<string>& container);
|
||||||
vector<string> split(const string& s, char delim);
|
vector<string> split(const string& s, char delim);
|
||||||
size_t find_nth(string haystack, size_t pos, string needle, size_t nth);
|
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);
|
string from_stream(const std::basic_ostream<char>& os);
|
||||||
hash_type hash(string src);
|
hash_type hash(string src);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "modules/counter.hpp"
|
#include "modules/counter.hpp"
|
||||||
#include "modules/cpu.hpp"
|
#include "modules/cpu.hpp"
|
||||||
#include "modules/date.hpp"
|
#include "modules/date.hpp"
|
||||||
|
#include "modules/fs.hpp"
|
||||||
#include "modules/memory.hpp"
|
#include "modules/memory.hpp"
|
||||||
#include "modules/menu.hpp"
|
#include "modules/menu.hpp"
|
||||||
#include "modules/script.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));
|
module.reset(new cpu_module(bar, m_log, m_conf, module_name));
|
||||||
else if (type == "internal/date")
|
else if (type == "internal/date")
|
||||||
module.reset(new date_module(bar, m_log, m_conf, module_name));
|
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")
|
else if (type == "internal/memory")
|
||||||
module.reset(new memory_module(bar, m_log, m_conf, module_name));
|
module.reset(new memory_module(bar, m_log, m_conf, module_name));
|
||||||
else if (type == "internal/i3")
|
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);
|
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/
|
* Get the resulting string from a ostream/
|
||||||
*
|
*
|
||||||
* Example usage:
|
* Example usage:
|
||||||
* @code cpp
|
* @code cpp
|
||||||
* string_util::from_stream(std::stringstream() << ...);
|
* string_util::from_stream(stringstream() << ...);
|
||||||
* @endcode
|
* @endcode
|
||||||
*/
|
*/
|
||||||
string from_stream(const std::basic_ostream<char>& os) {
|
string from_stream(const std::basic_ostream<char>& os) {
|
||||||
|
Loading…
Reference in New Issue
Block a user