1use crate::config::PrtConfig;
7use crate::core::bandwidth::BandwidthTracker;
8use crate::core::{container, scanner, suspicious};
9use crate::i18n;
10use crate::known_ports;
11use crate::model::{EntryStatus, SortState, TrackedEntry, GONE_RETENTION};
12use std::time::Instant;
13
14pub struct Session {
22 pub entries: Vec<TrackedEntry>,
23 pub sort: SortState,
24 pub is_elevated: bool,
25 pub is_root: bool,
26 pub config: PrtConfig,
27 pub bandwidth: BandwidthTracker,
28 sudo_password: Option<String>,
29}
30
31impl Default for Session {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Session {
38 pub fn new() -> Self {
39 Self {
40 entries: Vec::new(),
41 sort: SortState::default(),
42 is_elevated: false,
43 is_root: scanner::is_root(),
44 config: crate::config::load_config(),
45 bandwidth: BandwidthTracker::new(),
46 sudo_password: None,
47 }
48 }
49
50 pub fn refresh(&mut self) -> Result<(), String> {
52 let scan_result = if let Some(password) = &self.sudo_password {
53 scanner::scan_with_sudo(password)
54 } else {
55 scanner::scan()
56 };
57
58 match scan_result {
59 Ok(new_entries) => {
60 let now = Instant::now();
61 self.entries = scanner::diff_entries(&self.entries, new_entries, now);
62
63 self.enrich_service_names();
65 self.enrich_suspicious();
66 self.enrich_containers();
67
68 self.entries.retain(|e| {
69 e.status != EntryStatus::Gone || now.duration_since(e.seen_at) < GONE_RETENTION
70 });
71
72 self.bandwidth.sample();
74
75 scanner::sort_entries(&mut self.entries, &self.sort);
76 Ok(())
77 }
78 Err(e) => {
79 let s = i18n::strings();
80 Err(s.fmt_scan_error(&e.to_string()))
81 }
82 }
83 }
84
85 fn enrich_service_names(&mut self) {
88 for entry in &mut self.entries {
89 if entry.status != EntryStatus::Gone {
90 entry.service_name =
91 known_ports::lookup(entry.entry.local_port(), &self.config.known_ports);
92 }
93 }
94 }
95
96 fn enrich_suspicious(&mut self) {
99 for entry in &mut self.entries {
100 if entry.status != EntryStatus::Gone {
101 entry.suspicious = suspicious::check(&entry.entry);
102 }
103 }
104 }
105
106 fn enrich_containers(&mut self) {
109 let pids: Vec<u32> = self
110 .entries
111 .iter()
112 .filter(|e| e.status != EntryStatus::Gone)
113 .map(|e| e.entry.process.pid)
114 .collect();
115
116 let names = container::resolve_container_names(&pids);
117
118 for entry in &mut self.entries {
119 if entry.status != EntryStatus::Gone {
120 entry.container_name = names.get(&entry.entry.process.pid).cloned();
121 }
122 }
123 }
124
125 pub fn sudo_password(&self) -> Option<&str> {
127 self.sudo_password.as_deref()
128 }
129
130 pub fn filtered_indices(&self, query: &str) -> Vec<usize> {
131 scanner::filter_indices(&self.entries, query)
132 }
133
134 pub fn try_sudo(&mut self, password: &str) -> String {
136 let s = i18n::strings();
137 match scanner::scan_with_sudo(password) {
138 Ok(new_entries) => {
139 self.sudo_password = Some(password.to_string());
140 self.is_elevated = true;
141 self.is_root = true;
142 let now = Instant::now();
143 self.entries = scanner::diff_entries(&self.entries, new_entries, now);
144
145 self.entries.retain(|e| {
147 e.status != EntryStatus::Gone || e.seen_at.elapsed() < GONE_RETENTION
148 });
149
150 self.enrich_service_names();
152 self.enrich_suspicious();
153 self.enrich_containers();
154
155 self.bandwidth.sample();
157
158 scanner::sort_entries(&mut self.entries, &self.sort);
159 s.sudo_elevated.to_string()
160 }
161 Err(e) => {
162 self.sudo_password = None;
163 self.is_elevated = false;
164 let msg = e.to_string();
165 if msg.contains("incorrect password") || msg.contains("Sorry") {
166 s.sudo_wrong_password.to_string()
167 } else {
168 s.fmt_sudo_error(&msg)
169 }
170 }
171 }
172 }
173}