From 1930a0330c35b9916f21176e25828654f40dc5e5 Mon Sep 17 00:00:00 2001 From: rhasler1 Date: Mon, 9 Jun 2025 13:35:02 -0500 Subject: [PATCH 1/3] adding memory and temp --- bounded-queue/src/cpu_item.rs | 12 -- bounded-queue/src/lib.rs | 2 + bounded-queue/src/memory_item.rs | 46 ++--- bounded-queue/src/temp_item.rs | 35 ++++ process-list/src/process_list.rs | 48 ++--- process-list/src/process_list_item.rs | 25 ++- process-list/src/process_list_items.rs | 22 +-- src/app.rs | 99 ++++++++-- src/components/command.rs | 2 +- src/components/cpu.rs | 76 ++++---- src/components/error.rs | 2 +- src/components/filter.rs | 4 +- src/components/help.rs | 2 +- src/components/memory.rs | 109 +++++++++++ src/components/mod.rs | 2 + src/components/process.rs | 210 +++++++++++++++------ src/components/sysinfo_wrapper.rs | 80 +++++++- src/components/temp.rs | 243 +++++++++++++++++++++++++ src/config.rs | 78 ++------ src/events/event.rs | 42 ++--- src/main.rs | 24 +-- src/ui/mod.rs | 1 - src/ui/process_list_ui.rs | 107 ----------- 23 files changed, 868 insertions(+), 403 deletions(-) create mode 100644 bounded-queue/src/temp_item.rs create mode 100644 src/components/memory.rs create mode 100644 src/components/temp.rs delete mode 100644 src/ui/mod.rs delete mode 100644 src/ui/process_list_ui.rs diff --git a/bounded-queue/src/cpu_item.rs b/bounded-queue/src/cpu_item.rs index d6d94e3..9ca6ce7 100644 --- a/bounded-queue/src/cpu_item.rs +++ b/bounded-queue/src/cpu_item.rs @@ -3,9 +3,6 @@ pub struct CpuItem { id: usize, usage: f32, frequency: u64, - name: String, - brand: String, - vendor_id: String, } impl CpuItem { @@ -13,17 +10,11 @@ impl CpuItem { id: usize, usage: f32, frequency: u64, - name: String, - brand: String, - vendor_id: String, ) -> Self { Self { id, usage, frequency, - name, - brand, - vendor_id, } } @@ -44,9 +35,6 @@ impl CpuItem { self.frequency } - pub fn brand(&self) -> String { - self.brand.clone() - } } #[cfg(test)] diff --git a/bounded-queue/src/lib.rs b/bounded-queue/src/lib.rs index 80f4c37..04b7c02 100644 --- a/bounded-queue/src/lib.rs +++ b/bounded-queue/src/lib.rs @@ -1,7 +1,9 @@ mod cpu_item; mod memory_item; +mod temp_item; mod bounded_queue; pub use cpu_item::CpuItem; pub use memory_item::MemoryItem; +pub use temp_item::TempItem; pub use bounded_queue::BoundedQueue; diff --git a/bounded-queue/src/memory_item.rs b/bounded-queue/src/memory_item.rs index f1bd572..ce47983 100644 --- a/bounded-queue/src/memory_item.rs +++ b/bounded-queue/src/memory_item.rs @@ -2,17 +2,17 @@ pub struct MemoryItem { total_memory: u64, used_memory: u64, - free_memory: u64, - available_memory: u64, + total_swap: u64, + used_swap: u64, } impl MemoryItem { - pub fn new(total_memory: u64, used_memory: u64, free_memory: u64, available_memory: u64) -> Self { + pub fn new(total_memory: u64, used_memory: u64, total_swap: u64, used_swap: u64) -> Self { Self { total_memory, used_memory, - free_memory, - available_memory, + total_swap, + used_swap, } } @@ -23,14 +23,6 @@ impl MemoryItem { pub fn used_memory(&self) -> u64 { self.used_memory } - - pub fn free_memory(&self) -> u64 { - self.free_memory - } - - pub fn available_memory(&self) -> u64 { - self.available_memory - } pub fn total_memory_gb(&self) -> f64 { self.total_memory as f64 / 1000000000_f64 @@ -40,12 +32,20 @@ impl MemoryItem { self.used_memory as f64 / 1000000000_f64 } - pub fn free_memory_gb(&self) -> f64 { - self.free_memory as f64 / 1000000000_f64 + pub fn total_swap(&self) -> u64 { + self.total_swap } - pub fn available_memory_gb(&self) -> f64 { - self.available_memory as f64 / 1000000000_f64 + pub fn used_swap(&self) -> u64 { + self.used_swap + } + + pub fn total_swap_gb(&self) -> f64 { + self.total_swap as f64 / 1000000000_f64 + } + + pub fn used_swap_gb(&self) -> f64 { + self.used_swap as f64 / 1000000000_f64 } } @@ -58,24 +58,16 @@ mod test { let instance = MemoryItem::default(); assert_eq!(instance.total_memory(), 0); assert_eq!(instance.used_memory(), 0); - assert_eq!(instance.free_memory(), 0); - assert_eq!(instance.available_memory(), 0); assert_eq!(instance.total_memory_gb(), 0.0); assert_eq!(instance.used_memory_gb(), 0.0); - assert_eq!(instance.free_memory_gb(), 0.0); - assert_eq!(instance.available_memory_gb(), 0.0); } #[test] fn test_new() { - let instance = MemoryItem::new(1, 2, 3, 4); + let instance = MemoryItem::new(1, 2, 0, 0); assert_eq!(instance.total_memory(), 1); assert_eq!(instance.used_memory(), 2); - assert_eq!(instance.free_memory(), 3); - assert_eq!(instance.available_memory(), 4); assert_eq!(instance.total_memory_gb(), 0.000000001); assert_eq!(instance.used_memory_gb(), 0.000000002); - assert_eq!(instance.free_memory_gb(), 0.000000003); - assert_eq!(instance.available_memory_gb(), 0.000000004); } -} +} \ No newline at end of file diff --git a/bounded-queue/src/temp_item.rs b/bounded-queue/src/temp_item.rs new file mode 100644 index 0000000..5552636 --- /dev/null +++ b/bounded-queue/src/temp_item.rs @@ -0,0 +1,35 @@ +#[derive(Clone, Default, Debug)] +pub struct TempItem { + //TODO + temp: f32, + max_temp: f32, + critical_temp: f32, + label: String, +} + +impl TempItem { + pub fn new(temp: f32, max_temp: f32, critical_temp: f32, label: String) -> Self { + Self { + temp, + max_temp, + critical_temp, + label + } + } + + pub fn temp(&self) -> f32 { + self.temp + } + + pub fn max_temp(&self) -> f32 { + self.max_temp + } + + pub fn critical_temp(&self) -> f32 { + self.critical_temp + } + + pub fn label(&self) -> &str { + &self.label + } +} \ No newline at end of file diff --git a/process-list/src/process_list.rs b/process-list/src/process_list.rs index 6dbb5a8..49afb71 100644 --- a/process-list/src/process_list.rs +++ b/process-list/src/process_list.rs @@ -202,6 +202,10 @@ impl ProcessList { None } + pub fn get_sort_order(&self) -> &ListSortOrder { + &self.sort + } + pub fn iterate(&self, start_index: usize, max_amount: usize) -> ListIterator<'_> { let start = start_index; ListIterator::new(self.items.iterate(start, max_amount), self.selection) @@ -221,8 +225,8 @@ mod test { assert_eq!(empty_instance.selection(), None); // New constructor. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let instance = ProcessList::new(items); assert!(!instance.is_empty()); @@ -244,8 +248,8 @@ mod test { #[test] fn test_update() { // Update with empty list of items. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); let empty_items = vec![]; @@ -254,19 +258,19 @@ mod test { assert!(instance.selection().is_none()); // Update with non-empty list of items. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); - let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test")); + let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); let new_items = vec![item_2]; let _ = instance.update(new_items); assert!(!instance.is_empty()); assert_eq!(instance.selection(), Some(0)); // Update with empty list of items and follow_selection set to true. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); let _ = instance.toggle_follow_selection(); @@ -276,25 +280,25 @@ mod test { assert!(instance.selection().is_none()); // Update with non-empty list of items and follow_selection set to true case 1. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); let _ = instance.toggle_follow_selection(); - let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test")); + let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); let new_items = vec![item_2]; let _ = instance.update(new_items); assert!(!instance.is_empty()); assert_eq!(instance.selection(), Some(0)); // Update with non-empty list of items and follow_selection set to true case 2. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); let _ = instance.toggle_follow_selection(); - let item_2 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test")); + let item_2 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); let new_items = vec![item_2, item_3]; let _ = instance.update(new_items); assert!(!instance.is_empty()); @@ -304,8 +308,8 @@ mod test { #[test] fn test_sort() { // Test sort when follow_selection = false. - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_1, item_0]; let mut instance = ProcessList::new(items); assert!(instance.sort == ListSortOrder::CpuUsageDec); @@ -315,8 +319,8 @@ mod test { assert_eq!(instance.selection(), Some(0)); - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); assert!(instance.sort == ListSortOrder::CpuUsageDec); @@ -333,8 +337,8 @@ mod test { empty_instance.move_selection(MoveSelection::Down); assert_eq!(empty_instance.selection(), None); - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessList::new(items); assert_eq!(instance.selection(), Some(0)); diff --git a/process-list/src/process_list_item.rs b/process-list/src/process_list_item.rs index ee5b5fe..0ccbe54 100644 --- a/process-list/src/process_list_item.rs +++ b/process-list/src/process_list_item.rs @@ -8,6 +8,7 @@ pub struct ProcessListItem { run_time: u64, accumulated_cpu_time: u64, status: String, + path: String, } impl ProcessListItem { @@ -20,6 +21,7 @@ impl ProcessListItem { run_time: u64, accumulated_cpu_time: u64, status: String, + path: String, ) -> Self { Self { pid, @@ -30,6 +32,7 @@ impl ProcessListItem { run_time, accumulated_cpu_time, status, + path, } } @@ -63,12 +66,26 @@ impl ProcessListItem { self.run_time } + pub fn run_time_hh_mm_ss(&self) -> String { + let time_in_s = self.run_time; + + let ss = time_in_s % 60; + let mm = (time_in_s / 60) % 60; + let hh = (time_in_s / 60) / 60; + + format!("{:0>2}:{:0>2}:{:0>2}", hh, mm, ss) + } + pub fn accumulated_cpu_time(&self) -> u64 { self.accumulated_cpu_time } - pub fn status(&self) -> String { - self.status.clone() + pub fn status(&self) -> &str { + &self.status + } + + pub fn path(&self) -> &str { + &self.path } } @@ -95,7 +112,7 @@ pub mod test { assert_eq!(instance.accumulated_cpu_time, 0); assert!(String::is_empty(&instance.status)); - let instance = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); + let instance = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); assert_eq!(instance.pid, 1); assert_eq!(instance.name, String::from("a")); assert_eq!(instance.cpu_usage, 1.0); @@ -110,7 +127,7 @@ pub mod test { #[test] fn test_instance_functions() { let instance_0 = ProcessListItem::default(); - let instance_1 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); + let instance_1 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); assert_eq!(instance_0.pid(), instance_0.pid); assert_eq!(instance_0.name(), instance_0.name); diff --git a/process-list/src/process_list_items.rs b/process-list/src/process_list_items.rs index ae31d39..38e27e4 100644 --- a/process-list/src/process_list_items.rs +++ b/process-list/src/process_list_items.rs @@ -104,9 +104,9 @@ mod test { #[test] fn test_new() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let clone_0 = item_0.clone(); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let clone_1 = item_1.clone(); let items = vec![item_0, item_1]; let instance = ProcessListItems::new(items); @@ -123,9 +123,9 @@ mod test { #[test] fn test_filter() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let clone_0 = item_0.clone(); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let _clone_1 = item_1.clone(); let items = vec![item_0, item_1]; let instance = ProcessListItems::new(items); @@ -140,14 +140,14 @@ mod test { #[test] fn test_update_items() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1]; let mut instance = ProcessListItems::new(items); // Note: ProcessListItem's are compared by Pid. - let item_2 = ProcessListItem::new(1, String::from("a"), 7.0, 1337, 0, 10, 10, String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test")); + let item_2 = ProcessListItem::new(1, String::from("a"), 7.0, 1337, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); let new_items = vec![item_2, item_3]; let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); @@ -165,9 +165,9 @@ mod test { #[test] fn test_sort_items() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test")); + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); let items = vec![item_0, item_1, item_3]; let mut instance = ProcessListItems::new(items); diff --git a/src/app.rs b/src/app.rs index 7721f67..e4e4bc8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,11 @@ use anyhow::{Ok, Result}; use crossterm::event::KeyEvent; use ratatui::prelude::*; +use crate::components::temp::TempComponent; use crate::config::Config; use crate::components::{ cpu::CPUComponent, + memory::MemoryComponent, process::ProcessComponent, sysinfo_wrapper::SysInfoWrapper, error::ErrorComponent, @@ -18,6 +20,8 @@ use crate::components::{ enum MainFocus { CPU, Process, + Memory, + Temp, } pub struct App { @@ -26,6 +30,8 @@ pub struct App { system_wrapper: SysInfoWrapper, process: ProcessComponent, cpu: CPUComponent, + memory: MemoryComponent, + temp: TempComponent, help: HelpComponent, pub error: ErrorComponent, pub config: Config, @@ -34,23 +40,31 @@ pub struct App { impl App { pub fn new(config: Config) -> Self { let mut system_wrapper = SysInfoWrapper::new(config.clone()); - + system_wrapper.refresh_all(); let processes = system_wrapper.get_processes(); let mut cpu = CPUComponent::default(); - let cpus = system_wrapper.get_cpus(); - cpu.update(cpus); + let memory_item = system_wrapper.get_memory(); + let mut memory = MemoryComponent::new(config.clone()); + memory.update(memory_item); + + let temp_items = system_wrapper.get_temps(); + let mut temp = TempComponent::new(config.clone()); + temp.update(temp_items); + Self { focus: MainFocus::Process, expand: false, system_wrapper, process: ProcessComponent::new(config.clone(), processes), cpu, + memory, + temp, help: HelpComponent::new(config.clone()), error: ErrorComponent::new(config.clone()), config: config.clone(), @@ -64,11 +78,25 @@ impl App { self.update_cpu(); - self.update_cmds(); + self.update_memory(); + + self.update_temps(); Ok(EventState::Consumed) } + fn update_temps(&mut self) { + let temp_items = self.system_wrapper.get_temps(); + + self.temp.update(temp_items); + } + + fn update_memory(&mut self) { + let memory_item = self.system_wrapper.get_memory(); + + self.memory.update(memory_item); + } + fn update_process(&mut self) { let new_processes = self.system_wrapper.get_processes(); // receive ownership @@ -123,6 +151,16 @@ impl App { return Ok(EventState::Consumed) } } + MainFocus::Memory => { + if self.memory.event(key)?.is_consumed() { + return Ok(EventState::Consumed) + } + } + MainFocus::Temp => { + if self.temp.event(key)?.is_consumed() { + return Ok(EventState::Consumed) + } + } MainFocus::Process => { if self.process.event(key)?.is_consumed() { return Ok(EventState::Consumed) @@ -137,6 +175,12 @@ impl App { if key.code == self.config.key_config.tab { match self.focus { MainFocus::CPU => { + self.focus = MainFocus::Memory + } + MainFocus::Memory => { + self.focus = MainFocus::Temp + } + MainFocus::Temp => { self.focus = MainFocus::Process } MainFocus::Process => { @@ -177,13 +221,30 @@ impl App { true, )?; } + + if matches!(self.focus, MainFocus::Memory) { + self.memory.draw( + f, + chunks[0], + true, + )?; + } + + if matches!(self.focus, MainFocus::Temp) { + self.temp.draw( + f, + chunks[0], + true, + )?; + } } else { let vertical_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Percentage(25), - Constraint::Percentage(75), + Constraint::Percentage(24), + Constraint::Percentage(24), + Constraint::Percentage(52), ].as_ref()) .split(chunks[0]); @@ -193,9 +254,8 @@ impl App { let horizontal_chunk = Layout::default() .direction(Direction::Horizontal) .constraints([ - Constraint::Percentage(33), - Constraint::Percentage(33), - Constraint::Percentage(34), + Constraint::Percentage(50), + Constraint::Percentage(50), ]) .split(*chunk); @@ -204,7 +264,7 @@ impl App { self.process.draw( f, - vertical_chunks[1], + vertical_chunks[2], matches!(self.focus, MainFocus::Process) )?; @@ -213,6 +273,19 @@ impl App { vertical_chunks[0], matches!(self.focus, MainFocus::CPU) )?; + + self.memory.draw( + f, + horizontal_chunks[1][0], + //vertical_chunks[1], + matches!(self.focus, MainFocus::Memory) + )?; + + self.temp.draw( + f, + horizontal_chunks[1][1], + matches!(self.focus, MainFocus::Temp) + )?; } self.help.draw(f, Rect::default(), false)?; @@ -220,12 +293,6 @@ impl App { return Ok(()) } - fn update_cmds(&mut self) { - let cmds = self.commands(); - - self.help.set_commands(cmds); - } - fn commands(&self) -> Vec { let res = vec![ CommandInfo::new(command::help(&self.config.key_config)), diff --git a/src/components/command.rs b/src/components/command.rs index 2e37400..29572d4 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -75,7 +75,7 @@ pub fn exit_popup(key: &KeyConfig) -> CommandText { CommandText::new( format!( "Exit current screen [{:?}]", - key.exit_popup, + key.exit, ), CMD_GROUP_GENERAL ) diff --git a/src/components/cpu.rs b/src/components/cpu.rs index e17c3d5..0cd6e46 100644 --- a/src/components/cpu.rs +++ b/src/components/cpu.rs @@ -1,23 +1,12 @@ use std::collections::BTreeMap; - use ratatui::prelude::*; use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, List, ListItem, ListState}; - use std::str::FromStr; - use anyhow::Ok; use bounded_queue::{BoundedQueue, CpuItem}; use crate::config::Config; - use super::{Component, DrawableComponent}; -#[derive(Default)] -pub struct CPUComponent { - cpus: BTreeMap>, - ui_selection: usize, - config: Config, -} - #[derive(PartialEq, Clone, Copy)] pub enum ColorWheel { Red, @@ -71,6 +60,13 @@ impl ColorWheel { } } +#[derive(Default)] +pub struct CPUComponent { + cpus: BTreeMap>, + ui_selection: usize, + config: Config, +} + impl CPUComponent { // has ownership pub fn update(&mut self, cpus: Vec) { @@ -110,12 +106,12 @@ impl DrawableComponent for CPUComponent { let horizontal_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ - Constraint::Percentage(90), // chart - Constraint::Fill(1), // list + Constraint::Fill(1), // chart + Constraint::Length(16), // list ]).split(area); // containers - let mut all_data: Vec<(u32, Vec<(f64, f64)>)> = Vec::new(); // collect all data ensuring references live long enough to be drawn by `datasets` + let mut all_data: Vec<(u32, Vec<(f64, f64)>)> = Vec::new(); // collect all data ensuring references live long enough to be drawn by `datasets` let mut datasets: Vec = Vec::new(); // holds references to data to be drawn // get max index of a queue (they are all the same) @@ -172,11 +168,6 @@ impl DrawableComponent for CPUComponent { } } - // TODO: colors should match between these two - // datasets can be 1..len(cpus) - // names is always len(cpus) + 1(All) - - // populate datasets for drawing chart for (id, data) in all_data.iter() { datasets.push( @@ -191,29 +182,35 @@ impl DrawableComponent for CPUComponent { // populate names for UIList to draw cpu list let mut names: Vec = self.cpus .iter() - .map(|(key, _queue)| { - let title = if *key == 0 { - format!("Global") + .map(|(key, queue)| { + let usage = queue.back().unwrap().usage(); + + let label = if *key == 0 { + String::from("Global") } else { - format!("CPU {}", key.saturating_sub(1).to_string()) + format!("CPU {}", key.saturating_sub(1)) }; + + let title = format!("{:<7} {:.2}", label, usage); + ListItem::new(title).style(Color::from_str(ColorWheel::from_index(*key).as_str()).unwrap()) }) .collect(); // insert All option into UI list - names.insert(0, ListItem::new(String::from("All"))); + let title = format!("{:<7} {}", String::from("All"), String::from("%")); + names.insert(0, ListItem::new(title)); // render chart let chart = Chart::new(datasets) .block( { if !focused { - Block::default().borders(Borders::ALL).title(" CPU % ").style(Color::DarkGray) + Block::default().borders(Borders::ALL).title(" CPU % ").style(self.config.theme_config.style_border_not_focused) } else { - Block::default().borders(Borders::ALL).title(" CPU % ").style(Color::LightGreen) + Block::default().borders(Borders::ALL).title(" CPU % ").style(self.config.theme_config.style_border_focused) } } ) @@ -221,7 +218,7 @@ impl DrawableComponent for CPUComponent { .x_axis( Axis::default() .bounds([0.0, self.config.events_per_min().saturating_sub(1) as f64]) - .labels(vec![Span::raw(format!("-{}s",self.config.min_as_s())), Span::raw("now")]) + .labels(vec![Span::raw(format!("-{}s", self.config.min_as_s())), Span::raw("now")]) .labels_alignment(Alignment::Right), ) .y_axis( @@ -239,27 +236,30 @@ impl DrawableComponent for CPUComponent { // render cpu list let mut list_state = ListState::default(); + list_state.select(Some(self.ui_selection)); + let cpu_list = List::new(names) .scroll_padding(horizontal_chunks[1].height as usize / 2) .block( - { - if !focused { - Block::default().borders(Borders::ALL).style(Color::DarkGray) - } - else { - Block::default().borders(Borders::ALL).style(Color::LightGreen) - } + if !focused { + Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_not_focused) + } + else { + Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_focused) } ) .highlight_style( - Style::default() - .bg(Color::LightBlue) - .add_modifier(Modifier::BOLD) + if !focused { + self.config.theme_config.style_item_selected_not_focused + } + else { + self.config.theme_config.style_item_selected + } ); f.render_stateful_widget(cpu_list, horizontal_chunks[1], &mut list_state); Ok(()) } -} +} \ No newline at end of file diff --git a/src/components/error.rs b/src/components/error.rs index 5ed839c..6bc5467 100644 --- a/src/components/error.rs +++ b/src/components/error.rs @@ -45,7 +45,7 @@ impl ErrorComponent { impl Component for ErrorComponent { fn event(&mut self, key: KeyEvent) -> Result { if self.visible { - if key.code == self.config.key_config.exit_popup { + if key.code == self.config.key_config.exit { self.error = String::new(); self.hide()?; return Ok(EventState::Consumed); diff --git a/src/components/filter.rs b/src/components/filter.rs index 1b20964..35c8330 100644 --- a/src/components/filter.rs +++ b/src/components/filter.rs @@ -57,10 +57,10 @@ impl DrawableComponent for FilterComponent { let style: Style = if focused { - self.config.theme_config.component_in_focus + self.config.theme_config.style_border_focused } else { - self.config.theme_config.component_out_of_focus + self.config.theme_config.style_border_not_focused }; let filter_text: &str = self.input_str.as_str(); diff --git a/src/components/help.rs b/src/components/help.rs index bdcd424..2d4ec63 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -104,7 +104,7 @@ impl HelpComponent { impl Component for HelpComponent { fn event(&mut self, key: KeyEvent) -> Result { if self.visible { - if key.code == self.config.key_config.exit_popup { + if key.code == self.config.key_config.exit { self.hide(); return Ok(EventState::Consumed); } diff --git a/src/components/memory.rs b/src/components/memory.rs new file mode 100644 index 0000000..d8a086f --- /dev/null +++ b/src/components/memory.rs @@ -0,0 +1,109 @@ +use std::fs::File; + +// RAM component +use anyhow::Ok; +use anyhow::Result; +use bounded_queue::BoundedQueue; +use bounded_queue::MemoryItem; +use crate::components::DrawableComponent; +use crate::config::Config; +use super::Component; +use super::EventState; +use ratatui::{ + layout::{Layout, Direction, Constraint}, + style::{Style, Stylize}, + widgets::{Block, Gauge}, +}; + +//TODO: This does not need to use a bounded queue--only displaying fields of most recent item with gauges. +pub struct MemoryComponent { + config: Config, + memories: BoundedQueue, +} + +impl MemoryComponent { + pub fn new(config: Config) -> Self { + let memories: BoundedQueue = BoundedQueue::new(config.events_per_min() as usize); + + Self { + config, + memories, + } + } + + pub fn update(&mut self, memory_item: MemoryItem) { + self.memories.add_item(memory_item); + } +} + +impl Component for MemoryComponent { + fn event(&mut self, _key: crossterm::event::KeyEvent) -> Result { + Ok(EventState::NotConsumed) + } +} + +impl DrawableComponent for MemoryComponent { + fn draw(&mut self, f: &mut ratatui::Frame, area: ratatui::prelude::Rect, focused: bool) -> Result<()> { + let vertical_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Ratio(1, 2), + Constraint::Ratio(1, 2), + ].as_ref()) + .split(area); + + let style = if focused { + self.config.theme_config.style_border_focused + } + else { + self.config.theme_config.style_border_not_focused + }; + + // ram widget + let ram_percent = if let Some(item) = self.memories.back() { + ( item.used_memory_gb() / item.total_memory_gb() ) * 100_f64 + } + else { + 0_f64 + }; + + let ram_label = "RAM Usage"; + let ram_title = if let Some(item) = self.memories.back() { + format!(" {:<15} {:.2} GB / {:.2} GB ", ram_label, item.used_memory_gb(), item.total_memory_gb()) + } + else { + format!(" {:<15} ", ram_label) + }; + + let g_ram = Gauge::default() + .block(Block::bordered().style(style).title(ram_title)) + .gauge_style(Style::new().red().on_black().italic()) + .percent(ram_percent as u16); + + // swap widget + let swap_percent = if let Some(item) = self.memories.back() { + ( item.used_swap_gb() / item.total_swap_gb() ) * 100_f64 + } + else { + 0_f64 + }; + + let swap_label = "Swap Usage"; + let swap_title = if let Some(item) = self.memories.back() { + format!(" {:<15} {:.2} GB / {:.2} GB ", swap_label, item.used_swap_gb(), item.total_swap_gb()) + } + else { + format!(" {:<15} ", swap_label) + }; + + let g_swap = Gauge::default() + .block(Block::bordered().style(style).title(swap_title)) + .gauge_style(Style::new().magenta().on_black().italic()) + .percent(swap_percent as u16); + + f.render_widget(g_ram, vertical_chunks[0]); + f.render_widget(g_swap, vertical_chunks[1]); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/components/mod.rs b/src/components/mod.rs index ca4b6c7..090ffcf 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -11,6 +11,8 @@ pub mod process; pub mod utils; pub mod command; pub mod cpu; +pub mod memory; +pub mod temp; pub trait DrawableComponent { fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()>; diff --git a/src/components/process.rs b/src/components/process.rs index 8a4f701..fcfde1a 100644 --- a/src/components/process.rs +++ b/src/components/process.rs @@ -4,16 +4,14 @@ use ratatui::{ Frame, prelude::*, }; -use process_list::{ProcessList, ProcessListItem, ProcessListItems}; - -use super::{common_nav, common_sort}; -use super::{filter::FilterComponent, Component, DrawableComponent, EventState}; -use super::utils::vertical_scroll::VerticalScroll; -use crate::config::Config; -use crate::config::KeyConfig; -use crate::ui::process_list_ui::process_list_ui::draw_process_list; +use crate::config::{Config, KeyConfig}; +use process_list::{ListSortOrder, ProcessList, ProcessListItem, ProcessListItems}; +use super::{ + common_nav, common_sort, Component, DrawableComponent, EventState, + utils::vertical_scroll::VerticalScroll, + filter::FilterComponent, +}; -// focus of process component, this can be on either a ProcessList or FilterComponent #[derive(PartialEq, Clone)] pub enum Focus { Filter, @@ -41,29 +39,19 @@ impl ProcessComponent { } } - // update process list, return true if new processes is non empty - // given ownersip - pub fn update(&mut self, new_processes: Vec) -> bool { - // should never be empty + pub fn update(&mut self, new_processes: Vec) { assert!(!new_processes.is_empty()); - // two vectors are needed to update list and filtered list let dup = new_processes.clone(); - - // Must pass by reference to update both lists + self.list.update(new_processes); if let Some(filtered_list) = self.filtered_list.as_mut() { - // implement a better way to update the filtered list - // filter new processes let processes = ProcessListItems::new(dup); - //let filter_text = self.filter.input_str(); let filtered_processes = processes.filter(self.filter.input_str()); - // transfer ownership + filtered_list.update(filtered_processes.list_items); } - - true } // gets the selected process pid, returns Some(pid) or None @@ -81,40 +69,33 @@ impl ProcessComponent { } impl Component for ProcessComponent { - // Handle key events for ProcessComponent. fn event(&mut self, key: KeyEvent) -> Result { - // If they key event is filter and the ProcessComponent Focus is on the List, then move the focus to Filter and return. if key.code == self.config.key_config.filter && self.focus == Focus::List { self.focus = Focus::Filter; + return Ok(EventState::Consumed) } - // If the ProcessComponent Focus is on the Filter, then attempt to set the filtered_list. - // If the filter's input string is None, then set the filtered_list to None (no List to display), - // else create the filtered_list calling list.filter(input_str). - if matches!(self.focus, Focus::Filter) { - self.filtered_list = if self.filter.input_str().is_empty() { - None - } - else { - Some(self.list.filter(self.filter.input_str())) - }; - } - if matches!(self.focus, Focus::Filter) { if self.filter.event(key)?.is_consumed() { + self.filtered_list = if self.filter.input_str().is_empty() { + None + } + else { + Some(self.list.filter(self.filter.input_str())) + }; + return Ok(EventState::Consumed) } if key.code == self.config.key_config.enter { self.focus = Focus::List; + return Ok(EventState::Consumed) } } if matches!(self.focus, Focus::List) { - // Check if the key input is to navigate the list. If there is some filtered list, then that is the - // list we want to interact with, else interact with the unfiltered list. if list_nav( if let Some(list) = self.filtered_list.as_mut() { list @@ -128,8 +109,7 @@ impl Component for ProcessComponent { return Ok(EventState::Consumed); } - // Check if the key is to change the follow_selection value. - else if key.code == self.config.key_config.follow_selection { + if key.code == self.config.key_config.follow_selection { if let Some(filtered_list) = self.filtered_list.as_mut() { filtered_list.toggle_follow_selection(); } @@ -140,8 +120,6 @@ impl Component for ProcessComponent { return Ok(EventState::Consumed) } - // Check if the key input is to sort the list. If there is some filtered list, then that is the - // list we want to interact with, else interact with unfiltered list. else if list_sort( if let Some(list) = self.filtered_list.as_mut() { list @@ -160,13 +138,10 @@ impl Component for ProcessComponent { } } - -// Function calls common_nav, common_nav checks if key can be consumed, if so, -// Some(MoveSelection) is returned and list.move_selection(MoveSelection) is called. -// Else return false. fn list_nav(list: &mut ProcessList, key: KeyEvent, key_config: &KeyConfig) -> bool { if let Some(move_dir) = common_nav(key, key_config) { list.move_selection(move_dir); + true } else { @@ -174,12 +149,10 @@ fn list_nav(list: &mut ProcessList, key: KeyEvent, key_config: &KeyConfig) -> bo } } -// Function calls common_sort, common_sort checks if key can be consumed, if so, -// Some(ListSortOrder) is returned and list.sort(ListSortOrder) is called. -// Else return false. fn list_sort(list: &mut ProcessList, key: KeyEvent, key_config: &KeyConfig) -> Result { if let Some(sort) = common_sort(key, key_config) { list.sort(&sort); + Ok(true) } else { @@ -189,18 +162,15 @@ fn list_sort(list: &mut ProcessList, key: KeyEvent, key_config: &KeyConfig) -> R impl DrawableComponent for ProcessComponent { fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()> { - // split for filter let horizontal_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Fill(1), - Constraint::Length(3), + Constraint::Fill(1), //list + Constraint::Length(3), //filter ]).split(area); - // calculate visible list height let visible_list_height = horizontal_chunks[0].height.saturating_sub(3) as usize; - // determine list to display let list = if let Some(filtered_list) = self.filtered_list.as_ref() { filtered_list } @@ -208,7 +178,6 @@ impl DrawableComponent for ProcessComponent { &self.list }; - // updating the scroll struct--calculates the position at the top of the displayed list list.selection().map_or_else( { || self.scroll.reset() @@ -238,7 +207,8 @@ impl DrawableComponent for ProcessComponent { else { false }, - self.config.theme_config.clone() + self.config.theme_config.clone(), + list.get_sort_order(), ); self.scroll.draw( @@ -267,6 +237,134 @@ impl DrawableComponent for ProcessComponent { } } +use ratatui::widgets::{block::*, *}; +use process_list::ListIterator; +use crate::config::ThemeConfig; + +fn draw_process_list( + f: &mut Frame, + area: Rect, + visible_items: ListIterator<'_>, + follow_selection: bool, + focus: bool, + theme_config: ThemeConfig, + sort_order: &ListSortOrder, +) { + let follow_flag = follow_selection; + + // setting header + let header = ["", + if matches!(sort_order, ListSortOrder::PidInc) { "PID ▲" } + else if matches!(sort_order, ListSortOrder::PidDec) { "PID ▼" } + else { "PID" }, + + if matches!(sort_order, ListSortOrder::NameInc) { "Name ▲" } + else if matches!(sort_order, ListSortOrder::NameDec) { "Name ▼" } + else { "Name" }, + + if matches!(sort_order, ListSortOrder::CpuUsageInc) { "CPU (%) ▲" } + else if matches!(sort_order, ListSortOrder::CpuUsageDec) { "CPU (%) ▼" } + else { "CPU (%)" }, + + if matches!(sort_order, ListSortOrder::MemoryUsageInc) { "Memory (MB) ▲" } + else if matches!(sort_order, ListSortOrder::MemoryUsageDec) { "Memory (MB) ▼" } + else { "Memory (MB)" }, + + "Run (hh:mm:ss)", + "Status", + "Path"] + .into_iter() + .map(Cell::from) + .collect::() + .style( + if focus { + theme_config.style_border_focused + } + else { + theme_config.style_border_not_focused + } + ) + .height(1); + + // setting rows + let rows = visible_items + .map(|(item, selected)| { + let style = + if focus && selected && follow_flag { + theme_config.style_item_selected_followed + } + else if focus && selected && !follow_flag { + theme_config.style_item_selected + } + else if focus { + theme_config.style_item_focused + } + else if !focus && selected & follow_flag { + theme_config.style_item_selected_followed_not_focused + } + else if !focus && selected & !follow_flag { + theme_config.style_item_selected_not_focused + } + else { + theme_config.style_item_not_focused + } + ; + + let cells: Vec = vec![ + if style == theme_config.style_item_selected || + style == theme_config.style_item_selected_followed || + style == theme_config.style_item_selected_followed_not_focused || + style == theme_config.style_item_selected_not_focused { + Cell::from(String::from("->")) + } + else { + Cell::from(String::from("")) + }, + Cell::from(item.pid().to_string()), + Cell::from(item.name().to_string()), + Cell::from(format!("{:.2}", item.cpu_usage())), + Cell::from(format!("{:.2}", item.memory_usage()/1000000)), + Cell::from(item.run_time_hh_mm_ss()), + Cell::from(item.status()), + Cell::from(item.path()), + ]; + Row::new(cells).style(style) + }) + .collect::>(); + + // setting the width constraints. + let widths = + vec![ + Constraint::Length(2), + Constraint::Length(10), // pid + Constraint::Length(50), // name + Constraint::Length(15), // cpu usage + Constraint::Length(15), // memory usage + Constraint::Length(20), // run time + Constraint::Length(15), // status + Constraint::Min(0), // path + ]; + + // setting block information + let block_title: &str = " Process List "; + let block_style = + if focus { + theme_config.style_border_focused + } + else { + theme_config.style_border_not_focused + }; + + // setting the table + let table = Table::new(rows, widths) + .header(header) + .block(Block::default().borders(Borders::ALL).title(block_title)) + .style(block_style); + + // render + f.render_widget(table, area); +} + #[cfg(test)] mod test { // TODO: write tests diff --git a/src/components/sysinfo_wrapper.rs b/src/components/sysinfo_wrapper.rs index e5ae1f2..61dd94f 100644 --- a/src/components/sysinfo_wrapper.rs +++ b/src/components/sysinfo_wrapper.rs @@ -1,6 +1,8 @@ -use sysinfo::{Pid, System}; +use sysinfo::{Pid, System, Components}; use process_list::ProcessListItem; -use bounded_queue::CpuItem; +use bounded_queue::MemoryItem; +use bounded_queue::TempItem; +use bounded_queue::{CpuItem}; use crate::config::Config; // See here for refreshing system: https://crates.io/crates/sysinfo#:~:text=use%20sysinfo%3A%3ASystem,(sysinfo%3A%3AMINIMUM_CPU_UPDATE_INTERVAL)%3B%0A%7D @@ -29,9 +31,6 @@ impl SysInfoWrapper { 0, self.system.global_cpu_usage(), 0, - String::from("Global"), - String::from(""), - String::from("") )); for (id, cpu) in self.system.cpus().iter().enumerate() { @@ -39,9 +38,6 @@ impl SysInfoWrapper { id + 1, // id=0 reserved for global cpu usage cpu.cpu_usage(), cpu.frequency(), - String::from(cpu.name()), - String::from(cpu.brand()), - String::from(cpu.vendor_id()), ); cpus.push(cpu_item); @@ -77,14 +73,28 @@ impl SysInfoWrapper { let status = process.status().to_string(); + let path = if let Some(path) = process.exe() { + if let Some(path) = path.to_str() { + path.to_string() + } + else { + String::from("Non-valid Unicode") + } + } + else { + String::from("Permission Denied") + }; + let item = ProcessListItem::new( pid.as_u32(), - name, cpu_usage, + name, + cpu_usage, memory_usage, start_time, run_time, accumulated_cpu_time, - status + status, + path, ); processes.push(item); @@ -93,6 +103,56 @@ impl SysInfoWrapper { processes } + pub fn get_memory(&self) -> MemoryItem { + let total_memory = self.system.total_memory(); // total memory is size of RAM in bytes + let used_memory = self.system.used_memory(); // used memory is allocated memory + let total_swap = self.system.total_swap(); + let used_swap = self.system.used_swap(); + + let memory_item = MemoryItem::new(total_memory, used_memory, total_swap, used_swap); + + memory_item + } + + pub fn get_temps(&self) -> Vec { + let mut temps: Vec = Vec::new(); + + let components = Components::new_with_refreshed_list(); // it seems that this is separate from system, might need to add as Struct field, although, there is no refresh method? + + for component in &components { + let temp = if let Some(temp) = component.temperature() { + temp + } + else { + 0_f32 + }; + + let max_temp = if let Some(max_temp) = component.max() { + max_temp + } + else { + 0_f32 + }; + + let critical_temp = if let Some(critical_temp) = component.critical() { + critical_temp + } + else { + 0_f32 + }; + + let label = component.label().to_string(); + + + let item = TempItem::new(temp, max_temp, critical_temp, label); + + temps.push(item); + } + + temps + } + + pub fn terminate_process(&mut self, pid: u32) -> bool { let mut res = false; diff --git a/src/components/temp.rs b/src/components/temp.rs new file mode 100644 index 0000000..678b407 --- /dev/null +++ b/src/components/temp.rs @@ -0,0 +1,243 @@ +// make similar to CPU + +/* + +pub struct CPUComponent { + cpus: BTreeMap>, + ui_selection: usize, + config: Config, +} + +*/ +use std::collections::BTreeMap; +use bounded_queue::BoundedQueue; +use crate::{components::EventState, config::Config}; +use bounded_queue::TempItem; +use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, Gauge, GraphType, List, ListItem, ListState, Table}; +use anyhow::{Ok, Result}; +use super::{Component, DrawableComponent}; +use ratatui::{ + style::{Style, Stylize}, +}; + +pub struct TempComponent { + config: Config, + temps: BTreeMap>, + ui_selection: usize, +} + +impl TempComponent { + pub fn new(config: Config) -> Self { + let temps: BTreeMap> = BTreeMap::new(); + + Self { + config, + temps, + ui_selection: 0, + } + } + + pub fn update(&mut self, temp_items: Vec) { + for temp_item in temp_items { + let key = temp_item.label().to_string(); + + let queue = self.temps.entry(key).or_insert_with(|| { + BoundedQueue::new(self.config.events_per_min() as usize) + }); + + queue.add_item(temp_item); + } + } +} + +impl Component for TempComponent { + fn event(&mut self, key: crossterm::event::KeyEvent) -> Result { + let temps_max_idx = self.temps.len() - 1; + + if key.code == self.config.key_config.move_down { + if self.ui_selection < temps_max_idx { + self.ui_selection = self.ui_selection.saturating_add(1); + } + return Ok(super::EventState::Consumed); + } + if key.code == self.config.key_config.move_up { + self.ui_selection = self.ui_selection.saturating_sub(1); + return Ok(super::EventState::Consumed); + } + + Ok(super::EventState::NotConsumed) + } +} + + +// I can't get critical temp values on Mac and potentially Windows. +// Instead of doing gauges for temp, I'm just going to report as a table. +// Also, sorting by String, messes up the order e.g., item0 -> item1 -> item10 -> item2 +// TODO: Do not use boundedqueue, just create a Vector of TempItems. +/* + let header = ["", + if matches!(sort_order, ListSortOrder::PidInc) { "PID ▲" } + else if matches!(sort_order, ListSortOrder::PidDec) { "PID ▼" } + else { "PID" }, + + if matches!(sort_order, ListSortOrder::NameInc) { "Name ▲" } + else if matches!(sort_order, ListSortOrder::NameDec) { "Name ▼" } + else { "Name" }, + + if matches!(sort_order, ListSortOrder::CpuUsageInc) { "CPU (%) ▲" } + else if matches!(sort_order, ListSortOrder::CpuUsageDec) { "CPU (%) ▼" } + else { "CPU (%)" }, + + if matches!(sort_order, ListSortOrder::MemoryUsageInc) { "Memory (MB) ▲" } + else if matches!(sort_order, ListSortOrder::MemoryUsageDec) { "Memory (MB) ▼" } + else { "Memory (MB)" }, + + "Run (hh:mm:ss)", + "Status", + "Path"] + .into_iter() + .map(Cell::from) + .collect::() + .style( + if focus { + theme_config.style_border_focused + } + else { + theme_config.style_border_not_focused + } + ) + .height(1); + + // setting rows + let rows = visible_items + .map(|(item, selected)| { + let style = + if focus && selected && follow_flag { + theme_config.style_item_selected_followed + } + else if focus && selected && !follow_flag { + theme_config.style_item_selected + } + else if focus { + theme_config.style_item_focused + } + else if !focus && selected & follow_flag { + theme_config.style_item_selected_followed_not_focused + } + else if !focus && selected & !follow_flag { + theme_config.style_item_selected_not_focused + } + else { + theme_config.style_item_not_focused + } + ; + + let cells: Vec = vec![ + if style == theme_config.style_item_selected || + style == theme_config.style_item_selected_followed || + style == theme_config.style_item_selected_followed_not_focused || + style == theme_config.style_item_selected_not_focused { + Cell::from(String::from("->")) + } + else { + Cell::from(String::from("")) + }, + Cell::from(item.pid().to_string()), + Cell::from(item.name().to_string()), + Cell::from(format!("{:.2}", item.cpu_usage())), + Cell::from(format!("{:.2}", item.memory_usage()/1000000)), + Cell::from(item.run_time_hh_mm_ss()), + Cell::from(item.status()), + Cell::from(item.path()), + ]; + Row::new(cells).style(style) + }) + .collect::>(); + + // setting the width constraints. + let widths = + vec![ + Constraint::Length(2), + Constraint::Length(10), // pid + Constraint::Length(50), // name + Constraint::Length(15), // cpu usage + Constraint::Length(15), // memory usage + Constraint::Length(20), // run time + Constraint::Length(15), // status + Constraint::Min(0), // path + ]; + + // setting block information + let block_title: &str = " Process List "; + let block_style = + if focus { + theme_config.style_border_focused + } + else { + theme_config.style_border_not_focused + }; + + // setting the table + let table = Table::new(rows, widths) + .header(header) + .block(Block::default().borders(Borders::ALL).title(block_title)) + .style(block_style); + + // render + f.render_widget(table, area); +} +*/ +impl DrawableComponent for TempComponent { + fn draw(&mut self, f: &mut ratatui::Frame, area: ratatui::prelude::Rect, focused: bool) -> Result<()> { + // get the temp component selected + let selection = self.ui_selection; + + //let header = ["Sensor", "Temp", "Max Temp", "Critical Temp"]; + + //let rows = self.temps + // .iter() + // .map(|item| {}) + + //let table = Table::new(rows, widths) + //.header(header) + // .block(Block::default().borders(Borders::ALL).title(block_title)) + // .style(block_style); + + + // populate names for UIList to draw temp component list + let mut names: Vec = self.temps + .iter() + .map(|(key, queue)| { + let title = format!("{} {}", key.to_string(), queue.back().unwrap().critical_temp()); + ListItem::new(title) + }) + .collect(); + + let mut list_state = ListState::default(); + + list_state.select(Some(self.ui_selection)); + + let temp_list = List::new(names) + .scroll_padding(area.height as usize / 2) + .block( + if !focused { + Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_not_focused) + } + else { + Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_focused) + } + ) + .highlight_style( + if !focused { + self.config.theme_config.style_item_selected_not_focused + } + else { + self.config.theme_config.style_item_selected + } + ); + + f.render_stateful_widget(temp_list, area, &mut list_state); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 02d3f42..fc7a9d9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,6 +11,7 @@ pub struct Config { tick_rate: u64, } +//times are in ms impl Default for Config { fn default() -> Self { Self { @@ -55,7 +56,7 @@ pub struct KeyConfig { pub tab_right: KeyCode, pub tab_left: KeyCode, pub open_help: KeyCode, - pub exit_popup: KeyCode, + pub exit: KeyCode, pub sort_name_inc: KeyCode, pub sort_name_dec: KeyCode, pub sort_pid_inc: KeyCode, @@ -74,21 +75,17 @@ impl Default for KeyConfig { fn default() -> Self { Self { move_up: KeyCode::Up, - //move_up: KeyCode::Char('w'), move_top: KeyCode::Char('W'), move_down: KeyCode::Down, - //move_down: KeyCode::Char('s'), move_bottom: KeyCode::Char('S'), enter: KeyCode::Enter, tab: KeyCode::Tab, filter: KeyCode::Char('/'), terminate: KeyCode::Char('T'), tab_right: KeyCode::Right, - //tab_right: KeyCode::Char('d'), tab_left: KeyCode::Left, - //tab_left: KeyCode::Char('a'), open_help: KeyCode::Char('?'), - exit_popup: KeyCode::Esc, + exit: KeyCode::Esc, sort_name_inc: KeyCode::Char('n'), sort_name_dec: KeyCode::Char('N'), sort_pid_inc: KeyCode::Char('p'), @@ -105,66 +102,31 @@ impl Default for KeyConfig { } } -#[derive(Clone,PartialEq,Serialize,Deserialize)] -pub enum ThemeVariant { - Dark, - Light, -} -use ratatui::prelude::{Style,Color,Modifier}; +use ratatui::prelude::{Color, Modifier, Style}; #[derive(Clone,PartialEq,Serialize,Deserialize)] pub struct ThemeConfig { - pub theme_variant: ThemeVariant, - pub list_header: Style, - pub item_style: Style, //default - pub item_select: Style, - pub item_select_follow: Style, - pub component_out_of_focus: Style, - pub component_in_focus: Style, -} - -impl ThemeConfig { - fn set_dark_theme(&mut self) { - self.theme_variant = ThemeVariant::Dark; - self.list_header = Style::default().fg(Color::Black).bg(Color::Gray); - self.item_style = Style::default().fg(Color::White); - self.item_select = Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD); - self.item_select_follow = Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED); - self.component_out_of_focus = Style::default().fg(Color::DarkGray); - //self.component_in_focus = Style::default().fg(Color::LightGreen); - self.component_in_focus = Style::default().fg(Color::White); - } - - fn set_light_theme(&mut self) { - self.theme_variant = ThemeVariant::Light; - self.list_header = Style::default().fg(Color::White).bg(Color::Black); - self.item_style = Style::default().fg(Color::Black); - self.item_select = Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD); - self.item_select_follow = Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED); - self.component_out_of_focus = Style::default().fg(Color::DarkGray); - self.component_in_focus = Style::default().fg(Color::LightGreen); - } - - pub fn toggle_themes(&mut self) { - if self.theme_variant == ThemeVariant::Dark { - self.set_light_theme(); - return - } - self.set_dark_theme(); - } + pub style_border_focused: Style, + pub style_border_not_focused: Style, + pub style_item_focused: Style, + pub style_item_not_focused: Style, + pub style_item_selected: Style, + pub style_item_selected_not_focused: Style, + pub style_item_selected_followed: Style, + pub style_item_selected_followed_not_focused: Style, } impl Default for ThemeConfig { fn default() -> Self { Self { - theme_variant: ThemeVariant::Dark, - list_header: Style::default().fg(Color::Black).bg(Color::Gray), - item_style: Style::default().fg(Color::White), - item_select: Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD), - item_select_follow: Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED), - component_out_of_focus: Style::default().fg(Color::DarkGray), - component_in_focus: Style::default().fg(Color::LightGreen), - //component_in_focus: Style::default().fg(Color::White), + style_border_focused: Style::default().fg(Color::LightGreen), + style_border_not_focused: Style::default().fg(Color::DarkGray), + style_item_focused: Style::default().fg(Color::White), + style_item_not_focused: Style::default().fg(Color::DarkGray), + style_item_selected: Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD), + style_item_selected_not_focused: Style::default().bg(Color::Gray).add_modifier(Modifier::BOLD), + style_item_selected_followed: Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED), + style_item_selected_followed_not_focused: Style::default().bg(Color::Gray).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED), } } } diff --git a/src/events/event.rs b/src/events/event.rs index 9899bf2..dfe1dbf 100644 --- a/src/events/event.rs +++ b/src/events/event.rs @@ -1,10 +1,18 @@ -use crossterm::event::{self, KeyEvent, KeyCode, Event as CEvent, KeyEventKind}; -use std::sync::mpsc; -use std::{thread, time::Duration}; +use crossterm::event::{ + self, + KeyEvent, + Event as CEvent, + KeyEventKind +}; + +use std::{ + thread, + time::Duration, + sync::mpsc +}; #[derive(Clone, Copy)] pub struct EventConfig { - pub exit_key: KeyCode, pub tick_rate: Duration, pub refresh_rate: Duration, } @@ -12,20 +20,13 @@ pub struct EventConfig { impl Default for EventConfig { fn default() -> Self { Self { - // not sure if needed? - exit_key: KeyCode::Char('q'), tick_rate: Duration::from_millis(250), - // note: Minimum cpu refresh time is 200 ms-- this is the lower bound of refresh_rate refresh_rate: Duration::from_millis(10000), } } } -// Three possible Event type variants: -// 1. Input(K) where K is a KeyEvent -> "process this `K` Keyevent" -// 2. Tick -> "stop waiting for KeyEvent input and move on to next instruction in main" -// 3. Refresh -> "refresh system" -// + #[derive(Clone, Copy)] pub enum Event { Input(K), @@ -33,8 +34,6 @@ pub enum Event { Refresh, } -// Struct Events includes an async sender/receiver -// pub struct Events { rx: mpsc::Receiver>, _tx: mpsc::Sender>, @@ -50,11 +49,11 @@ impl Events { } pub fn with_config(config: EventConfig) -> Events { - // link sender and receiver on mpsc channel let (tx, rx) = mpsc::channel(); - - // thread for sending key events and tick events let input_tx = tx.clone(); + let tick_tx = tx.clone(); + let refresh_tx = tx.clone(); + thread::spawn(move || loop { if event::poll(config.tick_rate).unwrap() { if let Ok(event) = event::read() { @@ -65,17 +64,18 @@ impl Events { } } } - input_tx.send(Event::Tick).unwrap(); }); - // thread for sending refresh events -> refresh system - let refresh_tx = tx.clone(); + thread::spawn(move || loop { + thread::sleep(config.tick_rate); + tick_tx.send(Event::Tick).unwrap(); + }); + thread::spawn(move || loop { thread::sleep(config.refresh_rate); refresh_tx.send(Event::Refresh).unwrap(); }); - // return receiver/sender pair Events { rx, _tx: tx } } diff --git a/src/main.rs b/src/main.rs index 72b1ace..26542bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,34 +17,27 @@ use crate::app::App; pub mod app; pub mod config; pub mod components; -pub mod ui; pub mod events; +// If the program's view of the World is incorrect, crash the program, don't hide false beliefs. fn main() -> Result<()> { - // terminal setup - setup_terminal()?; + enable_raw_mode()?; + io::stdout().execute(EnterAlternateScreen)?; + let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; - // app creation and initialization let config = config::Config::default(); let tick_rate = config.tick_rate(); let refresh_rate = config.refresh_rate(); - // event handler setup - // argument 1: tick_rate , argument 2: system refresh_rate + let events = Events::new(tick_rate, refresh_rate); let mut app = App::new(config); - - //app.init()?; - - // clear terminal terminal.clear()?; - // main event loop loop { - // draw to terminal terminal.draw(|f| { match app.draw(f) { Ok(_state) => {} @@ -55,11 +48,10 @@ fn main() -> Result<()> { } })?; - // process next event match events.next()? { Event::Input(key) => match app.key_event(key) { Ok(state) => { - if !state.is_consumed() && key.code == app.config.key_config.exit_popup { + if !state.is_consumed() && key.code == app.config.key_config.exit { break; } } @@ -73,7 +65,9 @@ fn main() -> Result<()> { app.error.set(err.to_string())?; } } - Event::Tick => {} + Event::Tick => { + continue + } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index c225c1c..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod process_list_ui; \ No newline at end of file diff --git a/src/ui/process_list_ui.rs b/src/ui/process_list_ui.rs deleted file mode 100644 index c781a5a..0000000 --- a/src/ui/process_list_ui.rs +++ /dev/null @@ -1,107 +0,0 @@ -pub mod process_list_ui { - use ratatui::{ - Frame, - prelude::*, - widgets::{block::*, *}, - }; - use process_list::ListIterator; - use crate::config::ThemeConfig; - - pub fn draw_process_list( - f: &mut Frame, - area: Rect, - visible_items: ListIterator<'_>, - follow_selection: bool, - focus: bool, - theme_config: ThemeConfig, - ) { - let follow_flag = follow_selection; - let select_style = theme_config.item_select; - let select_follow_style = theme_config.item_select_follow; - let default_style = theme_config.item_style; - let out_of_focus_style = theme_config.component_out_of_focus; - let in_focus_style = theme_config.component_in_focus; - - // setting header - let header = ["", "Pid", "Name", "CPU (%)", "Memory (B)", "Runtime (s)", "Status"] - .into_iter() - .map(Cell::from) - .collect::() - .style( - if focus { - in_focus_style - } - else { - out_of_focus_style - } - ) - .height(1); - - // setting rows - let rows = visible_items - .map(|(item, selected)| { - let style = - if focus && selected && follow_flag { - Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED) - } - else if focus && selected && !follow_flag { - Style::default().bg(Color::Blue).add_modifier(Modifier::BOLD) - } - else if focus { - default_style - } - else { - out_of_focus_style - }; - - let cells: Vec = vec![ - if style == select_style || style == select_follow_style { - Cell::from(String::from("->")) - } - else { - Cell::from(String::from("")) - }, - Cell::from(item.pid().to_string()), - Cell::from(item.name().to_string()), - Cell::from(item.cpu_usage().to_string()), - Cell::from(item.memory_usage().to_string()), - Cell::from(item.run_time().to_string()), - Cell::from(item.status()), - ]; - Row::new(cells).style(style) - }) - .collect::>(); - - // setting the width constraints. - let widths = - vec![ - Constraint::Length(2), - Constraint::Length(10), // pid - Constraint::Length(50), // name - Constraint::Length(20), // cpu usage - Constraint::Length(20), // memory usage - Constraint::Length(20), // run time - Constraint::Length(20), // status - ]; - - // setting block information - let block_title: &str = " Process List "; - let block_style = - if focus { - in_focus_style - } - else { - out_of_focus_style - }; - - // setting the table - let table = Table::new(rows, widths) - .header(header) - .block(Block::default().borders(Borders::ALL).title(block_title)) - .style(block_style); - - // render - f.render_widget(table, area); - } - -} From 9c143779d1d46c24765a508189f9d711c4543aea Mon Sep 17 00:00:00 2001 From: rhasler1 Date: Mon, 7 Jul 2025 13:20:53 -0500 Subject: [PATCH 2/3] removing repeat allocations from ProcessList --- Cargo.lock | 8 - Cargo.toml | 2 - src/app.rs | 12 +- src/components/mod.rs | 4 +- src/components/process.rs | 28 +-- src/components/sysinfo_wrapper.rs | 57 ++++- src/main.rs | 1 + src/models/list_items_iter.rs | 50 ++++ src/models/list_iter.rs | 26 +++ src/models/mod.rs | 5 + src/models/process_list.rs | 372 ++++++++++++++++++++++++++++++ src/models/process_list_item.rs | 156 +++++++++++++ src/models/process_list_items.rs | 230 ++++++++++++++++++ 13 files changed, 917 insertions(+), 34 deletions(-) create mode 100644 src/models/list_items_iter.rs create mode 100644 src/models/list_iter.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/process_list.rs create mode 100644 src/models/process_list_item.rs create mode 100644 src/models/process_list_items.rs diff --git a/Cargo.lock b/Cargo.lock index af4a172..4a92389 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,19 +365,11 @@ dependencies = [ "crossterm", "dynamic-list", "itertools 0.10.5", - "process-list", "ratatui", "serde", "sysinfo", ] -[[package]] -name = "process-list" -version = "0.1.0" -dependencies = [ - "itertools 0.10.5", -] - [[package]] name = "quote" version = "1.0.36" diff --git a/Cargo.toml b/Cargo.toml index 7822f8e..fd2dca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [workspace] members = [ - "process-list", "bounded-queue", "dynamic-list" ] @@ -19,7 +18,6 @@ ratatui = { version = "0.26.0", features = ["serde", "macros"] } serde = { version = "1.0.188", features = ["derive"] } anyhow = "1.0.97" itertools = "0.10.0" -process-list = {path = "./process-list", version = "0.1.0"} bounded-queue = {path = "./bounded-queue", version = "0.1.0"} dynamic-list = {path = "./dynamic-list", version = "0.1.0"} clippy = "0.0.302" diff --git a/src/app.rs b/src/app.rs index e4e4bc8..6dd2cb2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,7 @@ use anyhow::{Ok, Result}; use crossterm::event::KeyEvent; use ratatui::prelude::*; +use crate::components::sysinfo_wrapper; use crate::components::temp::TempComponent; use crate::config::Config; use crate::components::{ @@ -43,7 +44,7 @@ impl App { system_wrapper.refresh_all(); - let processes = system_wrapper.get_processes(); + let process = ProcessComponent::new(config.clone(), &system_wrapper); let mut cpu = CPUComponent::default(); let cpus = system_wrapper.get_cpus(); @@ -57,11 +58,12 @@ impl App { let mut temp = TempComponent::new(config.clone()); temp.update(temp_items); + Self { focus: MainFocus::Process, expand: false, system_wrapper, - process: ProcessComponent::new(config.clone(), processes), + process, cpu, memory, temp, @@ -74,7 +76,7 @@ impl App { pub fn refresh_event(&mut self) -> Result { self.system_wrapper.refresh_all(); - self.update_process(); + self.process.update(&self.system_wrapper); self.update_cpu(); @@ -97,11 +99,11 @@ impl App { self.memory.update(memory_item); } - fn update_process(&mut self) { + /*fn update_process(&mut self) { let new_processes = self.system_wrapper.get_processes(); // receive ownership self.process.update(new_processes); // transfer ownership - } + }*/ fn update_cpu(&mut self) { let new_cpus = self.system_wrapper.get_cpus(); // receive ownership diff --git a/src/components/mod.rs b/src/components/mod.rs index 090ffcf..765b262 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crossterm::event::KeyEvent; use ratatui::prelude::*; -use process_list::{ListSortOrder, MoveSelection}; +use crate::models::process_list::{ListSortOrder, MoveSelection}; use super::config::KeyConfig; pub mod sysinfo_wrapper; pub mod filter; @@ -39,7 +39,7 @@ pub fn common_nav(key: KeyEvent, key_config: &KeyConfig) -> Option) -> Self { + pub fn new(config: Config, sysinfo: &SysInfoWrapper) -> Self { Self { focus: Focus::List, - list: ProcessList::new(processes), + list: ProcessList::new(sysinfo), filter: FilterComponent::new(config.clone()), filtered_list: None, scroll: VerticalScroll::new(), @@ -39,18 +42,11 @@ impl ProcessComponent { } } - pub fn update(&mut self, new_processes: Vec) { - assert!(!new_processes.is_empty()); - - let dup = new_processes.clone(); - - self.list.update(new_processes); + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + self.list.update(sysinfo, ""); if let Some(filtered_list) = self.filtered_list.as_mut() { - let processes = ProcessListItems::new(dup); - let filtered_processes = processes.filter(self.filter.input_str()); - - filtered_list.update(filtered_processes.list_items); + filtered_list.update(sysinfo, self.filter.input_str()); } } @@ -208,7 +204,7 @@ impl DrawableComponent for ProcessComponent { false }, self.config.theme_config.clone(), - list.get_sort_order(), + list.sort_order(), ); self.scroll.draw( @@ -238,7 +234,7 @@ impl DrawableComponent for ProcessComponent { } use ratatui::widgets::{block::*, *}; -use process_list::ListIterator; +use crate::models::list_iter::ListIterator; use crate::config::ThemeConfig; fn draw_process_list( diff --git a/src/components/sysinfo_wrapper.rs b/src/components/sysinfo_wrapper.rs index 61dd94f..d1d3d85 100644 --- a/src/components/sysinfo_wrapper.rs +++ b/src/components/sysinfo_wrapper.rs @@ -1,5 +1,5 @@ use sysinfo::{Pid, System, Components}; -use process_list::ProcessListItem; +use crate::models::process_list_item::ProcessListItem; use bounded_queue::MemoryItem; use bounded_queue::TempItem; use bounded_queue::{CpuItem}; @@ -46,6 +46,61 @@ impl SysInfoWrapper { cpus } + pub fn get_processes_test(&self, processes: &mut Vec) { + processes.clear(); + + for (pid, process) in self.system.processes() { + let name = if let Some(name) = process.name().to_str() { + String::from(name) + } + else { + String::from("No name") + }; + let cpu_usage = if let Some(core_count) = sysinfo::System::physical_core_count() { + process.cpu_usage() / core_count as f32 // normalizing process cpu usage by the number of cores + } + else { + process.cpu_usage() + }; + + let memory_usage = process.memory(); + + let start_time = process.start_time(); + + let run_time = process.run_time(); + + let accumulated_cpu_time = process.accumulated_cpu_time(); + + let status = process.status().to_string(); + + let path = if let Some(path) = process.exe() { + if let Some(path) = path.to_str() { + path.to_string() + } + else { + String::from("Non-valid Unicode") + } + } + else { + String::from("Permission Denied") + }; + + let item = ProcessListItem::new( + pid.as_u32(), + name, + cpu_usage, + memory_usage, + start_time, + run_time, + accumulated_cpu_time, + status, + path, + ); + + processes.push(item); + } + } + pub fn get_processes(&self) -> Vec { let mut processes: Vec = Vec::new(); diff --git a/src/main.rs b/src/main.rs index 26542bd..377287c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ pub mod app; pub mod config; pub mod components; pub mod events; +pub mod models; // If the program's view of the World is incorrect, crash the program, don't hide false beliefs. fn main() -> Result<()> { diff --git a/src/models/list_items_iter.rs b/src/models/list_items_iter.rs new file mode 100644 index 0000000..2042719 --- /dev/null +++ b/src/models/list_items_iter.rs @@ -0,0 +1,50 @@ +use super::process_list_item::ProcessListItem; +use super::process_list_items::ProcessListItems; + +pub struct ListItemsIterator<'a> { + list: &'a ProcessListItems, + index: usize, + increments: Option, + max_amount: usize, +} + +impl <'a> ListItemsIterator<'a> { + pub const fn new(list: &'a ProcessListItems, start: usize, max_amount: usize) -> Self { + Self { + list, + index: start, + increments: None, + max_amount, + } + } +} + +impl<'a> Iterator for ListItemsIterator<'a> { + type Item = (usize, &'a ProcessListItem); + + // required function for Iterator + fn next(&mut self) -> Option { + if self.increments.unwrap_or_default() < self.max_amount { + let items = &self.list.list_items; + let init = self.increments.is_none(); + + if let Some(i) = self.increments.as_mut() { + *i += 1; + } + else { + self.increments = Some(0); + }; + + if !init { + self.index += 1; + } + + if self.index >= self.list.list_items.len() { + return None + } + + return Some((self.index, &items[self.index])); + } + None + } +} \ No newline at end of file diff --git a/src/models/list_iter.rs b/src/models/list_iter.rs new file mode 100644 index 0000000..bca7cef --- /dev/null +++ b/src/models/list_iter.rs @@ -0,0 +1,26 @@ +use super::list_items_iter::ListItemsIterator; +use super::process_list_item::ProcessListItem; + +pub struct ListIterator<'a> { + item_iter: ListItemsIterator<'a>, + selection: Option, +} + +impl<'a> ListIterator<'a> { + pub const fn new(item_iter: ListItemsIterator<'a>, selection: Option) -> Self { + Self { + item_iter, + selection, + } + } +} + +impl<'a> Iterator for ListIterator<'a> { + type Item = (&'a ProcessListItem, bool); + + fn next(&mut self) -> Option { + self.item_iter + .next() + .map(|(index, item)| (item, self.selection.map(|i| i == index).unwrap_or_default())) + } +} \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..b33826a --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,5 @@ +pub mod process_list; +pub mod process_list_items; +pub mod process_list_item; +pub mod list_items_iter; +pub mod list_iter; \ No newline at end of file diff --git a/src/models/process_list.rs b/src/models/process_list.rs new file mode 100644 index 0000000..77c5c82 --- /dev/null +++ b/src/models/process_list.rs @@ -0,0 +1,372 @@ +use super::process_list_items::ProcessListItems; +use super::process_list_item::ProcessListItem; +use super::list_iter::ListIterator; +use crate::components::sysinfo_wrapper::SysInfoWrapper; + +#[derive(PartialEq, Clone, Default)] +pub enum ListSortOrder { + PidInc, + PidDec, + NameInc, + NameDec, + CpuUsageInc, + #[default] CpuUsageDec, + MemoryUsageInc, + MemoryUsageDec, +} + +#[derive(Copy, Clone)] +pub enum MoveSelection { + Up, + Down, + MultipleUp, + MultipleDown, + Top, + Bottom, +} + +#[derive(Default)] +pub struct ProcessList { + items: ProcessListItems, + sort: ListSortOrder, + follow_selection: bool, + pub selection: Option, +} + +impl ProcessList { + pub fn new(sysinfo: &SysInfoWrapper) -> Self { + Self { + items: ProcessListItems::new(sysinfo), + sort: ListSortOrder::default(), + follow_selection: false, + selection: Some(0), + } + } + + pub fn filter(&self, filter_text: &str) -> Self { + let items = self.items.filter(filter_text); + let len = items.len(); + + Self { + items, + sort: ListSortOrder::default(), + follow_selection: false, + selection: if len > 0 { + Some(0) + } + else { + None + }, + } + } + + pub fn update(&mut self, sysinfo: &SysInfoWrapper, filter_text: &str) { + let selected_item: Option<&ProcessListItem> = self.items.get_item(self.selection.unwrap_or_default()); + let pid: Option = selected_item.map(|item| item.pid()); + + + self.items.update(sysinfo, filter_text); + + self.items.sort_items(&self.sort); + + if self.items.len() == 0 { + self.selection = None; + return + } + + if self.follow_selection { + self.selection = pid.and_then(|p| self.items.get_idx(p)); + } + else { + if let Some(selection) = self.selection { + let max_idx = self.items.len().saturating_sub(1); + if selection > max_idx { + self.selection = Some(max_idx) + } + } + } + + if self.selection.is_none() { + self.selection = Some(0) + } + } + + pub fn sort(&mut self, sort: &ListSortOrder) { + let selected_item: Option<&ProcessListItem> = self.items.get_item(self.selection.unwrap_or_default()); + + let pid: Option = selected_item.map(|item| item.pid()); + + self.items.sort_items(sort); + + self.sort = sort.clone(); + + if self.follow_selection { + self.selection = pid.and_then(|p| self.items.get_idx(p)); + } + } + + pub fn move_selection(&mut self, dir: MoveSelection) { + if let Some(selection) = self.selection() { + let new_idx = match dir { + MoveSelection::Down => self.selection_down(selection, 1), + MoveSelection::MultipleDown => self.selection_down(selection, 10), + MoveSelection::Up => self.selection_up(selection, 1), + MoveSelection::MultipleUp => self.selection_up(selection, 10), + MoveSelection::Bottom => self.selection_bottom(selection), + MoveSelection::Top => self.selection_top(selection), + }; + + self.selection = new_idx; + } + } + + fn selection_down(&self, current_idx: usize, lines: usize) -> Option { + let mut new_idx = current_idx; + let max_idx = self.items.len().saturating_sub(1); + + 'a: for _ in 0..lines { + if new_idx >= max_idx { + break 'a; + } + new_idx = new_idx.saturating_add(1); + } + + Some(new_idx) + } + + fn selection_up(&self, current_idx: usize, lines: usize) -> Option { + let mut new_idx = current_idx; + let min_idx = 0; + + 'a: for _ in 0..lines { + if new_idx == min_idx { + break 'a; + } + new_idx = new_idx.saturating_sub(1); + } + + Some(new_idx) + } + + fn selection_bottom(&self, _current_idx: usize) -> Option { + let max_idx = self.items.len().saturating_sub(1); + + Some(max_idx) + + } + + fn selection_top(&self, _current_idx: usize) -> Option { + let min_idx = 0; + + Some(min_idx) + } + + pub fn toggle_follow_selection(&mut self) { + self.follow_selection = !self.follow_selection + } + + pub fn is_follow_selection(&self) -> bool { + self.follow_selection + } + + pub fn selection(&self) -> Option { + self.selection + } + + pub fn is_empty(&self) -> bool { + self.items.len() == 0 + } + + pub fn len(&self) -> usize { + self.items.len() + } + + pub fn selected_item(&self) -> Option<&ProcessListItem> { + if let Some(selection) = self.selection { + let selected_item = self.items.get_item(selection); + return selected_item + } + + None + } + + pub fn selected_pid(&self) -> Option { + if let Some(selection) = self.selection { + if let Some(item) = self.items.get_item(selection) { + return Some(item.pid()) + } + else { + return None + } + } + + None + } + + pub fn sort_order(&self) -> &ListSortOrder { + &self.sort + } + + pub fn iterate(&self, start_index: usize, max_amount: usize) -> ListIterator<'_> { + let start = start_index; + ListIterator::new(self.items.iterate(start, max_amount), self.selection) + } +} + +/* +#[cfg(test)] +mod test { + use crate::models::process_list::{ProcessList, ListSortOrder, MoveSelection}; + use crate::models::process_list_item::ProcessListItem; + + #[test] + fn test_constructors() { + // Default constructor. + let empty_instance = ProcessList::default(); + assert!(empty_instance.is_empty()); + assert_eq!(empty_instance.selection(), None); + + // New constructor. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let instance = ProcessList::new(items); + assert!(!instance.is_empty()); + assert_eq!(instance.selection(), Some(0)); + + // Filter constructor case 1. + let filter_string = String::from("c"); + let filter_instance = instance.filter(&filter_string); + assert!(filter_instance.is_empty()); + assert_eq!(filter_instance.selection(), None); + + // Filter constructor case 2. + let filter_string = String::from("b"); + let filter_instance = instance.filter(&filter_string); + assert!(!filter_instance.is_empty()); + assert_eq!(filter_instance.selection(), Some(0)); + } + + #[test] + fn test_update() { + // Update with empty list of items. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + let empty_items = vec![]; + let _ = instance.update(empty_items); + assert!(instance.is_empty()); + assert!(instance.selection().is_none()); + + // Update with non-empty list of items. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); + let new_items = vec![item_2]; + let _ = instance.update(new_items); + assert!(!instance.is_empty()); + assert_eq!(instance.selection(), Some(0)); + + // Update with empty list of items and follow_selection set to true. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + let _ = instance.toggle_follow_selection(); + let empty_items = vec![]; + let _ = instance.update(empty_items); + assert!(instance.is_empty()); + assert!(instance.selection().is_none()); + + // Update with non-empty list of items and follow_selection set to true case 1. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + let _ = instance.toggle_follow_selection(); + let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); + let new_items = vec![item_2]; + let _ = instance.update(new_items); + assert!(!instance.is_empty()); + assert_eq!(instance.selection(), Some(0)); + + // Update with non-empty list of items and follow_selection set to true case 2. + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + let _ = instance.toggle_follow_selection(); + let item_2 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); + let new_items = vec![item_2, item_3]; + let _ = instance.update(new_items); + assert!(!instance.is_empty()); + assert_eq!(instance.selection(), Some(0)); + } + + #[test] + fn test_sort() { + // Test sort when follow_selection = false. + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_1, item_0]; + let mut instance = ProcessList::new(items); + assert!(instance.sort == ListSortOrder::CpuUsageDec); + assert!(!instance.is_follow_selection()); + assert_eq!(instance.selection(), Some(0)); + let _ = instance.sort(&ListSortOrder::CpuUsageInc); + assert_eq!(instance.selection(), Some(0)); + + + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + assert!(instance.sort == ListSortOrder::CpuUsageDec); + let _ = instance.toggle_follow_selection(); + assert!(instance.is_follow_selection()); + assert_eq!(instance.selection(), Some(0)); + let _ = instance.sort(&ListSortOrder::CpuUsageInc); + assert_eq!(instance.selection(), Some(1)); + } + + #[test] + fn test_selection() { + let mut empty_instance = ProcessList::default(); + empty_instance.move_selection(MoveSelection::Down); + assert_eq!(empty_instance.selection(), None); + + let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessList::new(items); + assert_eq!(instance.selection(), Some(0)); + instance.move_selection(MoveSelection::Down); + instance.move_selection(MoveSelection::Down); + assert_eq!(instance.selection(), Some(1)); + + instance.move_selection(MoveSelection::Up); + instance.move_selection(MoveSelection::Up); + assert_eq!(instance.selection(), Some(0)); + + instance.move_selection(MoveSelection::End); + instance.move_selection(MoveSelection::End); + assert_eq!(instance.selection(), Some(1)); + + instance.move_selection(MoveSelection::Top); + instance.move_selection(MoveSelection::Top); + assert_eq!(instance.selection(), Some(0)); + + instance.move_selection(MoveSelection::MultipleDown); + instance.move_selection(MoveSelection::MultipleDown); + assert_eq!(instance.selection(), Some(1)); + + instance.move_selection(MoveSelection::MultipleUp); + instance.move_selection(MoveSelection::MultipleUp); + assert_eq!(instance.selection(), Some(0)); + } +} +*/ \ No newline at end of file diff --git a/src/models/process_list_item.rs b/src/models/process_list_item.rs new file mode 100644 index 0000000..00ae816 --- /dev/null +++ b/src/models/process_list_item.rs @@ -0,0 +1,156 @@ +#[derive(Default, Clone, Debug)] +pub struct ProcessListItem { + pid: u32, + name: String, + cpu_usage: f32, + memory_usage: u64, + start_time: u64, + run_time: u64, + accumulated_cpu_time: u64, + status: String, + path: String, +} + +impl ProcessListItem { + pub fn new( + pid: u32, + name: String, + cpu_usage: f32, + memory_usage: u64, + start_time: u64, + run_time: u64, + accumulated_cpu_time: u64, + status: String, + path: String, + ) -> Self { + Self { + pid, + name, + cpu_usage, + memory_usage, + start_time, + run_time, + accumulated_cpu_time, + status, + path, + } + } + + // match by name or pid + //pub fn is_match(&self, filter_text: &str) -> bool { + // self.name.contains(filter_text) || + // self.pid.to_string().contains(filter_text) + //} + + pub fn pid(&self) -> u32 { + self.pid + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + pub fn memory_usage(&self) -> u64 { + self.memory_usage + } + + pub fn start_time(&self) -> u64 { + self.start_time + } + + pub fn run_time(&self) -> u64 { + self.run_time + } + + pub fn run_time_hh_mm_ss(&self) -> String { + let time_in_s = self.run_time; + + let ss = time_in_s % 60; + let mm = (time_in_s / 60) % 60; + let hh = (time_in_s / 60) / 60; + + format!("{:0>2}:{:0>2}:{:0>2}", hh, mm, ss) + } + + pub fn accumulated_cpu_time(&self) -> u64 { + self.accumulated_cpu_time + } + + pub fn status(&self) -> &str { + &self.status + } + + pub fn path(&self) -> &str { + &self.path + } +} + +// PartialEq is needed for comparison, e.g., calling contains +impl PartialEq for ProcessListItem { + fn eq(&self, other: &Self) -> bool { + self.pid.eq(&other.pid) + } +} + +#[cfg(test)] +pub mod test { + use super::ProcessListItem; + + #[test] + fn test_constructors() { + let instance = ProcessListItem::default(); + assert_eq!(instance.pid, 0); + assert!(String::is_empty(&instance.name)); + assert_eq!(instance.cpu_usage, 0.0); + assert_eq!(instance.memory_usage, 0); + assert_eq!(instance.start_time, 0); + assert_eq!(instance.run_time, 0); + assert_eq!(instance.accumulated_cpu_time, 0); + assert!(String::is_empty(&instance.status)); + + let instance = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + assert_eq!(instance.pid, 1); + assert_eq!(instance.name, String::from("a")); + assert_eq!(instance.cpu_usage, 1.0); + assert_eq!(instance.memory_usage, 1); + assert_eq!(instance.start_time, 0); + assert_eq!(instance.run_time, 10); + assert_eq!(instance.accumulated_cpu_time, 10); + assert_eq!(instance.status, String::from("test")); + + } + + #[test] + fn test_instance_functions() { + let instance_0 = ProcessListItem::default(); + let instance_1 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + + assert_eq!(instance_0.pid(), instance_0.pid); + assert_eq!(instance_0.name(), instance_0.name); + assert_eq!(instance_0.cpu_usage(), instance_0.cpu_usage); + assert_eq!(instance_0.memory_usage(), instance_0.memory_usage); + assert_eq!(instance_0.start_time(), instance_0.start_time); + assert_eq!(instance_0.run_time(), instance_0.run_time); + assert_eq!(instance_0.accumulated_cpu_time(), instance_0.accumulated_cpu_time); + assert_eq!(instance_0.status(), instance_0.status); + //assert_eq!(instance_0.is_match(""), true); + //assert_eq!(instance_0.is_match("a"), false); + //assert_eq!(instance_0.is_match(&instance_0.pid.to_string()), true); + + assert_eq!(instance_1.pid(), instance_1.pid); + assert_eq!(instance_1.name(), instance_1.name); + assert_eq!(instance_1.cpu_usage(), instance_1.cpu_usage); + assert_eq!(instance_1.memory_usage(), instance_1.memory_usage); + assert_eq!(instance_0.start_time(), instance_0.start_time); + assert_eq!(instance_0.run_time(), instance_0.run_time); + assert_eq!(instance_0.accumulated_cpu_time(), instance_0.accumulated_cpu_time); + assert_eq!(instance_0.status(), instance_0.status); + //assert_eq!(instance_1.is_match("a"), true); + //assert_eq!(instance_1.is_match("aa"), false); + //assert_eq!(instance_1.is_match(&instance_1.pid.to_string()), true); + } +} \ No newline at end of file diff --git a/src/models/process_list_items.rs b/src/models/process_list_items.rs new file mode 100644 index 0000000..66263d6 --- /dev/null +++ b/src/models/process_list_items.rs @@ -0,0 +1,230 @@ +use crate::components::sysinfo_wrapper::SysInfoWrapper; + +use super::process_list::ListSortOrder; +use super::process_list_item::ProcessListItem; +use super::list_items_iter::ListItemsIterator; + +#[derive(Default, Clone)] +pub struct ProcessListItems { + pub list_items: Vec, +} + +impl ProcessListItems { + pub fn new(sysinfo: &SysInfoWrapper) -> Self { + let mut processes = Vec::new(); + + sysinfo.get_processes_test(&mut processes); + + Self { + list_items: processes, + } + } + + pub fn filter(&self, filter_text: &str) -> Self { + let list_items = self.list_items + .iter() + .filter(|item| { + item.name().contains(filter_text) || + item.pid().to_string().contains(filter_text) + }) + .cloned() + .collect(); + + Self { + list_items + } + } + + pub fn update(&mut self, sysinfo: &SysInfoWrapper, filter_text: &str) { + sysinfo.get_processes_test(&mut self.list_items); + + if !filter_text.is_empty() { + self.list_items.retain(|item| { + item.name().contains(filter_text) || + item.pid().to_string().contains(filter_text) + }); + } + } + + pub fn sort_items(&mut self, sort: &ListSortOrder) { + match sort { + ListSortOrder::PidInc => { + self.list_items.sort_by_key(|a| a.pid()); + } + ListSortOrder::PidDec => { + self.list_items.sort_by_key(|b| std::cmp::Reverse (b.pid())); + } + ListSortOrder::NameInc => { + self.list_items.sort_by_key(|a| a.name().to_string()); + } + ListSortOrder::NameDec => { + self.list_items.sort_by_key(|b| std::cmp::Reverse (b.name().to_string())); + } + ListSortOrder::CpuUsageInc => { + self.list_items.sort_by(|a, b| a.cpu_usage().partial_cmp(&b.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); + } + ListSortOrder::CpuUsageDec => { + self.list_items.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); + } + ListSortOrder::MemoryUsageInc => { + self.list_items.sort_by_key(|a| a.memory_usage()); + } + ListSortOrder::MemoryUsageDec => { + self.list_items.sort_by_key(|b| std::cmp::Reverse (b.memory_usage())); + } + } + } + + pub fn get_item(&self, idx: usize) -> Option<&ProcessListItem> { + self.list_items.get(idx) + } + + pub fn get_idx(&self, pid: u32) -> Option { + if let Some(idx) = self.list_items + .iter() + .position(|item| item.pid() == pid) + { + return Some(idx); + } + None + } + + pub fn len(&self) -> usize { + self.list_items.len() + } + + pub const fn iterate(&self, start: usize, max_amount: usize) -> ListItemsIterator<'_> { + ListItemsIterator::new(self, start, max_amount) + } +} + +/* +#[cfg(test)] +mod test { + use std::vec; + use crate::models::process_list::ListSortOrder; + use crate::models::process_list_item::ProcessListItem; + use crate::models::process_list_items::ProcessListItems; + + #[test] + fn test_default() { + let instance = ProcessListItems::default(); + assert_eq!(instance.items_len(), 0); + assert_eq!(instance.get_idx(4), None); + assert_eq!(instance.get_item_ref(0), None); + } + + #[test] + fn test_new() { + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let clone_0 = item_0.clone(); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let clone_1 = item_1.clone(); + let items = vec![item_0, item_1]; + let instance = ProcessListItems::new(items); + + assert_eq!(instance.items_len(), 2); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), None); + + assert_eq!(instance.get_item_ref(0), Some(&clone_0)); + assert_eq!(instance.get_item_ref(1), Some(&clone_1)); + assert_eq!(instance.get_item_ref(2), None); + } + + #[test] + fn test_filter() { + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let clone_0 = item_0.clone(); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let _clone_1 = item_1.clone(); + let items = vec![item_0, item_1]; + let instance = ProcessListItems::new(items); + + let filtered_instance = instance.filter(&String::from("a")); + assert_eq!(filtered_instance.items_len(), 1); + assert_eq!(filtered_instance.get_item_ref(0), Some(&clone_0)); + assert_eq!(filtered_instance.get_item_ref(1), None); + assert_eq!(filtered_instance.get_idx(1), Some(0)); + assert_eq!(filtered_instance.get_idx(2), None); + } + + #[test] + fn test_update_items() { + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1]; + let mut instance = ProcessListItems::new(items); + + // Note: ProcessListItem's are compared by Pid. + let item_2 = ProcessListItem::new(1, String::from("a"), 7.0, 1337, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); + let new_items = vec![item_2, item_3]; + + let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + let _ = instance.update_items(new_items); + let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); + // Pid 2 is not in new_items so it should be removed from the instance list. + assert_eq!(instance.get_idx(2), None); + // Pid 3 cpu usage is 3.0 so it should be first in the instance list. + assert_eq!(instance.get_idx(3), Some(0)); + // Pid 1 cpu usage is updated to 7.0 so it should be last in the instance list. + assert_eq!(instance.get_idx(1), Some(1)); + } + + #[test] + fn test_sort_items() { + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); + let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); + let items = vec![item_0, item_1, item_3]; + let mut instance = ProcessListItems::new(items); + + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(2)); + let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(2)); + + let _ = instance.sort_items(&ListSortOrder::CpuUsageDec); + assert_eq!(instance.get_idx(1), Some(2)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(0)); + + let _ = instance.sort_items(&ListSortOrder::NameInc); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(2)); + + let _ = instance.sort_items(&ListSortOrder::NameDec); + assert_eq!(instance.get_idx(1), Some(2)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(0)); + + let _ = instance.sort_items(&ListSortOrder::PidInc); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(2)); + + let _ = instance.sort_items(&ListSortOrder::PidDec); + assert_eq!(instance.get_idx(1), Some(2)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(0)); + + let _ = instance.sort_items(&ListSortOrder::MemoryUsageInc); + assert_eq!(instance.get_idx(1), Some(0)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(2)); + + let _ = instance.sort_items(&ListSortOrder::MemoryUsageDec); + assert_eq!(instance.get_idx(1), Some(2)); + assert_eq!(instance.get_idx(2), Some(1)); + assert_eq!(instance.get_idx(3), Some(0)); + } +} +*/ \ No newline at end of file From c444798f7e907756a8fe5f019cf0066c66b65c67 Mon Sep 17 00:00:00 2001 From: rhasler1 Date: Tue, 8 Jul 2025 16:21:39 -0500 Subject: [PATCH 3/3] removing repeat allocations and improving constructors --- Cargo.lock | 5 - Cargo.toml | 2 - bounded-queue/Cargo.toml | 8 - bounded-queue/src/lib.rs | 9 - dynamic-list/README.md | 2 + process-list/Cargo.toml | 9 - process-list/src/lib.rs | 11 - process-list/src/process_list.rs | 369 ------------------ process-list/src/process_list_item.rs | 156 -------- process-list/src/process_list_items.rs | 217 ---------- src/app.rs | 69 +--- src/components/cpu.rs | 32 +- src/components/memory.rs | 65 +-- src/components/mod.rs | 2 +- src/components/process.rs | 41 +- src/components/sysinfo_wrapper.rs | 86 +--- src/components/temp.rs | 281 +++++-------- src/main.rs | 8 +- .../models/b_queue}/bounded_queue.rs | 7 +- src/models/b_queue/mod.rs | 1 + .../src => src/models/items}/cpu_item.rs | 3 +- .../src => src/models/items}/memory_item.rs | 7 + src/models/items/mod.rs | 3 + .../src => src/models/items}/temp_item.rs | 0 src/models/list_items_iter.rs | 50 --- src/models/list_iter.rs | 26 -- src/models/mod.rs | 8 +- .../models/p_list}/list_items_iter.rs | 1 + .../src => src/models/p_list}/list_iter.rs | 0 src/models/p_list/mod.rs | 5 + src/models/{ => p_list}/process_list.rs | 0 src/models/{ => p_list}/process_list_item.rs | 0 src/models/{ => p_list}/process_list_items.rs | 53 ++- 33 files changed, 243 insertions(+), 1293 deletions(-) delete mode 100644 bounded-queue/Cargo.toml delete mode 100644 bounded-queue/src/lib.rs create mode 100644 dynamic-list/README.md delete mode 100644 process-list/Cargo.toml delete mode 100644 process-list/src/lib.rs delete mode 100644 process-list/src/process_list.rs delete mode 100644 process-list/src/process_list_item.rs delete mode 100644 process-list/src/process_list_items.rs rename {bounded-queue/src => src/models/b_queue}/bounded_queue.rs (96%) create mode 100644 src/models/b_queue/mod.rs rename {bounded-queue/src => src/models/items}/cpu_item.rs (99%) rename {bounded-queue/src => src/models/items}/memory_item.rs (86%) create mode 100644 src/models/items/mod.rs rename {bounded-queue/src => src/models/items}/temp_item.rs (100%) delete mode 100644 src/models/list_items_iter.rs delete mode 100644 src/models/list_iter.rs rename {process-list/src => src/models/p_list}/list_items_iter.rs (99%) rename {process-list/src => src/models/p_list}/list_iter.rs (100%) create mode 100644 src/models/p_list/mod.rs rename src/models/{ => p_list}/process_list.rs (100%) rename src/models/{ => p_list}/process_list_item.rs (100%) rename src/models/{ => p_list}/process_list_items.rs (85%) diff --git a/Cargo.lock b/Cargo.lock index 4a92389..01d87e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,10 +70,6 @@ dependencies = [ "constant_time_eq", ] -[[package]] -name = "bounded-queue" -version = "0.1.0" - [[package]] name = "byteorder" version = "1.5.0" @@ -360,7 +356,6 @@ name = "process-display" version = "0.1.0" dependencies = [ "anyhow", - "bounded-queue", "clippy", "crossterm", "dynamic-list", diff --git a/Cargo.toml b/Cargo.toml index fd2dca1..d1452ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [workspace] members = [ - "bounded-queue", "dynamic-list" ] @@ -18,6 +17,5 @@ ratatui = { version = "0.26.0", features = ["serde", "macros"] } serde = { version = "1.0.188", features = ["derive"] } anyhow = "1.0.97" itertools = "0.10.0" -bounded-queue = {path = "./bounded-queue", version = "0.1.0"} dynamic-list = {path = "./dynamic-list", version = "0.1.0"} clippy = "0.0.302" diff --git a/bounded-queue/Cargo.toml b/bounded-queue/Cargo.toml deleted file mode 100644 index 3e6c189..0000000 --- a/bounded-queue/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "bounded-queue" -version = "0.1.0" -edition = "2021" - -authours = ["rhasler1 "] - -[dependencies] diff --git a/bounded-queue/src/lib.rs b/bounded-queue/src/lib.rs deleted file mode 100644 index 04b7c02..0000000 --- a/bounded-queue/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod cpu_item; -mod memory_item; -mod temp_item; -mod bounded_queue; - -pub use cpu_item::CpuItem; -pub use memory_item::MemoryItem; -pub use temp_item::TempItem; -pub use bounded_queue::BoundedQueue; diff --git a/dynamic-list/README.md b/dynamic-list/README.md new file mode 100644 index 0000000..20188ed --- /dev/null +++ b/dynamic-list/README.md @@ -0,0 +1,2 @@ +1. This is not used in the app +2. This is the beginnings of a more generalizable p_list \ No newline at end of file diff --git a/process-list/Cargo.toml b/process-list/Cargo.toml deleted file mode 100644 index 49aaedb..0000000 --- a/process-list/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "process-list" -version = "0.1.0" -edition = "2021" - -authours = ["rhasler1 "] - -[dependencies] -itertools = "0.10.0" diff --git a/process-list/src/lib.rs b/process-list/src/lib.rs deleted file mode 100644 index 7d88f72..0000000 --- a/process-list/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod list_items_iter; -mod list_iter; -mod process_list_item; -mod process_list_items; -mod process_list; - -pub use list_items_iter::ListItemsIterator; -pub use list_iter::ListIterator; -pub use process_list::{ProcessList, ListSortOrder, MoveSelection}; -pub use process_list_items::ProcessListItems; -pub use process_list_item::ProcessListItem; diff --git a/process-list/src/process_list.rs b/process-list/src/process_list.rs deleted file mode 100644 index 49afb71..0000000 --- a/process-list/src/process_list.rs +++ /dev/null @@ -1,369 +0,0 @@ -use crate::process_list_items::ProcessListItems; -use crate::process_list_item::ProcessListItem; -use crate::list_iter::ListIterator; - -#[derive(PartialEq, Clone, Default)] -pub enum ListSortOrder { - PidInc, - PidDec, - NameInc, - NameDec, - CpuUsageInc, - #[default] CpuUsageDec, - MemoryUsageInc, - MemoryUsageDec, -} - -#[derive(Copy, Clone)] -pub enum MoveSelection { - Up, - Down, - MultipleUp, - MultipleDown, - Top, - End, -} - -#[derive(Default)] -pub struct ProcessList { - items: ProcessListItems, - sort: ListSortOrder, - follow_selection: bool, - pub selection: Option, -} - -impl ProcessList { - pub fn new(list: Vec) -> Self { - assert!(!list.is_empty()); - - Self { - items: ProcessListItems::new(list), - sort: ListSortOrder::default(), - follow_selection: false, - selection: Some(0), - } - } - - pub fn filter(&self, filter_text: &str) -> Self { - let items = self.items.filter(filter_text); - let len = items.items_len(); - - Self { - items, - sort: ListSortOrder::default(), - follow_selection: false, - selection: if len > 0 { - Some(0) - } - else { - None - }, - } - } - - pub fn update(&mut self, new_list: Vec) { - let selected_item: Option<&ProcessListItem> = self.items.get_item_ref(self.selection.unwrap_or_default()); - let pid: Option = selected_item.map(|item| item.pid()); - - self.items.update_items(new_list); - self.items.sort_items(&self.sort); - - if self.items.items_len() == 0 { - self.selection = None; - return - } - - if self.follow_selection { - self.selection = pid.and_then(|p| self.items.get_idx(p)); - } - else { - if let Some(selection) = self.selection { - let max_idx = self.items.items_len().saturating_sub(1); - if selection > max_idx { - self.selection = Some(max_idx) - } - } - } - - if self.selection.is_none() { - self.selection = Some(0) - } - } - - pub fn sort(&mut self, sort: &ListSortOrder) { - let selected_item: Option<&ProcessListItem> = self.items.get_item_ref(self.selection.unwrap_or_default()); - - let pid: Option = selected_item.map(|item| item.pid()); - - self.items.sort_items(sort); - - self.sort = sort.clone(); - - if self.follow_selection { - self.selection = pid.and_then(|p| self.items.get_idx(p)); - } - } - - pub fn move_selection(&mut self, dir: MoveSelection) { - if let Some(selection) = self.selection() { - let new_idx = match dir { - MoveSelection::Down => self.selection_down(selection, 1), - MoveSelection::MultipleDown => self.selection_down(selection, 10), - MoveSelection::Up => self.selection_up(selection, 1), - MoveSelection::MultipleUp => self.selection_up(selection, 10), - MoveSelection::End => self.selection_end(selection), - MoveSelection::Top => self.selection_start(selection), - }; - - self.selection = new_idx; - } - } - - fn selection_down(&self, current_idx: usize, lines: usize) -> Option { - let mut new_idx = current_idx; - let max_idx = self.items.items_len().saturating_sub(1); - - 'a: for _ in 0..lines { - if new_idx >= max_idx { - break 'a; - } - new_idx = new_idx.saturating_add(1); - } - - Some(new_idx) - } - - fn selection_up(&self, current_idx: usize, lines: usize) -> Option { - let mut new_idx = current_idx; - let min_idx = 0; - - 'a: for _ in 0..lines { - if new_idx == min_idx { - break 'a; - } - new_idx = new_idx.saturating_sub(1); - } - - Some(new_idx) - } - - fn selection_end(&self, _current_idx: usize) -> Option { - let max_idx = self.items.items_len().saturating_sub(1); - - Some(max_idx) - - } - - fn selection_start(&self, _current_idx: usize) -> Option { - let min_idx = 0; - - Some(min_idx) - } - - pub fn toggle_follow_selection(&mut self) { - self.follow_selection = !self.follow_selection - } - - pub fn is_empty(&self) -> bool { - self.items.items_len() == 0 - } - - pub fn is_follow_selection(&self) -> bool { - self.follow_selection - } - - pub fn len(&self) -> usize { - self.items.items_len() - } - - pub fn selection(&self) -> Option { - self.selection - } - - // gets reference to selected process list item - pub fn selected_item(&self) -> Option<&ProcessListItem> { - if let Some(selection) = self.selection { - let selected_item = self.items.get_item_ref(selection); - return selected_item - } - None - } - - // gets pid of selected process list item - pub fn selected_pid(&self) -> Option { - if let Some(selection) = self.selection { - if let Some(item) = self.items.get_item_ref(selection) { - return Some(item.pid()) - } - else { - return None - } - } - None - } - - pub fn get_sort_order(&self) -> &ListSortOrder { - &self.sort - } - - pub fn iterate(&self, start_index: usize, max_amount: usize) -> ListIterator<'_> { - let start = start_index; - ListIterator::new(self.items.iterate(start, max_amount), self.selection) - } -} - -#[cfg(test)] -mod test { - use crate::process_list::{ProcessList, ListSortOrder, MoveSelection}; - use crate::process_list_item::ProcessListItem; - - #[test] - fn test_constructors() { - // Default constructor. - let empty_instance = ProcessList::default(); - assert!(empty_instance.is_empty()); - assert_eq!(empty_instance.selection(), None); - - // New constructor. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let instance = ProcessList::new(items); - assert!(!instance.is_empty()); - assert_eq!(instance.selection(), Some(0)); - - // Filter constructor case 1. - let filter_string = String::from("c"); - let filter_instance = instance.filter(&filter_string); - assert!(filter_instance.is_empty()); - assert_eq!(filter_instance.selection(), None); - - // Filter constructor case 2. - let filter_string = String::from("b"); - let filter_instance = instance.filter(&filter_string); - assert!(!filter_instance.is_empty()); - assert_eq!(filter_instance.selection(), Some(0)); - } - - #[test] - fn test_update() { - // Update with empty list of items. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - let empty_items = vec![]; - let _ = instance.update(empty_items); - assert!(instance.is_empty()); - assert!(instance.selection().is_none()); - - // Update with non-empty list of items. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); - let new_items = vec![item_2]; - let _ = instance.update(new_items); - assert!(!instance.is_empty()); - assert_eq!(instance.selection(), Some(0)); - - // Update with empty list of items and follow_selection set to true. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - let _ = instance.toggle_follow_selection(); - let empty_items = vec![]; - let _ = instance.update(empty_items); - assert!(instance.is_empty()); - assert!(instance.selection().is_none()); - - // Update with non-empty list of items and follow_selection set to true case 1. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - let _ = instance.toggle_follow_selection(); - let item_2 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); - let new_items = vec![item_2]; - let _ = instance.update(new_items); - assert!(!instance.is_empty()); - assert_eq!(instance.selection(), Some(0)); - - // Update with non-empty list of items and follow_selection set to true case 2. - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - let _ = instance.toggle_follow_selection(); - let item_2 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); - let new_items = vec![item_2, item_3]; - let _ = instance.update(new_items); - assert!(!instance.is_empty()); - assert_eq!(instance.selection(), Some(0)); - } - - #[test] - fn test_sort() { - // Test sort when follow_selection = false. - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_1, item_0]; - let mut instance = ProcessList::new(items); - assert!(instance.sort == ListSortOrder::CpuUsageDec); - assert!(!instance.is_follow_selection()); - assert_eq!(instance.selection(), Some(0)); - let _ = instance.sort(&ListSortOrder::CpuUsageInc); - assert_eq!(instance.selection(), Some(0)); - - - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - assert!(instance.sort == ListSortOrder::CpuUsageDec); - let _ = instance.toggle_follow_selection(); - assert!(instance.is_follow_selection()); - assert_eq!(instance.selection(), Some(0)); - let _ = instance.sort(&ListSortOrder::CpuUsageInc); - assert_eq!(instance.selection(), Some(1)); - } - - #[test] - fn test_selection() { - let mut empty_instance = ProcessList::default(); - empty_instance.move_selection(MoveSelection::Down); - assert_eq!(empty_instance.selection(), None); - - let item_0 = ProcessListItem::new(1, String::from("a"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessList::new(items); - assert_eq!(instance.selection(), Some(0)); - instance.move_selection(MoveSelection::Down); - instance.move_selection(MoveSelection::Down); - assert_eq!(instance.selection(), Some(1)); - - instance.move_selection(MoveSelection::Up); - instance.move_selection(MoveSelection::Up); - assert_eq!(instance.selection(), Some(0)); - - instance.move_selection(MoveSelection::End); - instance.move_selection(MoveSelection::End); - assert_eq!(instance.selection(), Some(1)); - - instance.move_selection(MoveSelection::Top); - instance.move_selection(MoveSelection::Top); - assert_eq!(instance.selection(), Some(0)); - - instance.move_selection(MoveSelection::MultipleDown); - instance.move_selection(MoveSelection::MultipleDown); - assert_eq!(instance.selection(), Some(1)); - - instance.move_selection(MoveSelection::MultipleUp); - instance.move_selection(MoveSelection::MultipleUp); - assert_eq!(instance.selection(), Some(0)); - } -} diff --git a/process-list/src/process_list_item.rs b/process-list/src/process_list_item.rs deleted file mode 100644 index 0ccbe54..0000000 --- a/process-list/src/process_list_item.rs +++ /dev/null @@ -1,156 +0,0 @@ -#[derive(Default, Clone, Debug)] -pub struct ProcessListItem { - pid: u32, - name: String, - cpu_usage: f32, - memory_usage: u64, - start_time: u64, - run_time: u64, - accumulated_cpu_time: u64, - status: String, - path: String, -} - -impl ProcessListItem { - pub fn new( - pid: u32, - name: String, - cpu_usage: f32, - memory_usage: u64, - start_time: u64, - run_time: u64, - accumulated_cpu_time: u64, - status: String, - path: String, - ) -> Self { - Self { - pid, - name, - cpu_usage, - memory_usage, - start_time, - run_time, - accumulated_cpu_time, - status, - path, - } - } - - // match by name or pid - //pub fn is_match(&self, filter_text: &str) -> bool { - // self.name.contains(filter_text) || - // self.pid.to_string().contains(filter_text) - //} - - pub fn pid(&self) -> u32 { - self.pid - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn cpu_usage(&self) -> f32 { - self.cpu_usage - } - - pub fn memory_usage(&self) -> u64 { - self.memory_usage - } - - pub fn start_time(&self) -> u64 { - self.start_time - } - - pub fn run_time(&self) -> u64 { - self.run_time - } - - pub fn run_time_hh_mm_ss(&self) -> String { - let time_in_s = self.run_time; - - let ss = time_in_s % 60; - let mm = (time_in_s / 60) % 60; - let hh = (time_in_s / 60) / 60; - - format!("{:0>2}:{:0>2}:{:0>2}", hh, mm, ss) - } - - pub fn accumulated_cpu_time(&self) -> u64 { - self.accumulated_cpu_time - } - - pub fn status(&self) -> &str { - &self.status - } - - pub fn path(&self) -> &str { - &self.path - } -} - -// PartialEq is needed for comparison, e.g., calling contains -impl PartialEq for ProcessListItem { - fn eq(&self, other: &Self) -> bool { - self.pid.eq(&other.pid) - } -} - -#[cfg(test)] -pub mod test { - use super::ProcessListItem; - - #[test] - fn test_constructors() { - let instance = ProcessListItem::default(); - assert_eq!(instance.pid, 0); - assert!(String::is_empty(&instance.name)); - assert_eq!(instance.cpu_usage, 0.0); - assert_eq!(instance.memory_usage, 0); - assert_eq!(instance.start_time, 0); - assert_eq!(instance.run_time, 0); - assert_eq!(instance.accumulated_cpu_time, 0); - assert!(String::is_empty(&instance.status)); - - let instance = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - assert_eq!(instance.pid, 1); - assert_eq!(instance.name, String::from("a")); - assert_eq!(instance.cpu_usage, 1.0); - assert_eq!(instance.memory_usage, 1); - assert_eq!(instance.start_time, 0); - assert_eq!(instance.run_time, 10); - assert_eq!(instance.accumulated_cpu_time, 10); - assert_eq!(instance.status, String::from("test")); - - } - - #[test] - fn test_instance_functions() { - let instance_0 = ProcessListItem::default(); - let instance_1 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - - assert_eq!(instance_0.pid(), instance_0.pid); - assert_eq!(instance_0.name(), instance_0.name); - assert_eq!(instance_0.cpu_usage(), instance_0.cpu_usage); - assert_eq!(instance_0.memory_usage(), instance_0.memory_usage); - assert_eq!(instance_0.start_time(), instance_0.start_time); - assert_eq!(instance_0.run_time(), instance_0.run_time); - assert_eq!(instance_0.accumulated_cpu_time(), instance_0.accumulated_cpu_time); - assert_eq!(instance_0.status(), instance_0.status); - //assert_eq!(instance_0.is_match(""), true); - //assert_eq!(instance_0.is_match("a"), false); - //assert_eq!(instance_0.is_match(&instance_0.pid.to_string()), true); - - assert_eq!(instance_1.pid(), instance_1.pid); - assert_eq!(instance_1.name(), instance_1.name); - assert_eq!(instance_1.cpu_usage(), instance_1.cpu_usage); - assert_eq!(instance_1.memory_usage(), instance_1.memory_usage); - assert_eq!(instance_0.start_time(), instance_0.start_time); - assert_eq!(instance_0.run_time(), instance_0.run_time); - assert_eq!(instance_0.accumulated_cpu_time(), instance_0.accumulated_cpu_time); - assert_eq!(instance_0.status(), instance_0.status); - //assert_eq!(instance_1.is_match("a"), true); - //assert_eq!(instance_1.is_match("aa"), false); - //assert_eq!(instance_1.is_match(&instance_1.pid.to_string()), true); - } -} diff --git a/process-list/src/process_list_items.rs b/process-list/src/process_list_items.rs deleted file mode 100644 index 38e27e4..0000000 --- a/process-list/src/process_list_items.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::process_list::ListSortOrder; -use crate::process_list_item::ProcessListItem; -use crate::list_items_iter::ListItemsIterator; - -#[derive(Default, Clone)] -pub struct ProcessListItems { - pub list_items: Vec, -} - -impl ProcessListItems { - pub fn new(list: Vec) -> Self { - Self { - list_items: list, - } - } - - pub fn filter(&self, filter_text: &str) -> Self { - Self { - list_items: self.create_filtered_items(filter_text) - } - } - - fn create_filtered_items(&self, filter_text: &str) -> Vec { - self.list_items - .iter() - .filter(|item| { - item.name().contains(filter_text) || - item.pid().to_string().contains(filter_text) - }) - .cloned() - .collect() - } - - pub fn update_items(&mut self, new_list: Vec) { - self.list_items = new_list; - } - - pub fn sort_items(&mut self, sort: &ListSortOrder) { - match sort { - ListSortOrder::PidInc => { - self.list_items.sort_by_key(|a| a.pid()); - } - ListSortOrder::PidDec => { - self.list_items.sort_by_key(|b| std::cmp::Reverse (b.pid())); - } - ListSortOrder::NameInc => { - self.list_items.sort_by_key(|a| a.name().to_string()); - } - ListSortOrder::NameDec => { - self.list_items.sort_by_key(|b| std::cmp::Reverse (b.name().to_string())); - } - ListSortOrder::CpuUsageInc => { - self.list_items.sort_by(|a, b| a.cpu_usage().partial_cmp(&b.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); - } - ListSortOrder::CpuUsageDec => { - self.list_items.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); - } - ListSortOrder::MemoryUsageInc => { - self.list_items.sort_by_key(|a| a.memory_usage()); - } - ListSortOrder::MemoryUsageDec => { - self.list_items.sort_by_key(|b| std::cmp::Reverse (b.memory_usage())); - } - } - } - - pub fn get_item_ref(&self, idx: usize) -> Option<&ProcessListItem> { - self.list_items.get(idx) - } - - pub fn get_idx(&self, pid: u32) -> Option { - if let Some(idx) = self.list_items - .iter() - .position(|item| item.pid() == pid) - { - return Some(idx); - } - None - } - - pub fn items_len(&self) -> usize { - self.list_items.len() - } - - pub const fn iterate(&self, start: usize, max_amount: usize) -> ListItemsIterator<'_> { - ListItemsIterator::new(self, start, max_amount) - } -} - -#[cfg(test)] -mod test { - use std::vec; - use crate::process_list::ListSortOrder; - use crate::process_list_item::ProcessListItem; - use crate::process_list_items::ProcessListItems; - - #[test] - fn test_default() { - let instance = ProcessListItems::default(); - assert_eq!(instance.items_len(), 0); - assert_eq!(instance.get_idx(4), None); - assert_eq!(instance.get_item_ref(0), None); - } - - #[test] - fn test_new() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let clone_0 = item_0.clone(); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let clone_1 = item_1.clone(); - let items = vec![item_0, item_1]; - let instance = ProcessListItems::new(items); - - assert_eq!(instance.items_len(), 2); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), None); - - assert_eq!(instance.get_item_ref(0), Some(&clone_0)); - assert_eq!(instance.get_item_ref(1), Some(&clone_1)); - assert_eq!(instance.get_item_ref(2), None); - } - - #[test] - fn test_filter() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let clone_0 = item_0.clone(); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let _clone_1 = item_1.clone(); - let items = vec![item_0, item_1]; - let instance = ProcessListItems::new(items); - - let filtered_instance = instance.filter(&String::from("a")); - assert_eq!(filtered_instance.items_len(), 1); - assert_eq!(filtered_instance.get_item_ref(0), Some(&clone_0)); - assert_eq!(filtered_instance.get_item_ref(1), None); - assert_eq!(filtered_instance.get_idx(1), Some(0)); - assert_eq!(filtered_instance.get_idx(2), None); - } - - #[test] - fn test_update_items() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1]; - let mut instance = ProcessListItems::new(items); - - // Note: ProcessListItem's are compared by Pid. - let item_2 = ProcessListItem::new(1, String::from("a"), 7.0, 1337, 0, 10, 10, String::from("test"), String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); - let new_items = vec![item_2, item_3]; - - let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - let _ = instance.update_items(new_items); - let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); - // Pid 2 is not in new_items so it should be removed from the instance list. - assert_eq!(instance.get_idx(2), None); - // Pid 3 cpu usage is 3.0 so it should be first in the instance list. - assert_eq!(instance.get_idx(3), Some(0)); - // Pid 1 cpu usage is updated to 7.0 so it should be last in the instance list. - assert_eq!(instance.get_idx(1), Some(1)); - } - - #[test] - fn test_sort_items() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); - let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); - let item_3 = ProcessListItem::new(3, String::from("c"), 3.0, 3, 0, 10, 10, String::from("test"), String::from("test")); - let items = vec![item_0, item_1, item_3]; - let mut instance = ProcessListItems::new(items); - - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(2)); - let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(2)); - - let _ = instance.sort_items(&ListSortOrder::CpuUsageDec); - assert_eq!(instance.get_idx(1), Some(2)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(0)); - - let _ = instance.sort_items(&ListSortOrder::NameInc); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(2)); - - let _ = instance.sort_items(&ListSortOrder::NameDec); - assert_eq!(instance.get_idx(1), Some(2)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(0)); - - let _ = instance.sort_items(&ListSortOrder::PidInc); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(2)); - - let _ = instance.sort_items(&ListSortOrder::PidDec); - assert_eq!(instance.get_idx(1), Some(2)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(0)); - - let _ = instance.sort_items(&ListSortOrder::MemoryUsageInc); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(2)); - - let _ = instance.sort_items(&ListSortOrder::MemoryUsageDec); - assert_eq!(instance.get_idx(1), Some(2)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), Some(0)); - } -} diff --git a/src/app.rs b/src/app.rs index 6dd2cb2..9ca01a1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,6 @@ use anyhow::{Ok, Result}; use crossterm::event::KeyEvent; use ratatui::prelude::*; -use crate::components::sysinfo_wrapper; use crate::components::temp::TempComponent; use crate::config::Config; use crate::components::{ @@ -14,8 +13,6 @@ use crate::components::{ EventState, DrawableComponent, help::HelpComponent, - command, - command::CommandInfo, }; enum MainFocus { @@ -41,23 +38,12 @@ pub struct App { impl App { pub fn new(config: Config) -> Self { let mut system_wrapper = SysInfoWrapper::new(config.clone()); - system_wrapper.refresh_all(); let process = ProcessComponent::new(config.clone(), &system_wrapper); - - let mut cpu = CPUComponent::default(); - let cpus = system_wrapper.get_cpus(); - cpu.update(cpus); - - let memory_item = system_wrapper.get_memory(); - let mut memory = MemoryComponent::new(config.clone()); - memory.update(memory_item); - - let temp_items = system_wrapper.get_temps(); - let mut temp = TempComponent::new(config.clone()); - temp.update(temp_items); - + let memory = MemoryComponent::new(config.clone(), &system_wrapper); + let cpu = CPUComponent::new(config.clone(), &system_wrapper); + let temp = TempComponent::new(config.clone(), &system_wrapper); Self { focus: MainFocus::Process, @@ -77,40 +63,13 @@ impl App { self.system_wrapper.refresh_all(); self.process.update(&self.system_wrapper); - - self.update_cpu(); - - self.update_memory(); - - self.update_temps(); + self.memory.update(&self.system_wrapper); + self.cpu.update(&self.system_wrapper); + self.temp.update(&self.system_wrapper); Ok(EventState::Consumed) } - fn update_temps(&mut self) { - let temp_items = self.system_wrapper.get_temps(); - - self.temp.update(temp_items); - } - - fn update_memory(&mut self) { - let memory_item = self.system_wrapper.get_memory(); - - self.memory.update(memory_item); - } - - /*fn update_process(&mut self) { - let new_processes = self.system_wrapper.get_processes(); // receive ownership - - self.process.update(new_processes); // transfer ownership - }*/ - - fn update_cpu(&mut self) { - let new_cpus = self.system_wrapper.get_cpus(); // receive ownership - - self.cpu.update(new_cpus); // transfer ownership - } - fn toggle_expand(&mut self) { self.expand = !self.expand } @@ -127,13 +86,6 @@ impl App { return Ok(EventState::Consumed); } - else if key.code == self.config.key_config.terminate { - if let Some(pid) = self.process.selected_pid() { - self.system_wrapper.terminate_process(pid); - } - - return Ok(EventState::Consumed) - } Ok(EventState::NotConsumed) } @@ -167,6 +119,12 @@ impl App { if self.process.event(key)?.is_consumed() { return Ok(EventState::Consumed) } + // terminate case + if key.code == self.config.key_config.terminate { + self.process.terminate_process(&self.system_wrapper); + + return Ok(EventState::Consumed) + } } } @@ -295,6 +253,7 @@ impl App { return Ok(()) } + /* fn commands(&self) -> Vec { let res = vec![ CommandInfo::new(command::help(&self.config.key_config)), @@ -312,5 +271,5 @@ impl App { ]; res - } + }*/ } diff --git a/src/components/cpu.rs b/src/components/cpu.rs index 0cd6e46..f226586 100644 --- a/src/components/cpu.rs +++ b/src/components/cpu.rs @@ -3,7 +3,9 @@ use ratatui::prelude::*; use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, List, ListItem, ListState}; use std::str::FromStr; use anyhow::Ok; -use bounded_queue::{BoundedQueue, CpuItem}; +use crate::components::sysinfo_wrapper::SysInfoWrapper; +use crate::models::b_queue::bounded_queue::BoundedQueue; +use crate::models::items::cpu_item::CpuItem; use crate::config::Config; use super::{Component, DrawableComponent}; @@ -68,9 +70,31 @@ pub struct CPUComponent { } impl CPUComponent { + pub fn new(config: Config, sysinfo: &SysInfoWrapper) -> Self { + let mut cpus: BTreeMap> = BTreeMap::new(); + let ui_selection: usize = 0; + + for cpu in sysinfo.get_cpus() { + let id = cpu.id(); + + let perf_q = cpus.entry(id).or_insert_with(|| { + BoundedQueue::new(config.events_per_min() as usize) + }); + + // passes ownership + perf_q.add_item(cpu); + } + + Self { + cpus, + ui_selection, + config, + } + } + // has ownership - pub fn update(&mut self, cpus: Vec) { - for cpu in cpus { + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + for cpu in sysinfo.get_cpus() { let id = cpu.id(); let perf_q = self.cpus.entry(id).or_insert_with(|| { @@ -107,7 +131,7 @@ impl DrawableComponent for CPUComponent { .direction(Direction::Horizontal) .constraints([ Constraint::Fill(1), // chart - Constraint::Length(16), // list + Constraint::Length(16), // list ]).split(area); // containers diff --git a/src/components/memory.rs b/src/components/memory.rs index d8a086f..0594b67 100644 --- a/src/components/memory.rs +++ b/src/components/memory.rs @@ -1,43 +1,40 @@ -use std::fs::File; - -// RAM component -use anyhow::Ok; -use anyhow::Result; -use bounded_queue::BoundedQueue; -use bounded_queue::MemoryItem; -use crate::components::DrawableComponent; -use crate::config::Config; -use super::Component; -use super::EventState; +use anyhow::{Ok, Result}; use ratatui::{ layout::{Layout, Direction, Constraint}, style::{Style, Stylize}, widgets::{Block, Gauge}, }; +use crossterm::event::KeyEvent; +use crate::components::sysinfo_wrapper::SysInfoWrapper; +use crate::components::DrawableComponent; +use crate::models::items::memory_item::MemoryItem; +use crate::config::Config; +use super::Component; +use super::EventState; -//TODO: This does not need to use a bounded queue--only displaying fields of most recent item with gauges. pub struct MemoryComponent { config: Config, - memories: BoundedQueue, + memory: MemoryItem, } impl MemoryComponent { - pub fn new(config: Config) -> Self { - let memories: BoundedQueue = BoundedQueue::new(config.events_per_min() as usize); + pub fn new(config: Config, sysinfo: &SysInfoWrapper) -> Self { + let mut memory = MemoryItem::default(); + sysinfo.get_memory(&mut memory); Self { config, - memories, + memory, } } - pub fn update(&mut self, memory_item: MemoryItem) { - self.memories.add_item(memory_item); + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + sysinfo.get_memory(&mut self.memory); } } impl Component for MemoryComponent { - fn event(&mut self, _key: crossterm::event::KeyEvent) -> Result { + fn event(&mut self, _key: KeyEvent) -> Result { Ok(EventState::NotConsumed) } } @@ -60,20 +57,9 @@ impl DrawableComponent for MemoryComponent { }; // ram widget - let ram_percent = if let Some(item) = self.memories.back() { - ( item.used_memory_gb() / item.total_memory_gb() ) * 100_f64 - } - else { - 0_f64 - }; - + let ram_percent = ( self.memory.used_memory_gb() / self.memory.total_memory_gb() ) * 100_f64; let ram_label = "RAM Usage"; - let ram_title = if let Some(item) = self.memories.back() { - format!(" {:<15} {:.2} GB / {:.2} GB ", ram_label, item.used_memory_gb(), item.total_memory_gb()) - } - else { - format!(" {:<15} ", ram_label) - }; + let ram_title = format!(" {:<15} {:.2} GB / {:.2} GB ", ram_label, self.memory.used_memory_gb(), self.memory.total_memory_gb()); let g_ram = Gauge::default() .block(Block::bordered().style(style).title(ram_title)) @@ -81,20 +67,9 @@ impl DrawableComponent for MemoryComponent { .percent(ram_percent as u16); // swap widget - let swap_percent = if let Some(item) = self.memories.back() { - ( item.used_swap_gb() / item.total_swap_gb() ) * 100_f64 - } - else { - 0_f64 - }; - + let swap_percent = ( self.memory.used_swap_gb() / self.memory.total_swap_gb() ) * 100_f64; let swap_label = "Swap Usage"; - let swap_title = if let Some(item) = self.memories.back() { - format!(" {:<15} {:.2} GB / {:.2} GB ", swap_label, item.used_swap_gb(), item.total_swap_gb()) - } - else { - format!(" {:<15} ", swap_label) - }; + let swap_title = format!(" {:<15} {:.2} GB / {:.2} GB ", swap_label, self.memory.used_swap_gb(), self.memory.total_swap_gb()); let g_swap = Gauge::default() .block(Block::bordered().style(style).title(swap_title)) diff --git a/src/components/mod.rs b/src/components/mod.rs index 765b262..601746a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crossterm::event::KeyEvent; use ratatui::prelude::*; -use crate::models::process_list::{ListSortOrder, MoveSelection}; +use crate::models::p_list::process_list::{ListSortOrder, MoveSelection}; use super::config::KeyConfig; pub mod sysinfo_wrapper; pub mod filter; diff --git a/src/components/process.rs b/src/components/process.rs index a9cb037..3a42d05 100644 --- a/src/components/process.rs +++ b/src/components/process.rs @@ -4,16 +4,14 @@ use ratatui::{ Frame, prelude::*, }; -use crate::{components::{filter, sysinfo_wrapper::SysInfoWrapper}, config::{Config, KeyConfig}}; -use crate::models::process_list::{ListSortOrder, ProcessList}; -use crate::models::process_list_item::ProcessListItem; -use crate::models::process_list_items::ProcessListItems; - -use super::{ - common_nav, common_sort, Component, DrawableComponent, EventState, +use crate::components::{ + sysinfo_wrapper::SysInfoWrapper, + common_nav, common_sort, DrawableComponent, Component, EventState, utils::vertical_scroll::VerticalScroll, filter::FilterComponent, }; +use crate::config::{Config, KeyConfig}; +use crate::models::p_list::process_list::{ListSortOrder, ProcessList}; #[derive(PartialEq, Clone)] pub enum Focus { @@ -50,17 +48,16 @@ impl ProcessComponent { } } - // gets the selected process pid, returns Some(pid) or None - pub fn selected_pid(&self) -> Option { - if matches!(self.focus, Focus::List) { - if let Some(filtered_list) = self.filtered_list.as_ref() { - return filtered_list.selected_pid() - } - else { - return self.list.selected_pid() - } - } - None + pub fn terminate_process(&mut self, sysinfo: &SysInfoWrapper) -> bool { + self.filtered_list + .as_ref() + .and_then(|f| f.selected_pid()) + .or_else(|| self.list.selected_pid()) + .map(|pid| { + sysinfo.terminate_process(pid); + true + }) + .unwrap_or(false) } } @@ -116,7 +113,7 @@ impl Component for ProcessComponent { return Ok(EventState::Consumed) } - else if list_sort( + if list_sort( if let Some(list) = self.filtered_list.as_mut() { list } @@ -174,6 +171,7 @@ impl DrawableComponent for ProcessComponent { &self.list }; + // update vert scroll list.selection().map_or_else( { || self.scroll.reset() @@ -234,7 +232,7 @@ impl DrawableComponent for ProcessComponent { } use ratatui::widgets::{block::*, *}; -use crate::models::list_iter::ListIterator; +use crate::models::p_list::list_iter::ListIterator; use crate::config::ThemeConfig; fn draw_process_list( @@ -303,8 +301,7 @@ fn draw_process_list( } else { theme_config.style_item_not_focused - } - ; + }; let cells: Vec = vec![ if style == theme_config.style_item_selected || diff --git a/src/components/sysinfo_wrapper.rs b/src/components/sysinfo_wrapper.rs index d1d3d85..7f49503 100644 --- a/src/components/sysinfo_wrapper.rs +++ b/src/components/sysinfo_wrapper.rs @@ -1,14 +1,13 @@ use sysinfo::{Pid, System, Components}; -use crate::models::process_list_item::ProcessListItem; -use bounded_queue::MemoryItem; -use bounded_queue::TempItem; -use bounded_queue::{CpuItem}; +use crate::models::p_list::process_list_item::ProcessListItem; +use crate::models::items::{memory_item::MemoryItem, temp_item::TempItem, cpu_item::CpuItem}; use crate::config::Config; // See here for refreshing system: https://crates.io/crates/sysinfo#:~:text=use%20sysinfo%3A%3ASystem,(sysinfo%3A%3AMINIMUM_CPU_UPDATE_INTERVAL)%3B%0A%7D // note: sysinfo::MINIMUM_CPU_UPDATE_INTERVAL = 200 ms pub struct SysInfoWrapper { system: System, + components: Components, pub _config: Config } @@ -16,12 +15,14 @@ impl SysInfoWrapper { pub fn new(config: Config) -> Self { Self { system: System::new_all(), + components: Components::new_with_refreshed_list(), _config: config } } pub fn refresh_all(&mut self) { self.system.refresh_all(); + self.components.refresh(false); } pub fn get_cpus(&self) -> Vec { @@ -46,7 +47,7 @@ impl SysInfoWrapper { cpus } - pub fn get_processes_test(&self, processes: &mut Vec) { + pub fn get_processes(&self, processes: &mut Vec) { processes.clear(); for (pid, process) in self.system.processes() { @@ -101,80 +102,19 @@ impl SysInfoWrapper { } } - pub fn get_processes(&self) -> Vec { - let mut processes: Vec = Vec::new(); - - for (pid, process) in self.system.processes() { - let name = if let Some(name) = process.name().to_str() { - String::from(name) - } - else { - String::from("No name") - }; - let cpu_usage = if let Some(core_count) = sysinfo::System::physical_core_count() { - process.cpu_usage() / core_count as f32 // normalizing process cpu usage by the number of cores - } - else { - process.cpu_usage() - }; - - let memory_usage = process.memory(); - - let start_time = process.start_time(); - - let run_time = process.run_time(); - - let accumulated_cpu_time = process.accumulated_cpu_time(); - - let status = process.status().to_string(); - - let path = if let Some(path) = process.exe() { - if let Some(path) = path.to_str() { - path.to_string() - } - else { - String::from("Non-valid Unicode") - } - } - else { - String::from("Permission Denied") - }; - - let item = ProcessListItem::new( - pid.as_u32(), - name, - cpu_usage, - memory_usage, - start_time, - run_time, - accumulated_cpu_time, - status, - path, - ); - - processes.push(item); - } - - processes - } - - pub fn get_memory(&self) -> MemoryItem { + pub fn get_memory(&self, memory: &mut MemoryItem) { let total_memory = self.system.total_memory(); // total memory is size of RAM in bytes let used_memory = self.system.used_memory(); // used memory is allocated memory let total_swap = self.system.total_swap(); let used_swap = self.system.used_swap(); - let memory_item = MemoryItem::new(total_memory, used_memory, total_swap, used_swap); - - memory_item + memory.update(total_memory, used_memory, total_swap, used_swap); } - pub fn get_temps(&self) -> Vec { - let mut temps: Vec = Vec::new(); - - let components = Components::new_with_refreshed_list(); // it seems that this is separate from system, might need to add as Struct field, although, there is no refresh method? + pub fn get_temps(&self, temps: &mut Vec) { + temps.clear(); - for component in &components { + for component in &self.components { let temp = if let Some(temp) = component.temperature() { temp } @@ -203,12 +143,10 @@ impl SysInfoWrapper { temps.push(item); } - - temps } - pub fn terminate_process(&mut self, pid: u32) -> bool { + pub fn terminate_process(&self, pid: u32) -> bool { let mut res = false; if let Some(process) = self.system.process(Pid::from_u32(pid)) { diff --git a/src/components/temp.rs b/src/components/temp.rs index 678b407..ba49e3d 100644 --- a/src/components/temp.rs +++ b/src/components/temp.rs @@ -1,57 +1,43 @@ -// make similar to CPU - -/* - -pub struct CPUComponent { - cpus: BTreeMap>, - ui_selection: usize, - config: Config, -} - -*/ -use std::collections::BTreeMap; -use bounded_queue::BoundedQueue; -use crate::{components::EventState, config::Config}; -use bounded_queue::TempItem; -use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, Gauge, GraphType, List, ListItem, ListState, Table}; use anyhow::{Ok, Result}; +use ratatui::widgets::{Block, Borders, Table}; +use ratatui::prelude::*; +use ratatui::Frame; +use ratatui::widgets::Cell; +use ratatui::widgets::Row; +use crossterm::event::KeyEvent; +use crate::components::sysinfo_wrapper::SysInfoWrapper; +use crate::components::utils::vertical_scroll::VerticalScroll; +use crate::models::items::temp_item::TempItem; +use crate::{components::EventState, config::Config}; use super::{Component, DrawableComponent}; -use ratatui::{ - style::{Style, Stylize}, -}; pub struct TempComponent { config: Config, - temps: BTreeMap>, + temps: Vec, ui_selection: usize, + vertical_scroll: VerticalScroll, } impl TempComponent { - pub fn new(config: Config) -> Self { - let temps: BTreeMap> = BTreeMap::new(); + pub fn new(config: Config, sysinfo: &SysInfoWrapper) -> Self { + let mut temps = Vec::new(); + sysinfo.get_temps(&mut temps); Self { config, temps, ui_selection: 0, + vertical_scroll: VerticalScroll::new(), } } - pub fn update(&mut self, temp_items: Vec) { - for temp_item in temp_items { - let key = temp_item.label().to_string(); - - let queue = self.temps.entry(key).or_insert_with(|| { - BoundedQueue::new(self.config.events_per_min() as usize) - }); - - queue.add_item(temp_item); - } + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + sysinfo.get_temps(&mut self.temps); } } impl Component for TempComponent { - fn event(&mut self, key: crossterm::event::KeyEvent) -> Result { + fn event(&mut self, key: KeyEvent) -> Result { let temps_max_idx = self.temps.len() - 1; if key.code == self.config.key_config.move_down { @@ -69,174 +55,83 @@ impl Component for TempComponent { } } - -// I can't get critical temp values on Mac and potentially Windows. -// Instead of doing gauges for temp, I'm just going to report as a table. -// Also, sorting by String, messes up the order e.g., item0 -> item1 -> item10 -> item2 -// TODO: Do not use boundedqueue, just create a Vector of TempItems. -/* - let header = ["", - if matches!(sort_order, ListSortOrder::PidInc) { "PID ▲" } - else if matches!(sort_order, ListSortOrder::PidDec) { "PID ▼" } - else { "PID" }, - - if matches!(sort_order, ListSortOrder::NameInc) { "Name ▲" } - else if matches!(sort_order, ListSortOrder::NameDec) { "Name ▼" } - else { "Name" }, - - if matches!(sort_order, ListSortOrder::CpuUsageInc) { "CPU (%) ▲" } - else if matches!(sort_order, ListSortOrder::CpuUsageDec) { "CPU (%) ▼" } - else { "CPU (%)" }, - - if matches!(sort_order, ListSortOrder::MemoryUsageInc) { "Memory (MB) ▲" } - else if matches!(sort_order, ListSortOrder::MemoryUsageDec) { "Memory (MB) ▼" } - else { "Memory (MB)" }, - - "Run (hh:mm:ss)", - "Status", - "Path"] - .into_iter() - .map(Cell::from) - .collect::() - .style( - if focus { - theme_config.style_border_focused - } - else { - theme_config.style_border_not_focused - } - ) - .height(1); - - // setting rows - let rows = visible_items - .map(|(item, selected)| { - let style = - if focus && selected && follow_flag { - theme_config.style_item_selected_followed - } - else if focus && selected && !follow_flag { - theme_config.style_item_selected - } - else if focus { - theme_config.style_item_focused - } - else if !focus && selected & follow_flag { - theme_config.style_item_selected_followed_not_focused - } - else if !focus && selected & !follow_flag { - theme_config.style_item_selected_not_focused +//TODO: take a closer look at: +// 1. VerticalScroll +// 2. ListIter & ListItemsIter +impl DrawableComponent for TempComponent { + fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()> { + let visible_height = area.height.saturating_sub(3) as usize; + + self.vertical_scroll.update( + self.ui_selection, + self.temps.len(), + visible_height, + ); + + let visible_items = self.temps.iter().skip(self.vertical_scroll.get_top()).take(visible_height); + + let header = ["Sensor", "Temp", "Max Temp", "Critical Temp"] + .into_iter() + .map(Cell::from) + .collect::() + .style( + if focused { + self.config.theme_config.style_border_focused } else { - theme_config.style_item_not_focused + self.config.theme_config.style_border_not_focused } - ; - - let cells: Vec = vec![ - if style == theme_config.style_item_selected || - style == theme_config.style_item_selected_followed || - style == theme_config.style_item_selected_followed_not_focused || - style == theme_config.style_item_selected_not_focused { - Cell::from(String::from("->")) - } - else { - Cell::from(String::from("")) - }, - Cell::from(item.pid().to_string()), - Cell::from(item.name().to_string()), - Cell::from(format!("{:.2}", item.cpu_usage())), - Cell::from(format!("{:.2}", item.memory_usage()/1000000)), - Cell::from(item.run_time_hh_mm_ss()), - Cell::from(item.status()), - Cell::from(item.path()), - ]; - Row::new(cells).style(style) - }) - .collect::>(); - - // setting the width constraints. - let widths = - vec![ - Constraint::Length(2), - Constraint::Length(10), // pid - Constraint::Length(50), // name - Constraint::Length(15), // cpu usage - Constraint::Length(15), // memory usage - Constraint::Length(20), // run time - Constraint::Length(15), // status - Constraint::Min(0), // path - ]; - - // setting block information - let block_title: &str = " Process List "; - let block_style = - if focus { - theme_config.style_border_focused + ) + .height(1); + + let rows = visible_items + .map(|item| { + let style: Style = + if focused && item.label().eq(self.temps[self.ui_selection].label()) { + self.config.theme_config.style_item_selected + } + else if focused { + self.config.theme_config.style_item_focused + } + else if !focused && item.label().eq(self.temps[self.ui_selection].label()) { + self.config.theme_config.style_item_selected_not_focused + } + else { + self.config.theme_config.style_item_not_focused + }; + + let cells: Vec = vec![ + Cell::from(item.label().to_string()), + Cell::from(item.temp().to_string()), + Cell::from(item.max_temp().to_string()), + Cell::from(item.critical_temp().to_string()), + ]; + + Row::new(cells).style(style) + }) + .collect::>(); + + let widths = vec![ + Constraint::Percentage(25), + Constraint::Percentage(25), + Constraint::Percentage(25), + Constraint::Percentage(25), + ]; + + let block_title: &str = " Sensor Temperatures "; + let block_style = if focused { + self.config.theme_config.style_border_focused } else { - theme_config.style_border_not_focused + self.config.theme_config.style_border_not_focused }; - // setting the table - let table = Table::new(rows, widths) - .header(header) - .block(Block::default().borders(Borders::ALL).title(block_title)) - .style(block_style); - - // render - f.render_widget(table, area); -} -*/ -impl DrawableComponent for TempComponent { - fn draw(&mut self, f: &mut ratatui::Frame, area: ratatui::prelude::Rect, focused: bool) -> Result<()> { - // get the temp component selected - let selection = self.ui_selection; - - //let header = ["Sensor", "Temp", "Max Temp", "Critical Temp"]; - - //let rows = self.temps - // .iter() - // .map(|item| {}) - - //let table = Table::new(rows, widths) - //.header(header) - // .block(Block::default().borders(Borders::ALL).title(block_title)) - // .style(block_style); + let table = Table::new(rows, widths) + .header(header) + .block(Block::default().borders(Borders::ALL).title(block_title)) + .style(block_style); - - // populate names for UIList to draw temp component list - let mut names: Vec = self.temps - .iter() - .map(|(key, queue)| { - let title = format!("{} {}", key.to_string(), queue.back().unwrap().critical_temp()); - ListItem::new(title) - }) - .collect(); - - let mut list_state = ListState::default(); - - list_state.select(Some(self.ui_selection)); - - let temp_list = List::new(names) - .scroll_padding(area.height as usize / 2) - .block( - if !focused { - Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_not_focused) - } - else { - Block::default().borders(Borders::ALL).style(self.config.theme_config.style_border_focused) - } - ) - .highlight_style( - if !focused { - self.config.theme_config.style_item_selected_not_focused - } - else { - self.config.theme_config.style_item_selected - } - ); - - f.render_stateful_widget(temp_list, area, &mut list_state); + f.render_widget(table, area); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 377287c..6edd703 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,10 +82,4 @@ fn main() -> Result<()> { terminal.show_cursor()?; Ok(()) -} - -fn setup_terminal() -> Result<()> { - enable_raw_mode()?; - io::stdout().execute(EnterAlternateScreen)?; - Ok(()) -} +} \ No newline at end of file diff --git a/bounded-queue/src/bounded_queue.rs b/src/models/b_queue/bounded_queue.rs similarity index 96% rename from bounded-queue/src/bounded_queue.rs rename to src/models/b_queue/bounded_queue.rs index a38fc45..a864f79 100644 --- a/bounded-queue/src/bounded_queue.rs +++ b/src/models/b_queue/bounded_queue.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, io}; +use std::collections::VecDeque; #[derive(Default)] pub struct BoundedQueue { @@ -50,10 +50,11 @@ impl BoundedQueue { } } +/* #[cfg(test)] mod test { use super::BoundedQueue; - use crate::CpuItem; + use crate::models::items::cpu_item::CpuItem; /*#[test] fn test_bounded_queue() { @@ -72,4 +73,4 @@ mod test { assert_eq!(instance.back().unwrap().global_usage(), 15.7); assert_eq!(instance.front().unwrap().global_usage(), 13.2); }*/ -} +}*/ \ No newline at end of file diff --git a/src/models/b_queue/mod.rs b/src/models/b_queue/mod.rs new file mode 100644 index 0000000..c07b676 --- /dev/null +++ b/src/models/b_queue/mod.rs @@ -0,0 +1 @@ +pub mod bounded_queue; \ No newline at end of file diff --git a/bounded-queue/src/cpu_item.rs b/src/models/items/cpu_item.rs similarity index 99% rename from bounded-queue/src/cpu_item.rs rename to src/models/items/cpu_item.rs index 9ca6ce7..0caf204 100644 --- a/bounded-queue/src/cpu_item.rs +++ b/src/models/items/cpu_item.rs @@ -37,6 +37,7 @@ impl CpuItem { } +/* #[cfg(test)] mod test { use super::CpuItem; @@ -59,4 +60,4 @@ mod test { assert_eq!(instance.brand(), String::from("Apple")); } */ -} +}*/ diff --git a/bounded-queue/src/memory_item.rs b/src/models/items/memory_item.rs similarity index 86% rename from bounded-queue/src/memory_item.rs rename to src/models/items/memory_item.rs index ce47983..9f856de 100644 --- a/bounded-queue/src/memory_item.rs +++ b/src/models/items/memory_item.rs @@ -16,6 +16,13 @@ impl MemoryItem { } } + pub fn update(&mut self, total_memory: u64, used_memory: u64, total_swap: u64, used_swap: u64) { + self.total_memory = total_memory; + self.used_memory = used_memory; + self.total_swap = total_swap; + self.used_swap = used_swap; + } + pub fn total_memory(&self) -> u64 { self.total_memory } diff --git a/src/models/items/mod.rs b/src/models/items/mod.rs new file mode 100644 index 0000000..82f6fde --- /dev/null +++ b/src/models/items/mod.rs @@ -0,0 +1,3 @@ +pub mod cpu_item; +pub mod memory_item; +pub mod temp_item; \ No newline at end of file diff --git a/bounded-queue/src/temp_item.rs b/src/models/items/temp_item.rs similarity index 100% rename from bounded-queue/src/temp_item.rs rename to src/models/items/temp_item.rs diff --git a/src/models/list_items_iter.rs b/src/models/list_items_iter.rs deleted file mode 100644 index 2042719..0000000 --- a/src/models/list_items_iter.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::process_list_item::ProcessListItem; -use super::process_list_items::ProcessListItems; - -pub struct ListItemsIterator<'a> { - list: &'a ProcessListItems, - index: usize, - increments: Option, - max_amount: usize, -} - -impl <'a> ListItemsIterator<'a> { - pub const fn new(list: &'a ProcessListItems, start: usize, max_amount: usize) -> Self { - Self { - list, - index: start, - increments: None, - max_amount, - } - } -} - -impl<'a> Iterator for ListItemsIterator<'a> { - type Item = (usize, &'a ProcessListItem); - - // required function for Iterator - fn next(&mut self) -> Option { - if self.increments.unwrap_or_default() < self.max_amount { - let items = &self.list.list_items; - let init = self.increments.is_none(); - - if let Some(i) = self.increments.as_mut() { - *i += 1; - } - else { - self.increments = Some(0); - }; - - if !init { - self.index += 1; - } - - if self.index >= self.list.list_items.len() { - return None - } - - return Some((self.index, &items[self.index])); - } - None - } -} \ No newline at end of file diff --git a/src/models/list_iter.rs b/src/models/list_iter.rs deleted file mode 100644 index bca7cef..0000000 --- a/src/models/list_iter.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::list_items_iter::ListItemsIterator; -use super::process_list_item::ProcessListItem; - -pub struct ListIterator<'a> { - item_iter: ListItemsIterator<'a>, - selection: Option, -} - -impl<'a> ListIterator<'a> { - pub const fn new(item_iter: ListItemsIterator<'a>, selection: Option) -> Self { - Self { - item_iter, - selection, - } - } -} - -impl<'a> Iterator for ListIterator<'a> { - type Item = (&'a ProcessListItem, bool); - - fn next(&mut self) -> Option { - self.item_iter - .next() - .map(|(index, item)| (item, self.selection.map(|i| i == index).unwrap_or_default())) - } -} \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index b33826a..b039fa3 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,3 @@ -pub mod process_list; -pub mod process_list_items; -pub mod process_list_item; -pub mod list_items_iter; -pub mod list_iter; \ No newline at end of file +pub mod p_list; +pub mod items; +pub mod b_queue; \ No newline at end of file diff --git a/process-list/src/list_items_iter.rs b/src/models/p_list/list_items_iter.rs similarity index 99% rename from process-list/src/list_items_iter.rs rename to src/models/p_list/list_items_iter.rs index 2042719..a236f3b 100644 --- a/process-list/src/list_items_iter.rs +++ b/src/models/p_list/list_items_iter.rs @@ -45,6 +45,7 @@ impl<'a> Iterator for ListItemsIterator<'a> { return Some((self.index, &items[self.index])); } + None } } \ No newline at end of file diff --git a/process-list/src/list_iter.rs b/src/models/p_list/list_iter.rs similarity index 100% rename from process-list/src/list_iter.rs rename to src/models/p_list/list_iter.rs diff --git a/src/models/p_list/mod.rs b/src/models/p_list/mod.rs new file mode 100644 index 0000000..b33826a --- /dev/null +++ b/src/models/p_list/mod.rs @@ -0,0 +1,5 @@ +pub mod process_list; +pub mod process_list_items; +pub mod process_list_item; +pub mod list_items_iter; +pub mod list_iter; \ No newline at end of file diff --git a/src/models/process_list.rs b/src/models/p_list/process_list.rs similarity index 100% rename from src/models/process_list.rs rename to src/models/p_list/process_list.rs diff --git a/src/models/process_list_item.rs b/src/models/p_list/process_list_item.rs similarity index 100% rename from src/models/process_list_item.rs rename to src/models/p_list/process_list_item.rs diff --git a/src/models/process_list_items.rs b/src/models/p_list/process_list_items.rs similarity index 85% rename from src/models/process_list_items.rs rename to src/models/p_list/process_list_items.rs index 66263d6..18aa3bd 100644 --- a/src/models/process_list_items.rs +++ b/src/models/p_list/process_list_items.rs @@ -1,5 +1,4 @@ use crate::components::sysinfo_wrapper::SysInfoWrapper; - use super::process_list::ListSortOrder; use super::process_list_item::ProcessListItem; use super::list_items_iter::ListItemsIterator; @@ -13,7 +12,7 @@ impl ProcessListItems { pub fn new(sysinfo: &SysInfoWrapper) -> Self { let mut processes = Vec::new(); - sysinfo.get_processes_test(&mut processes); + sysinfo.get_processes(&mut processes); Self { list_items: processes, @@ -36,7 +35,7 @@ impl ProcessListItems { } pub fn update(&mut self, sysinfo: &SysInfoWrapper, filter_text: &str) { - sysinfo.get_processes_test(&mut self.list_items); + sysinfo.get_processes(&mut self.list_items); if !filter_text.is_empty() { self.list_items.retain(|item| { @@ -98,10 +97,14 @@ impl ProcessListItems { } } -/* + +/* +// TODO: come up with new unit testing strategy #[cfg(test)] mod test { use std::vec; + use crate::components::sysinfo_wrapper::{self, SysInfoWrapper}; + use crate::config::{self, Config}; use crate::models::process_list::ListSortOrder; use crate::models::process_list_item::ProcessListItem; use crate::models::process_list_items::ProcessListItems; @@ -109,32 +112,41 @@ mod test { #[test] fn test_default() { let instance = ProcessListItems::default(); - assert_eq!(instance.items_len(), 0); + assert_eq!(instance.len(), 0); assert_eq!(instance.get_idx(4), None); - assert_eq!(instance.get_item_ref(0), None); + assert_eq!(instance.get_item(0), None); } #[test] fn test_new() { - let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); + let config = Config::default(); + let sysinfo_wrapper = SysInfoWrapper::new(config); + sysinfo_wrapper.refresh_all(); + + /*let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let clone_0 = item_0.clone(); let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); let clone_1 = item_1.clone(); let items = vec![item_0, item_1]; - let instance = ProcessListItems::new(items); + let instance = ProcessListItems::new(items);*/ - assert_eq!(instance.items_len(), 2); - assert_eq!(instance.get_idx(1), Some(0)); - assert_eq!(instance.get_idx(2), Some(1)); - assert_eq!(instance.get_idx(3), None); + let pl_instance = ProcessListItems::new(&sysinfo_wrapper); + + assert_eq!(pl_instance.len(), 2); + assert_eq!(pl_instance.get_idx(1), Some(0)); + assert_eq!(pl_instance.get_idx(2), Some(1)); + assert_eq!(pl_instance.get_idx(3), None); - assert_eq!(instance.get_item_ref(0), Some(&clone_0)); - assert_eq!(instance.get_item_ref(1), Some(&clone_1)); - assert_eq!(instance.get_item_ref(2), None); + assert_eq!(pl_instance.get_item(0), Some(&clone_0)); + assert_eq!(pl_instance.get_item(1), Some(&clone_1)); + assert_eq!(pl_instance.get_item(2), None); } #[test] fn test_filter() { + let config = Config::new + let system_wrapper = SysInfoWrapper::new(config) + let item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test"), String::from("test")); let clone_0 = item_0.clone(); let item_1 = ProcessListItem::new(2, String::from("b"), 2.0, 2, 0, 10, 10, String::from("test"), String::from("test")); @@ -143,9 +155,9 @@ mod test { let instance = ProcessListItems::new(items); let filtered_instance = instance.filter(&String::from("a")); - assert_eq!(filtered_instance.items_len(), 1); - assert_eq!(filtered_instance.get_item_ref(0), Some(&clone_0)); - assert_eq!(filtered_instance.get_item_ref(1), None); + assert_eq!(filtered_instance.len(), 1); + assert_eq!(filtered_instance.get_item(0), Some(&clone_0)); + assert_eq!(filtered_instance.get_item(1), None); assert_eq!(filtered_instance.get_idx(1), Some(0)); assert_eq!(filtered_instance.get_idx(2), None); } @@ -165,7 +177,7 @@ mod test { let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); assert_eq!(instance.get_idx(1), Some(0)); assert_eq!(instance.get_idx(2), Some(1)); - let _ = instance.update_items(new_items); + let _ = instance.update(new_items); let _ = instance.sort_items(&ListSortOrder::CpuUsageInc); // Pid 2 is not in new_items so it should be removed from the instance list. assert_eq!(instance.get_idx(2), None); @@ -226,5 +238,4 @@ mod test { assert_eq!(instance.get_idx(2), Some(1)); assert_eq!(instance.get_idx(3), Some(0)); } -} -*/ \ No newline at end of file +}*/ \ No newline at end of file