use std::sync::mpsc; use std::thread; use latchd_core::{accounts, preferences, session, session::SessionDescriptor}; use super::{App, NoticeLevel, unknown_account_status}; impl App { pub(super) fn apply_preferred_session(&mut self) { if let Some(index) = self .preferences .preferred_index(&self.username, &self.sessions) { self.selected = index; } } pub(super) fn remember_selected_session(&mut self, session: &SessionDescriptor) { self.preferences.remember(&self.username, session); let Some(path) = self.preference_path.clone() else { return; }; if let Err(error) = preferences::save(&path, &self.preferences) { tracing::debug!(%error, "could not persist last-session preference"); } } pub(super) fn poll_faillock(&mut self) { let Some(receiver) = &self.faillock_receiver else { return; }; match receiver.try_recv() { Ok(Ok(status)) => { self.faillock_status = Some(status); self.faillock_receiver = None; if status.available { if status.locked_out { self.notify( "Account has reached the advisory faillock threshold", NoticeLevel::Warning, ); } else if status.failures > 0 { self.notify( &format!("{} failed attempt(s) recorded", status.failures), NoticeLevel::Info, ); } } } Ok(Err(error)) => { tracing::debug!(%error, "faillock query failed"); self.faillock_receiver = None; } Err(mpsc::TryRecvError::Empty) => {} Err(mpsc::TryRecvError::Disconnected) => self.faillock_receiver = None, } } pub(super) fn poll_refresh(&mut self) { let Some(receiver) = &self.refresh_receiver else { return; }; match receiver.try_recv() { Ok((sessions, accounts, account_statuses)) => { self.sessions = sessions; self.accounts = accounts; self.account_statuses = account_statuses; self.refresh_receiver = None; self.refreshing = false; self.reconcile_selection(); self.status = format!( "Discovery refreshed: {} session(s), {} account(s)", self.sessions.len(), self.accounts.len() ); self.notify("Account and session discovery refreshed", NoticeLevel::Info); } Err(mpsc::TryRecvError::Empty) => {} Err(mpsc::TryRecvError::Disconnected) => { self.refresh_receiver = None; self.refreshing = false; self.status = "Discovery refresh failed".into(); self.notify( "Could not refresh account and session discovery", NoticeLevel::Warning, ); } } } pub(super) fn start_refresh(&mut self) { if self.refreshing { self.status = "Discovery refresh is already running".into(); return; } let config = self.config.clone(); let (sender, receiver) = mpsc::sync_channel(1); let task = thread::Builder::new() .name("latchd-discovery".into()) .spawn(move || { let sessions = session::discover(&config); let (accounts, account_statuses) = if config.accounts.enumerate { let records = accounts::discover_with_status(&config.accounts); records .into_iter() .map(|record| (record.identity, record.status)) .unzip() } else { (Vec::new(), Vec::new()) }; let _ = sender.send((sessions, accounts, account_statuses)); }); match task { Ok(_) => { self.refresh_receiver = Some(receiver); self.refreshing = true; self.status = "Refreshing accounts and sessions…".into(); } Err(error) => { tracing::debug!(%error, "could not start discovery refresh"); self.status = format!("Could not start discovery refresh: {error}"); } } } pub(super) fn reconcile_selection(&mut self) { if self.sessions.is_empty() { self.selected = 0; } else if self.selected >= self.sessions.len() { self.selected = self.sessions.len() - 1; } if self.accounts.is_empty() { self.account_index = None; self.account_statuses.clear(); } else if self.account_statuses.len() != self.accounts.len() { self.account_statuses .resize(self.accounts.len(), unknown_account_status()); } else if self .account_index .is_some_and(|index| index >= self.accounts.len()) { self.account_index = Some(0); } if let Some(index) = self.account_index { if let Some(account) = self.accounts.get(index) { self.username = account.username.clone(); } } } pub(super) fn start_faillock_query(&mut self) { let username = self.username.clone(); if username.is_empty() { self.faillock_receiver = None; self.faillock_status = None; return; } let config = self.config.accounts.clone(); let (sender, receiver) = mpsc::sync_channel(1); self.faillock_receiver = Some(receiver); let task = thread::Builder::new() .name("latchd-faillock".into()) .spawn(move || { let result = accounts::faillock_status(&username, &config); let _ = sender.send(result); }); if let Err(error) = task { tracing::debug!(%error, "could not start faillock query"); self.faillock_receiver = None; } } }