diff --git a/Cargo.lock b/Cargo.lock index af4a172..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,24 +356,15 @@ name = "process-display" version = "0.1.0" dependencies = [ "anyhow", - "bounded-queue", "clippy", "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..d1452ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,6 @@ edition = "2021" [workspace] members = [ - "process-list", - "bounded-queue", "dynamic-list" ] @@ -19,7 +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" -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/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 80f4c37..0000000 --- a/bounded-queue/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod cpu_item; -mod memory_item; -mod bounded_queue; - -pub use cpu_item::CpuItem; -pub use memory_item::MemoryItem; -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/src/app.rs b/src/app.rs index 7721f67..9ca01a1 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, @@ -11,13 +13,13 @@ use crate::components::{ EventState, DrawableComponent, help::HelpComponent, - command, - command::CommandInfo, }; enum MainFocus { CPU, Process, + Memory, + Temp, } pub struct App { @@ -26,6 +28,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 +38,21 @@ 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 process = ProcessComponent::new(config.clone(), &system_wrapper); + 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, expand: false, system_wrapper, - process: ProcessComponent::new(config.clone(), processes), + process, cpu, + memory, + temp, help: HelpComponent::new(config.clone()), error: ErrorComponent::new(config.clone()), config: config.clone(), @@ -60,27 +62,14 @@ impl App { pub fn refresh_event(&mut self) -> Result { self.system_wrapper.refresh_all(); - self.update_process(); - - self.update_cpu(); - - self.update_cmds(); + self.process.update(&self.system_wrapper); + self.memory.update(&self.system_wrapper); + self.cpu.update(&self.system_wrapper); + self.temp.update(&self.system_wrapper); Ok(EventState::Consumed) } - 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 } @@ -97,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) } @@ -123,10 +105,26 @@ 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) } + // terminate case + if key.code == self.config.key_config.terminate { + self.process.terminate_process(&self.system_wrapper); + + return Ok(EventState::Consumed) + } } } @@ -137,6 +135,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 +181,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 +214,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 +224,7 @@ impl App { self.process.draw( f, - vertical_chunks[1], + vertical_chunks[2], matches!(self.focus, MainFocus::Process) )?; @@ -213,6 +233,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 +253,7 @@ 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)), @@ -243,5 +271,5 @@ impl App { ]; res - } + }*/ } 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..f226586 100644 --- a/src/components/cpu.rs +++ b/src/components/cpu.rs @@ -1,23 +1,14 @@ 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::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}; -#[derive(Default)] -pub struct CPUComponent { - cpus: BTreeMap>, - ui_selection: usize, - config: Config, -} - #[derive(PartialEq, Clone, Copy)] pub enum ColorWheel { Red, @@ -71,10 +62,39 @@ impl ColorWheel { } } +#[derive(Default)] +pub struct CPUComponent { + cpus: BTreeMap>, + ui_selection: usize, + config: Config, +} + 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(|| { @@ -110,12 +130,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 +192,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 +206,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 +242,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 +260,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..0594b67 --- /dev/null +++ b/src/components/memory.rs @@ -0,0 +1,84 @@ +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; + +pub struct MemoryComponent { + config: Config, + memory: MemoryItem, +} + +impl MemoryComponent { + pub fn new(config: Config, sysinfo: &SysInfoWrapper) -> Self { + let mut memory = MemoryItem::default(); + sysinfo.get_memory(&mut memory); + + Self { + config, + memory, + } + } + + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + sysinfo.get_memory(&mut self.memory); + } +} + +impl Component for MemoryComponent { + fn event(&mut self, _key: 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 = ( self.memory.used_memory_gb() / self.memory.total_memory_gb() ) * 100_f64; + let ram_label = "RAM Usage"; + 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)) + .gauge_style(Style::new().red().on_black().italic()) + .percent(ram_percent as u16); + + // swap widget + let swap_percent = ( self.memory.used_swap_gb() / self.memory.total_swap_gb() ) * 100_f64; + let swap_label = "Swap Usage"; + 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)) + .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..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 process_list::{ListSortOrder, MoveSelection}; +use crate::models::p_list::process_list::{ListSortOrder, MoveSelection}; use super::config::KeyConfig; pub mod sysinfo_wrapper; pub mod filter; @@ -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<()>; @@ -37,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(), @@ -41,80 +40,55 @@ 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 - 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); + pub fn update(&mut self, sysinfo: &SysInfoWrapper) { + self.list.update(sysinfo, ""); 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); + filtered_list.update(sysinfo, self.filter.input_str()); } - - true } - // 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) } } 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 +102,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,9 +113,7 @@ 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 list_sort( if let Some(list) = self.filtered_list.as_mut() { list } @@ -160,13 +131,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 +142,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 +155,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 +171,7 @@ impl DrawableComponent for ProcessComponent { &self.list }; - // updating the scroll struct--calculates the position at the top of the displayed list + // update vert scroll list.selection().map_or_else( { || self.scroll.reset() @@ -238,7 +201,8 @@ impl DrawableComponent for ProcessComponent { else { false }, - self.config.theme_config.clone() + self.config.theme_config.clone(), + list.sort_order(), ); self.scroll.draw( @@ -267,6 +231,133 @@ impl DrawableComponent for ProcessComponent { } } +use ratatui::widgets::{block::*, *}; +use crate::models::p_list::list_iter::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..7f49503 100644 --- a/src/components/sysinfo_wrapper.rs +++ b/src/components/sysinfo_wrapper.rs @@ -1,12 +1,13 @@ -use sysinfo::{Pid, System}; -use process_list::ProcessListItem; -use bounded_queue::CpuItem; +use sysinfo::{Pid, System, Components}; +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 } @@ -14,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 { @@ -29,9 +32,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 +39,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); @@ -50,8 +47,8 @@ impl SysInfoWrapper { cpus } - pub fn get_processes(&self) -> Vec { - let mut processes: Vec = Vec::new(); + pub fn get_processes(&self, processes: &mut Vec) { + processes.clear(); for (pid, process) in self.system.processes() { let name = if let Some(name) = process.name().to_str() { @@ -77,23 +74,79 @@ 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); } + } + + 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(); + + memory.update(total_memory, used_memory, total_swap, used_swap); + } + + pub fn get_temps(&self, temps: &mut Vec) { + temps.clear(); + + for component in &self.components { + let temp = if let Some(temp) = component.temperature() { + temp + } + else { + 0_f32 + }; - processes + 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); + } } - 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 new file mode 100644 index 0000000..ba49e3d --- /dev/null +++ b/src/components/temp.rs @@ -0,0 +1,138 @@ +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}; + +pub struct TempComponent { + config: Config, + temps: Vec, + ui_selection: usize, + vertical_scroll: VerticalScroll, +} + +impl TempComponent { + 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, sysinfo: &SysInfoWrapper) { + sysinfo.get_temps(&mut self.temps); + } +} + +impl Component for TempComponent { + fn event(&mut self, key: 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) + } +} + +//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 { + self.config.theme_config.style_border_not_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 { + self.config.theme_config.style_border_not_focused + }; + + let table = Table::new(rows, widths) + .header(header) + .block(Block::default().borders(Borders::ALL).title(block_title)) + .style(block_style); + + f.render_widget(table, area); + + 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..6edd703 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,34 +17,28 @@ use crate::app::App; pub mod app; pub mod config; pub mod components; -pub mod ui; 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<()> { - // 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 +49,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 +66,9 @@ fn main() -> Result<()> { app.error.set(err.to_string())?; } } - Event::Tick => {} + Event::Tick => { + continue + } } } @@ -87,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 82% rename from bounded-queue/src/cpu_item.rs rename to src/models/items/cpu_item.rs index d6d94e3..0caf204 100644 --- a/bounded-queue/src/cpu_item.rs +++ b/src/models/items/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,11 +35,9 @@ impl CpuItem { self.frequency } - pub fn brand(&self) -> String { - self.brand.clone() - } } +/* #[cfg(test)] mod test { use super::CpuItem; @@ -71,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 53% rename from bounded-queue/src/memory_item.rs rename to src/models/items/memory_item.rs index f1bd572..9f856de 100644 --- a/bounded-queue/src/memory_item.rs +++ b/src/models/items/memory_item.rs @@ -2,20 +2,27 @@ 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, } } + 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 } @@ -23,14 +30,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 +39,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 +65,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/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/src/models/items/temp_item.rs b/src/models/items/temp_item.rs new file mode 100644 index 0000000..5552636 --- /dev/null +++ b/src/models/items/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/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..b039fa3 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,3 @@ +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/process-list/src/process_list.rs b/src/models/p_list/process_list.rs similarity index 78% rename from process-list/src/process_list.rs rename to src/models/p_list/process_list.rs index 6dbb5a8..77c5c82 100644 --- a/process-list/src/process_list.rs +++ b/src/models/p_list/process_list.rs @@ -1,6 +1,7 @@ -use crate::process_list_items::ProcessListItems; -use crate::process_list_item::ProcessListItem; -use crate::list_iter::ListIterator; +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 { @@ -21,7 +22,7 @@ pub enum MoveSelection { MultipleUp, MultipleDown, Top, - End, + Bottom, } #[derive(Default)] @@ -33,11 +34,9 @@ pub struct ProcessList { } impl ProcessList { - pub fn new(list: Vec) -> Self { - assert!(!list.is_empty()); - + pub fn new(sysinfo: &SysInfoWrapper) -> Self { Self { - items: ProcessListItems::new(list), + items: ProcessListItems::new(sysinfo), sort: ListSortOrder::default(), follow_selection: false, selection: Some(0), @@ -46,7 +45,7 @@ impl ProcessList { pub fn filter(&self, filter_text: &str) -> Self { let items = self.items.filter(filter_text); - let len = items.items_len(); + let len = items.len(); Self { items, @@ -61,14 +60,16 @@ impl ProcessList { } } - pub fn update(&mut self, new_list: Vec) { - let selected_item: Option<&ProcessListItem> = self.items.get_item_ref(self.selection.unwrap_or_default()); + 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_items(new_list); + + self.items.update(sysinfo, filter_text); + self.items.sort_items(&self.sort); - if self.items.items_len() == 0 { + if self.items.len() == 0 { self.selection = None; return } @@ -78,7 +79,7 @@ impl ProcessList { } else { if let Some(selection) = self.selection { - let max_idx = self.items.items_len().saturating_sub(1); + let max_idx = self.items.len().saturating_sub(1); if selection > max_idx { self.selection = Some(max_idx) } @@ -91,7 +92,7 @@ impl ProcessList { } pub fn sort(&mut self, sort: &ListSortOrder) { - let selected_item: Option<&ProcessListItem> = self.items.get_item_ref(self.selection.unwrap_or_default()); + let selected_item: Option<&ProcessListItem> = self.items.get_item(self.selection.unwrap_or_default()); let pid: Option = selected_item.map(|item| item.pid()); @@ -111,8 +112,8 @@ impl ProcessList { 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), + MoveSelection::Bottom => self.selection_bottom(selection), + MoveSelection::Top => self.selection_top(selection), }; self.selection = new_idx; @@ -121,7 +122,7 @@ impl ProcessList { 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); + let max_idx = self.items.len().saturating_sub(1); 'a: for _ in 0..lines { if new_idx >= max_idx { @@ -147,14 +148,14 @@ impl ProcessList { Some(new_idx) } - fn selection_end(&self, _current_idx: usize) -> Option { - let max_idx = self.items.items_len().saturating_sub(1); + fn selection_bottom(&self, _current_idx: usize) -> Option { + let max_idx = self.items.len().saturating_sub(1); Some(max_idx) } - fn selection_start(&self, _current_idx: usize) -> Option { + fn selection_top(&self, _current_idx: usize) -> Option { let min_idx = 0; Some(min_idx) @@ -164,54 +165,59 @@ impl ProcessList { 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 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_ref(selection); + let selected_item = self.items.get_item(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) { + 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::process_list::{ProcessList, ListSortOrder, MoveSelection}; - use crate::process_list_item::ProcessListItem; + use crate::models::process_list::{ProcessList, ListSortOrder, MoveSelection}; + use crate::models::process_list_item::ProcessListItem; #[test] fn test_constructors() { @@ -221,8 +227,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 +250,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 +260,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 +282,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 +310,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 +321,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 +339,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)); @@ -363,3 +369,4 @@ mod test { assert_eq!(instance.selection(), Some(0)); } } +*/ \ No newline at end of file diff --git a/process-list/src/process_list_item.rs b/src/models/p_list/process_list_item.rs similarity index 87% rename from process-list/src/process_list_item.rs rename to src/models/p_list/process_list_item.rs index ee5b5fe..00ae816 100644 --- a/process-list/src/process_list_item.rs +++ b/src/models/p_list/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); @@ -136,4 +153,4 @@ pub mod test { //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/process-list/src/process_list_items.rs b/src/models/p_list/process_list_items.rs similarity index 67% rename from process-list/src/process_list_items.rs rename to src/models/p_list/process_list_items.rs index ae31d39..18aa3bd 100644 --- a/process-list/src/process_list_items.rs +++ b/src/models/p_list/process_list_items.rs @@ -1,6 +1,7 @@ -use crate::process_list::ListSortOrder; -use crate::process_list_item::ProcessListItem; -use crate::list_items_iter::ListItemsIterator; +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 { @@ -8,31 +9,40 @@ pub struct ProcessListItems { } impl ProcessListItems { - pub fn new(list: Vec) -> Self { - Self { - list_items: list, - } - } + pub fn new(sysinfo: &SysInfoWrapper) -> Self { + let mut processes = Vec::new(); - pub fn filter(&self, filter_text: &str) -> Self { + sysinfo.get_processes(&mut processes); + Self { - list_items: self.create_filtered_items(filter_text) + list_items: processes, } } - fn create_filtered_items(&self, filter_text: &str) -> Vec { - self.list_items + 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() + .collect(); + + Self { + list_items + } } - pub fn update_items(&mut self, new_list: Vec) { - self.list_items = new_list; + pub fn update(&mut self, sysinfo: &SysInfoWrapper, filter_text: &str) { + sysinfo.get_processes(&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) { @@ -64,7 +74,7 @@ impl ProcessListItems { } } - pub fn get_item_ref(&self, idx: usize) -> Option<&ProcessListItem> { + pub fn get_item(&self, idx: usize) -> Option<&ProcessListItem> { self.list_items.get(idx) } @@ -78,7 +88,7 @@ impl ProcessListItems { None } - pub fn items_len(&self) -> usize { + pub fn len(&self) -> usize { self.list_items.len() } @@ -87,73 +97,87 @@ impl ProcessListItems { } } + +/* +// TODO: come up with new unit testing strategy #[cfg(test)] mod test { use std::vec; - use crate::process_list::ListSortOrder; - use crate::process_list_item::ProcessListItem; - use crate::process_list_items::ProcessListItems; + 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; #[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")); + 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")); + 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 item_0 = ProcessListItem::new(1, String::from("a"), 1.0, 1, 0, 10, 10, String::from("test")); + 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")); + 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.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); } #[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); 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); @@ -165,9 +189,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); @@ -214,4 +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 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); - } - -}