refactor: rename files from mod to their directory names

This commit is contained in:
ClementTsang 2021-08-24 22:11:06 -04:00
parent b5e6dea324
commit 189be96622
12 changed files with 1158 additions and 0 deletions

364
src/app/data_harvester.rs Normal file
View File

@ -0,0 +1,364 @@
//! This is the main file to house data collection functions.
use std::time::Instant;
#[cfg(target_os = "linux")]
use fxhash::FxHashMap;
#[cfg(not(target_os = "linux"))]
use sysinfo::{System, SystemExt};
use battery::{Battery, Manager};
use futures::join;
use super::{DataFilters, UsedWidgets};
pub mod batteries;
pub mod cpu;
pub mod disks;
pub mod memory;
pub mod network;
pub mod processes;
pub mod temperature;
#[derive(Clone, Debug)]
pub struct Data {
pub last_collection_time: Instant,
pub cpu: Option<cpu::CpuHarvest>,
pub load_avg: Option<cpu::LoadAvgHarvest>,
pub memory: Option<memory::MemHarvest>,
pub swap: Option<memory::MemHarvest>,
pub temperature_sensors: Option<Vec<temperature::TempHarvest>>,
pub network: Option<network::NetworkHarvest>,
pub list_of_processes: Option<Vec<processes::ProcessHarvest>>,
pub disks: Option<Vec<disks::DiskHarvest>>,
pub io: Option<disks::IoHarvest>,
pub list_of_batteries: Option<Vec<batteries::BatteryHarvest>>,
}
impl Default for Data {
fn default() -> Self {
Data {
last_collection_time: Instant::now(),
cpu: None,
load_avg: None,
memory: None,
swap: None,
temperature_sensors: None,
list_of_processes: None,
disks: None,
io: None,
network: None,
list_of_batteries: None,
}
}
}
impl Data {
pub fn cleanup(&mut self) {
self.io = None;
self.temperature_sensors = None;
self.list_of_processes = None;
self.disks = None;
self.memory = None;
self.swap = None;
self.cpu = None;
self.load_avg = None;
if let Some(network) = &mut self.network {
network.first_run_cleanup();
}
}
}
#[derive(Debug)]
pub struct DataCollector {
pub data: Data,
#[cfg(not(target_os = "linux"))]
sys: System,
previous_cpu_times: Vec<(cpu::PastCpuWork, cpu::PastCpuTotal)>,
previous_average_cpu_time: Option<(cpu::PastCpuWork, cpu::PastCpuTotal)>,
#[cfg(target_os = "linux")]
pid_mapping: FxHashMap<crate::Pid, processes::PrevProcDetails>,
#[cfg(target_os = "linux")]
prev_idle: f64,
#[cfg(target_os = "linux")]
prev_non_idle: f64,
mem_total_kb: u64,
temperature_type: temperature::TemperatureType,
use_current_cpu_total: bool,
last_collection_time: Instant,
total_rx: u64,
total_tx: u64,
show_average_cpu: bool,
widgets_to_harvest: UsedWidgets,
battery_manager: Option<Manager>,
battery_list: Option<Vec<Battery>>,
filters: DataFilters,
}
impl DataCollector {
pub fn new(filters: DataFilters) -> Self {
DataCollector {
data: Data::default(),
#[cfg(not(target_os = "linux"))]
sys: System::new_with_specifics(sysinfo::RefreshKind::new()),
previous_cpu_times: vec![],
previous_average_cpu_time: None,
#[cfg(target_os = "linux")]
pid_mapping: FxHashMap::default(),
#[cfg(target_os = "linux")]
prev_idle: 0_f64,
#[cfg(target_os = "linux")]
prev_non_idle: 0_f64,
mem_total_kb: 0,
temperature_type: temperature::TemperatureType::Celsius,
use_current_cpu_total: false,
last_collection_time: Instant::now(),
total_rx: 0,
total_tx: 0,
show_average_cpu: false,
widgets_to_harvest: UsedWidgets::default(),
battery_manager: None,
battery_list: None,
filters,
}
}
pub fn init(&mut self) {
#[cfg(target_os = "linux")]
{
futures::executor::block_on(self.initialize_memory_size());
}
#[cfg(not(target_os = "linux"))]
{
self.sys.refresh_memory();
self.mem_total_kb = self.sys.get_total_memory();
// TODO: Would be good to get this and network list running on a timer instead...?
// Refresh components list once...
if self.widgets_to_harvest.use_temp {
self.sys.refresh_components_list();
}
// Refresh network list once...
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
self.sys.refresh_networks_list();
}
}
if self.widgets_to_harvest.use_battery {
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
let battery_list: Vec<Battery> = batteries.filter_map(Result::ok).collect();
if !battery_list.is_empty() {
self.battery_list = Some(battery_list);
self.battery_manager = Some(battery_manager);
}
}
}
}
futures::executor::block_on(self.update_data());
std::thread::sleep(std::time::Duration::from_millis(250));
self.data.cleanup();
// trace!("Enabled widgets to harvest: {:#?}", self.widgets_to_harvest);
}
#[cfg(target_os = "linux")]
async fn initialize_memory_size(&mut self) {
self.mem_total_kb = if let Ok(mem) = heim::memory::memory().await {
mem.total().get::<heim::units::information::kilobyte>()
} else {
1
};
}
pub fn set_collected_data(&mut self, used_widgets: UsedWidgets) {
self.widgets_to_harvest = used_widgets;
}
pub fn set_temperature_type(&mut self, temperature_type: temperature::TemperatureType) {
self.temperature_type = temperature_type;
}
pub fn set_use_current_cpu_total(&mut self, use_current_cpu_total: bool) {
self.use_current_cpu_total = use_current_cpu_total;
}
pub fn set_show_average_cpu(&mut self, show_average_cpu: bool) {
self.show_average_cpu = show_average_cpu;
}
pub async fn update_data(&mut self) {
#[cfg(not(target_os = "linux"))]
{
if self.widgets_to_harvest.use_proc {
self.sys.refresh_processes();
}
if self.widgets_to_harvest.use_temp {
self.sys.refresh_components();
}
if cfg!(target_os = "windows") && self.widgets_to_harvest.use_net {
self.sys.refresh_networks();
}
}
let current_instant = std::time::Instant::now();
// CPU
if self.widgets_to_harvest.use_cpu {
if let Ok(cpu_data) = cpu::get_cpu_data_list(
self.show_average_cpu,
&mut self.previous_cpu_times,
&mut self.previous_average_cpu_time,
)
.await
{
self.data.cpu = Some(cpu_data);
}
#[cfg(target_family = "unix")]
{
// Load Average
if let Ok(load_avg_data) = cpu::get_load_avg().await {
self.data.load_avg = Some(load_avg_data);
}
}
}
// Batteries
if let Some(battery_manager) = &self.battery_manager {
if let Some(battery_list) = &mut self.battery_list {
self.data.list_of_batteries =
Some(batteries::refresh_batteries(battery_manager, battery_list));
}
}
if self.widgets_to_harvest.use_proc {
if let Ok(process_list) = {
#[cfg(target_os = "linux")]
{
processes::get_process_data(
&mut self.prev_idle,
&mut self.prev_non_idle,
&mut self.pid_mapping,
self.use_current_cpu_total,
current_instant
.duration_since(self.last_collection_time)
.as_secs(),
self.mem_total_kb,
)
}
#[cfg(not(target_os = "linux"))]
{
processes::get_process_data(
&self.sys,
self.use_current_cpu_total,
self.mem_total_kb,
)
}
} {
self.data.list_of_processes = Some(process_list);
}
}
let network_data_fut = {
#[cfg(target_os = "windows")]
{
network::get_network_data(
&self.sys,
self.last_collection_time,
&mut self.total_rx,
&mut self.total_tx,
current_instant,
self.widgets_to_harvest.use_net,
&self.filters.net_filter,
)
}
#[cfg(not(target_os = "windows"))]
{
network::get_network_data(
self.last_collection_time,
&mut self.total_rx,
&mut self.total_tx,
current_instant,
self.widgets_to_harvest.use_net,
&self.filters.net_filter,
)
}
};
let mem_data_fut = memory::get_mem_data(self.widgets_to_harvest.use_mem);
let disk_data_fut = disks::get_disk_usage(
self.widgets_to_harvest.use_disk,
&self.filters.disk_filter,
&self.filters.mount_filter,
);
let disk_io_usage_fut = disks::get_io_usage(self.widgets_to_harvest.use_disk);
let temp_data_fut = {
#[cfg(not(target_os = "linux"))]
{
temperature::get_temperature_data(
&self.sys,
&self.temperature_type,
self.widgets_to_harvest.use_temp,
&self.filters.temp_filter,
)
}
#[cfg(target_os = "linux")]
{
temperature::get_temperature_data(
&self.temperature_type,
self.widgets_to_harvest.use_temp,
&self.filters.temp_filter,
)
}
};
let (net_data, mem_res, disk_res, io_res, temp_res) = join!(
network_data_fut,
mem_data_fut,
disk_data_fut,
disk_io_usage_fut,
temp_data_fut
);
if let Ok(net_data) = net_data {
if let Some(net_data) = &net_data {
self.total_rx = net_data.total_rx;
self.total_tx = net_data.total_tx;
}
self.data.network = net_data;
}
if let Ok(memory) = mem_res.0 {
self.data.memory = memory;
}
if let Ok(swap) = mem_res.1 {
self.data.swap = swap;
}
if let Ok(disks) = disk_res {
self.data.disks = disks;
}
if let Ok(io) = io_res {
self.data.io = io;
}
if let Ok(temp) = temp_res {
self.data.temperature_sensors = temp;
}
// Update time
self.data.last_collection_time = current_instant;
self.last_collection_time = current_instant;
}
}

View File

@ -0,0 +1,10 @@
//! Data collection for batteries.
//!
//! For Linux, macOS, Windows, FreeBSD, Dragonfly, and iOS, this is handled by the battery crate.
cfg_if::cfg_if! {
if #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "ios"))] {
pub mod battery;
pub use self::battery::*;
}
}

View File

@ -0,0 +1,14 @@
//! Data collection for CPU usage and load average.
//!
//! For CPU usage, Linux, macOS, and Windows are handled by Heim.
//!
//! For load average, macOS and Linux are supported through Heim.
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
pub mod heim;
pub use self::heim::*;
}
}
pub type LoadAvgHarvest = [f32; 3];

View File

@ -0,0 +1,170 @@
//! CPU stats through heim.
//! Supports macOS, Linux, and Windows.
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub mod linux;
pub use linux::*;
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
pub mod windows_macos;
pub use windows_macos::*;
}
}
cfg_if::cfg_if! {
if #[cfg(target_family = "unix")] {
pub mod unix;
pub use unix::*;
}
}
#[derive(Default, Debug, Clone)]
pub struct CpuData {
pub cpu_prefix: String,
pub cpu_count: Option<usize>,
pub cpu_usage: f64,
}
pub type CpuHarvest = Vec<CpuData>;
pub type PastCpuWork = f64;
pub type PastCpuTotal = f64;
use futures::StreamExt;
use std::collections::VecDeque;
pub async fn get_cpu_data_list(
show_average_cpu: bool, previous_cpu_times: &mut Vec<(PastCpuWork, PastCpuTotal)>,
previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>,
) -> crate::error::Result<CpuHarvest> {
fn calculate_cpu_usage_percentage(
(previous_working_time, previous_total_time): (f64, f64),
(current_working_time, current_total_time): (f64, f64),
) -> f64 {
((if current_working_time > previous_working_time {
current_working_time - previous_working_time
} else {
0.0
}) * 100.0)
/ (if current_total_time > previous_total_time {
current_total_time - previous_total_time
} else {
1.0
})
}
// Get all CPU times...
let cpu_times = heim::cpu::times().await?;
futures::pin_mut!(cpu_times);
let mut cpu_deque: VecDeque<CpuData> = if previous_cpu_times.is_empty() {
// Must initialize ourselves. Use a very quick timeout to calculate an initial.
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
let second_cpu_times = heim::cpu::times().await?;
futures::pin_mut!(second_cpu_times);
let mut new_cpu_times: Vec<(PastCpuWork, PastCpuTotal)> = Vec::new();
let mut cpu_deque: VecDeque<CpuData> = VecDeque::new();
let mut collected_zip = cpu_times.zip(second_cpu_times).enumerate(); // Gotta move it here, can't on while line.
while let Some((itx, (past, present))) = collected_zip.next().await {
if let (Ok(past), Ok(present)) = (past, present) {
let present_times = convert_cpu_times(&present);
new_cpu_times.push(present_times);
cpu_deque.push_back(CpuData {
cpu_prefix: "CPU".to_string(),
cpu_count: Some(itx),
cpu_usage: calculate_cpu_usage_percentage(
convert_cpu_times(&past),
present_times,
),
});
} else {
new_cpu_times.push((0.0, 0.0));
cpu_deque.push_back(CpuData {
cpu_prefix: "CPU".to_string(),
cpu_count: Some(itx),
cpu_usage: 0.0,
});
}
}
*previous_cpu_times = new_cpu_times;
cpu_deque
} else {
let (new_cpu_times, cpu_deque): (Vec<(PastCpuWork, PastCpuTotal)>, VecDeque<CpuData>) =
cpu_times
.collect::<Vec<_>>()
.await
.iter()
.zip(&*previous_cpu_times)
.enumerate()
.map(|(itx, (current_cpu, (past_cpu_work, past_cpu_total)))| {
if let Ok(cpu_time) = current_cpu {
let present_times = convert_cpu_times(cpu_time);
(
present_times,
CpuData {
cpu_prefix: "CPU".to_string(),
cpu_count: Some(itx),
cpu_usage: calculate_cpu_usage_percentage(
(*past_cpu_work, *past_cpu_total),
present_times,
),
},
)
} else {
(
(*past_cpu_work, *past_cpu_total),
CpuData {
cpu_prefix: "CPU".to_string(),
cpu_count: Some(itx),
cpu_usage: 0.0,
},
)
}
})
.unzip();
*previous_cpu_times = new_cpu_times;
cpu_deque
};
// Get average CPU if needed... and slap it at the top
if show_average_cpu {
let cpu_time = heim::cpu::time().await?;
let (cpu_usage, new_average_cpu_time) = if let Some((past_cpu_work, past_cpu_total)) =
previous_average_cpu_time
{
let present_times = convert_cpu_times(&cpu_time);
(
calculate_cpu_usage_percentage((*past_cpu_work, *past_cpu_total), present_times),
present_times,
)
} else {
// Again, we need to do a quick timeout...
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
let second_cpu_time = heim::cpu::time().await?;
let present_times = convert_cpu_times(&second_cpu_time);
(
calculate_cpu_usage_percentage(convert_cpu_times(&cpu_time), present_times),
present_times,
)
};
*previous_average_cpu_time = Some(new_average_cpu_time);
cpu_deque.push_front(CpuData {
cpu_prefix: "AVG".to_string(),
cpu_count: None,
cpu_usage,
})
}
// Ok(Vec::from(cpu_deque.drain(0..3).collect::<Vec<_>>())) // For artificially limiting the CPU results
Ok(Vec::from(cpu_deque))
}

View File

@ -0,0 +1,10 @@
//! Data collection for disks (IO, usage, space, etc.).
//!
//! For Linux, macOS, and Windows, this is handled by heim.
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
pub mod heim;
pub use self::heim::*;
}
}

View File

@ -0,0 +1,154 @@
use crate::app::Filter;
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub mod linux;
pub use linux::*;
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
pub mod windows_macos;
pub use windows_macos::*;
}
}
#[derive(Debug, Clone, Default)]
pub struct DiskHarvest {
pub name: String,
pub mount_point: String,
pub free_space: Option<u64>,
pub used_space: Option<u64>,
pub total_space: Option<u64>,
}
#[derive(Clone, Debug)]
pub struct IoData {
pub read_bytes: u64,
pub write_bytes: u64,
}
pub type IoHarvest = std::collections::HashMap<String, Option<IoData>>;
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
if !actually_get {
return Ok(None);
}
use futures::StreamExt;
let mut io_hash: std::collections::HashMap<String, Option<IoData>> =
std::collections::HashMap::new();
let counter_stream = heim::disk::io_counters().await?;
futures::pin_mut!(counter_stream);
while let Some(io) = counter_stream.next().await {
if let Ok(io) = io {
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
io_hash.insert(
mount_point.to_string(),
Some(IoData {
read_bytes: io.read_bytes().get::<heim::units::information::byte>(),
write_bytes: io.write_bytes().get::<heim::units::information::byte>(),
}),
);
}
}
Ok(Some(io_hash))
}
pub async fn get_disk_usage(
actually_get: bool, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
if !actually_get {
return Ok(None);
}
use futures::StreamExt;
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
let partitions_stream = heim::disk::partitions_physical().await?;
futures::pin_mut!(partitions_stream);
while let Some(part) = partitions_stream.next().await {
if let Ok(partition) = part {
let name = get_device_name(&partition);
let mount_point = (partition
.mount_point()
.to_str()
.unwrap_or("Name Unavailable"))
.to_string();
// Precedence ordering in the case where name and mount filters disagree, "allow" takes precedence over "deny".
//
// For implementation, we do this as follows:
// 1. Is the entry allowed through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `false`? If so, we always keep this entry.
// 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry.
// 3. Anything else is allowed.
let filter_check_map = [(disk_filter, &name), (mount_filter, &mount_point)];
// This represents case 1. That is, if there is a match in an allowing list - if there is, then
// immediately allow it!
let matches_allow_list = filter_check_map.iter().any(|(filter, text)| {
if let Some(filter) = filter {
if !filter.is_list_ignored {
for r in &filter.list {
if r.is_match(text) {
return true;
}
}
}
}
false
});
let to_keep = if matches_allow_list {
true
} else {
// If it doesn't match an allow list, then check if it is denied.
// That is, if it matches in a reject filter, then reject. Otherwise, we always keep it.
!filter_check_map.iter().any(|(filter, text)| {
if let Some(filter) = filter {
if filter.is_list_ignored {
for r in &filter.list {
if r.is_match(text) {
return true;
}
}
}
}
false
})
};
if to_keep {
// The usage line can fail in some cases (for example, if you use Void Linux + LUKS,
// see https://github.com/ClementTsang/bottom/issues/419 for details). As such, check
// it like this instead.
if let Ok(usage) = heim::disk::usage(partition.mount_point().to_path_buf()).await {
vec_disks.push(DiskHarvest {
free_space: Some(usage.free().get::<heim::units::information::byte>()),
used_space: Some(usage.used().get::<heim::units::information::byte>()),
total_space: Some(usage.total().get::<heim::units::information::byte>()),
mount_point,
name,
});
} else {
vec_disks.push(DiskHarvest {
free_space: None,
used_space: None,
total_space: None,
mount_point,
name,
});
}
}
}
}
vec_disks.sort_by(|a, b| a.name.cmp(&b.name));
Ok(Some(vec_disks))
}

View File

@ -0,0 +1,10 @@
//! Data collection for memory.
//!
//! For Linux, macOS, and Windows, this is handled by Heim.
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
pub mod general;
pub use self::general::*;
}
}

View File

@ -0,0 +1,30 @@
//! Data collection for network usage/IO.
//!
//! For Linux and macOS, this is handled by Heim.
//! For Windows, this is handled by sysinfo.
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "macos"))] {
pub mod heim;
pub use self::heim::*;
} else if #[cfg(target_os = "windows")] {
pub mod sysinfo;
pub use self::sysinfo::*;
}
}
#[derive(Default, Clone, Debug)]
/// All units in bits.
pub struct NetworkHarvest {
pub rx: u64,
pub tx: u64,
pub total_rx: u64,
pub total_tx: u64,
}
impl NetworkHarvest {
pub fn first_run_cleanup(&mut self) {
self.rx = 0;
self.tx = 0;
}
}

View File

@ -0,0 +1,97 @@
//! Data collection for processes.
//!
//! For Linux, this is handled by a custom set of functions.
//! For Windows and macOS, this is handled by sysinfo.
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub mod linux;
pub use self::linux::*;
} else if #[cfg(target_os = "macos")] {
pub mod macos;
pub use self::macos::*;
} else if #[cfg(target_os = "windows")] {
pub mod windows;
pub use self::windows::*;
}
}
cfg_if::cfg_if! {
if #[cfg(target_family = "unix")] {
pub mod unix;
pub use self::unix::*;
}
}
use crate::Pid;
// TODO: Add value so we know if it's sorted ascending or descending by default?
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum ProcessSorting {
CpuPercent,
Mem,
MemPercent,
Pid,
ProcessName,
Command,
ReadPerSecond,
WritePerSecond,
TotalRead,
TotalWrite,
State,
User,
Count,
}
impl std::fmt::Display for ProcessSorting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match &self {
ProcessSorting::CpuPercent => "CPU%",
ProcessSorting::MemPercent => "Mem%",
ProcessSorting::Mem => "Mem",
ProcessSorting::ReadPerSecond => "R/s",
ProcessSorting::WritePerSecond => "W/s",
ProcessSorting::TotalRead => "T.Read",
ProcessSorting::TotalWrite => "T.Write",
ProcessSorting::State => "State",
ProcessSorting::ProcessName => "Name",
ProcessSorting::Command => "Command",
ProcessSorting::Pid => "PID",
ProcessSorting::Count => "Count",
ProcessSorting::User => "User",
}
)
}
}
impl Default for ProcessSorting {
fn default() -> Self {
ProcessSorting::CpuPercent
}
}
#[derive(Debug, Clone, Default)]
pub struct ProcessHarvest {
pub pid: Pid,
pub parent_pid: Option<Pid>, // Remember, parent_pid 0 is root...
pub cpu_usage_percent: f64,
pub mem_usage_percent: f64,
pub mem_usage_bytes: u64,
// pub rss_kb: u64,
// pub virt_kb: u64,
pub name: String,
pub command: String,
pub read_bytes_per_sec: u64,
pub write_bytes_per_sec: u64,
pub total_read_bytes: u64,
pub total_write_bytes: u64,
pub process_state: String,
pub process_state_char: char,
/// This is the *effective* user ID.
#[cfg(target_family = "unix")]
pub uid: Option<libc::uid_t>,
}

View File

@ -0,0 +1,73 @@
//! Data collection for temperature metrics.
//!
//! For Linux and macOS, this is handled by Heim.
//! For Windows, this is handled by sysinfo.
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub mod heim;
pub use self::heim::*;
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
pub mod sysinfo;
pub use self::sysinfo::*;
}
}
use std::cmp::Ordering;
use crate::app::Filter;
#[derive(Default, Debug, Clone)]
pub struct TempHarvest {
pub name: String,
pub temperature: f32,
}
#[derive(Clone, Debug)]
pub enum TemperatureType {
Celsius,
Kelvin,
Fahrenheit,
}
impl Default for TemperatureType {
fn default() -> Self {
TemperatureType::Celsius
}
}
fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool {
if let Some(filter) = filter {
if filter.is_list_ignored {
let mut ret = true;
for r in &filter.list {
if r.is_match(text) {
ret = false;
break;
}
}
ret
} else {
true
}
} else {
true
}
}
fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) {
// By default, sort temperature, then by alphabetically!
// TODO: [TEMPS] Allow users to control this.
// Note we sort in reverse here; we want greater temps to be higher priority.
temperature_vec.sort_by(|a, b| match a.temperature.partial_cmp(&b.temperature) {
Some(x) => match x {
Ordering::Less => Ordering::Greater,
Ordering::Greater => Ordering::Less,
Ordering::Equal => Ordering::Equal,
},
None => Ordering::Equal,
});
temperature_vec.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap_or(Ordering::Equal));
}

210
src/app/widgets.rs Normal file
View File

@ -0,0 +1,210 @@
use std::time::Instant;
use crossterm::event::{KeyEvent, MouseEvent};
use enum_dispatch::enum_dispatch;
use tui::{layout::Rect, widgets::TableState};
use crate::{
app::{
event::{EventResult, SelectionAction},
layout_manager::BottomWidgetType,
},
constants,
};
pub mod base;
pub use base::*;
pub mod process;
pub use process::*;
pub mod net;
pub use net::*;
pub mod mem;
pub use mem::*;
pub mod cpu;
pub use cpu::*;
pub mod disk;
pub use disk::*;
pub mod battery;
pub use self::battery::*;
pub mod temp;
pub use temp::*;
/// A trait for things that are drawn with state.
#[enum_dispatch]
#[allow(unused_variables)]
pub trait Component {
/// Handles a [`KeyEvent`].
///
/// Defaults to returning [`EventResult::NoRedraw`], indicating nothing should be done.
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
EventResult::NoRedraw
}
/// Handles a [`MouseEvent`].
///
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done.
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
EventResult::NoRedraw
}
/// Returns a [`Component`]'s bounding box. Note that these are defined in *global*, *absolute*
/// coordinates.
fn bounds(&self) -> Rect;
/// Updates a [`Component`]s bounding box to `new_bounds`.
fn set_bounds(&mut self, new_bounds: Rect);
}
/// A trait for actual fully-fledged widgets to be displayed in bottom.
#[enum_dispatch]
pub trait Widget {
/// Updates a [`Widget`] given some data. Defaults to doing nothing.
fn update(&mut self) {}
/// Handles what to do when trying to respond to a widget selection movement to the left.
/// Defaults to just moving to the next-possible widget in that direction.
fn handle_widget_selection_left(&mut self) -> SelectionAction {
SelectionAction::NotHandled
}
/// Handles what to do when trying to respond to a widget selection movement to the right.
/// Defaults to just moving to the next-possible widget in that direction.
fn handle_widget_selection_right(&mut self) -> SelectionAction {
SelectionAction::NotHandled
}
/// Handles what to do when trying to respond to a widget selection movement upward.
/// Defaults to just moving to the next-possible widget in that direction.
fn handle_widget_selection_up(&mut self) -> SelectionAction {
SelectionAction::NotHandled
}
/// Handles what to do when trying to respond to a widget selection movement downward.
/// Defaults to just moving to the next-possible widget in that direction.
fn handle_widget_selection_down(&mut self) -> SelectionAction {
SelectionAction::NotHandled
}
fn get_pretty_name(&self) -> &'static str;
}
/// The "main" widgets that are used by bottom to display information!
#[enum_dispatch(Component, Widget)]
pub enum TmpBottomWidget {
MemGraph,
TempTable,
DiskTable,
CpuGraph,
NetGraph,
OldNetGraph,
ProcessManager,
BatteryTable,
}
// ----- Old stuff below -----
#[derive(Debug)]
pub enum ScrollDirection {
// UP means scrolling up --- this usually DECREMENTS
Up,
// DOWN means scrolling down --- this usually INCREMENTS
Down,
}
impl Default for ScrollDirection {
fn default() -> Self {
ScrollDirection::Down
}
}
#[derive(Debug)]
pub enum CursorDirection {
Left,
Right,
}
/// AppScrollWidgetState deals with fields for a scrollable app's current state.
#[derive(Default)]
pub struct AppScrollWidgetState {
pub current_scroll_position: usize,
pub previous_scroll_position: usize,
pub scroll_direction: ScrollDirection,
pub table_state: TableState,
}
#[derive(PartialEq)]
pub enum KillSignal {
Cancel,
Kill(usize),
}
impl Default for KillSignal {
#[cfg(target_family = "unix")]
fn default() -> Self {
KillSignal::Kill(15)
}
#[cfg(target_os = "windows")]
fn default() -> Self {
KillSignal::Kill(1)
}
}
#[derive(Default)]
pub struct AppDeleteDialogState {
pub is_showing_dd: bool,
pub selected_signal: KillSignal,
/// tl x, tl y, br x, br y, index/signal
pub button_positions: Vec<(u16, u16, u16, u16, usize)>,
pub keyboard_signal_select: usize,
pub last_number_press: Option<Instant>,
pub scroll_pos: usize,
}
pub struct AppHelpDialogState {
pub is_showing_help: bool,
pub scroll_state: ParagraphScrollState,
pub index_shortcuts: Vec<u16>,
}
impl Default for AppHelpDialogState {
fn default() -> Self {
AppHelpDialogState {
is_showing_help: false,
scroll_state: ParagraphScrollState::default(),
index_shortcuts: vec![0; constants::HELP_TEXT.len()],
}
}
}
/// Meant for canvas operations involving table column widths.
#[derive(Default)]
pub struct CanvasTableWidthState {
pub desired_column_widths: Vec<u16>,
pub calculated_column_widths: Vec<u16>,
}
pub struct BasicTableWidgetState {
// Since this is intended (currently) to only be used for ONE widget, that's
// how it's going to be written. If we want to allow for multiple of these,
// then we can expand outwards with a normal BasicTableState and a hashmap
pub currently_displayed_widget_type: BottomWidgetType,
pub currently_displayed_widget_id: u64,
pub widget_id: i64,
pub left_tlc: Option<(u16, u16)>,
pub left_brc: Option<(u16, u16)>,
pub right_tlc: Option<(u16, u16)>,
pub right_brc: Option<(u16, u16)>,
}
#[derive(Default)]
pub struct ParagraphScrollState {
pub current_scroll_index: u16,
pub max_scroll_index: u16,
}

16
src/app/widgets/base.rs Normal file
View File

@ -0,0 +1,16 @@
//! A collection of basic components.
pub mod text_table;
pub use text_table::TextTable;
pub mod time_graph;
pub use time_graph::TimeGraph;
pub mod scrollable;
pub use scrollable::Scrollable;
pub mod text_input;
pub use text_input::TextInput;
pub mod carousel;
pub use carousel::Carousel;