From 5e5e6f6806859fdaf5d521e030ef283faba0dfcb Mon Sep 17 00:00:00 2001 From: Albert Armea Date: Mon, 29 Dec 2025 17:51:55 -0500 Subject: [PATCH] Lint: electric boogaloo --- CONTRIBUTING.md | 4 + crates/shepherd-api/src/commands.rs | 6 - crates/shepherd-config/src/policy.rs | 3 +- crates/shepherd-config/src/validation.rs | 5 +- crates/shepherd-core/src/engine.rs | 28 ++--- crates/shepherd-host-api/src/volume.rs | 4 +- crates/shepherd-host-linux/src/adapter.rs | 9 +- crates/shepherd-host-linux/src/process.rs | 14 +-- crates/shepherd-host-linux/src/volume.rs | 15 +-- crates/shepherd-hud/src/app.rs | 9 -- crates/shepherd-hud/src/battery.rs | 11 +- crates/shepherd-hud/src/main.rs | 1 - crates/shepherd-hud/src/state.rs | 46 ++----- crates/shepherd-hud/src/volume.rs | 46 ------- crates/shepherd-ipc/src/server.rs | 7 +- crates/shepherd-launcher-ui/src/app.rs | 6 +- crates/shepherd-launcher-ui/src/client.rs | 9 +- crates/shepherd-launcher-ui/src/grid.rs | 9 +- crates/shepherd-launcher-ui/src/main.rs | 1 - crates/shepherd-launcher-ui/src/state.rs | 17 ++- crates/shepherd-store/src/sqlite.rs | 4 +- crates/shepherdd/src/main.rs | 143 +++++----------------- 22 files changed, 106 insertions(+), 291 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c724e45..4e1ef72 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,12 +52,16 @@ Run the test suite: ```sh cargo test +# as run in CI: +cargo test --all-targets ``` Run lint checks: ```sh cargo clippy +# as run in CI: +cargo clippy --all-targets -- -D warnings ``` diff --git a/crates/shepherd-api/src/commands.rs b/crates/shepherd-api/src/commands.rs index 123419c..2e1ff91 100644 --- a/crates/shepherd-api/src/commands.rs +++ b/crates/shepherd-api/src/commands.rs @@ -135,12 +135,6 @@ pub enum Command { /// Set volume to a specific percentage SetVolume { percent: u8 }, - /// Increase volume by a step - VolumeUp { step: u8 }, - - /// Decrease volume by a step - VolumeDown { step: u8 }, - /// Toggle mute state ToggleMute, diff --git a/crates/shepherd-config/src/policy.rs b/crates/shepherd-config/src/policy.rs index 480f165..841b95b 100644 --- a/crates/shepherd-config/src/policy.rs +++ b/crates/shepherd-config/src/policy.rs @@ -1,10 +1,9 @@ //! Validated policy structures -use crate::schema::{RawConfig, RawDays, RawEntry, RawEntryKind, RawVolumeConfig, RawServiceConfig, RawWarningThreshold}; +use crate::schema::{RawConfig, RawEntry, RawEntryKind, RawVolumeConfig, RawServiceConfig, RawWarningThreshold}; use crate::validation::{parse_days, parse_time}; use shepherd_api::{EntryKind, WarningSeverity, WarningThreshold}; use shepherd_util::{DaysOfWeek, EntryId, TimeWindow, WallClock}; -use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; diff --git a/crates/shepherd-config/src/validation.rs b/crates/shepherd-config/src/validation.rs index 1442bbc..5905f0c 100644 --- a/crates/shepherd-config/src/validation.rs +++ b/crates/shepherd-config/src/validation.rs @@ -113,8 +113,8 @@ fn validate_entry(entry: &RawEntry, config: &RawConfig) -> Vec .or(config.service.default_max_run_seconds); // Only validate warnings if max_run is Some and not 0 (unlimited) - if let (Some(warnings), Some(max_run)) = (&entry.warnings, max_run) { - if max_run > 0 { + if let (Some(warnings), Some(max_run)) = (&entry.warnings, max_run) + && max_run > 0 { for warning in warnings { if warning.seconds_before >= max_run { errors.push(ValidationError::WarningExceedsMaxRun { @@ -124,7 +124,6 @@ fn validate_entry(entry: &RawEntry, config: &RawConfig) -> Vec }); } } - } // Note: warnings are ignored for unlimited entries (max_run = 0) } diff --git a/crates/shepherd-core/src/engine.rs b/crates/shepherd-core/src/engine.rs index fe394d1..cf0cb6a 100644 --- a/crates/shepherd-core/src/engine.rs +++ b/crates/shepherd-core/src/engine.rs @@ -2,7 +2,7 @@ use chrono::{DateTime, Local}; use shepherd_api::{ - ServiceStateSnapshot, EntryKindTag, EntryView, ReasonCode, SessionEndReason, + ServiceStateSnapshot, EntryView, ReasonCode, SessionEndReason, WarningSeverity, API_VERSION, }; use shepherd_config::{Entry, Policy}; @@ -11,7 +11,7 @@ use shepherd_store::{AuditEvent, AuditEventType, Store}; use shepherd_util::{EntryId, MonotonicInstant, SessionId}; use std::sync::Arc; use std::time::Duration; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; use crate::{ActiveSession, CoreEvent, SessionPlan, StopResult}; @@ -128,22 +128,20 @@ impl CoreEngine { } // Check cooldown - if let Ok(Some(until)) = self.store.get_cooldown_until(&entry.id) { - if until > now { + if let Ok(Some(until)) = self.store.get_cooldown_until(&entry.id) + && until > now { enabled = false; reasons.push(ReasonCode::CooldownActive { available_at: until }); } - } // Check daily quota if let Some(quota) = entry.limits.daily_quota { let today = now.date_naive(); - if let Ok(used) = self.store.get_usage(&entry.id, today) { - if used >= quota { + if let Ok(used) = self.store.get_usage(&entry.id, today) + && used >= quota { enabled = false; reasons.push(ReasonCode::QuotaExhausted { used, quota }); } - } } // Calculate max run if enabled (None when disabled, Some(None) flattened for unlimited) @@ -393,12 +391,11 @@ impl CoreEngine { let _ = self.store.add_usage(&session.plan.entry_id, today, duration); // Set cooldown if configured - if let Some(entry) = self.policy.get_entry(&session.plan.entry_id) { - if let Some(cooldown) = entry.limits.cooldown { + if let Some(entry) = self.policy.get_entry(&session.plan.entry_id) + && let Some(cooldown) = entry.limits.cooldown { let until = now + chrono::Duration::from_std(cooldown).unwrap(); let _ = self.store.set_cooldown_until(&session.plan.entry_id, until); } - } // Log to audit let _ = self.store.append_audit(AuditEvent::new(AuditEventType::SessionEnded { @@ -443,12 +440,11 @@ impl CoreEngine { let _ = self.store.add_usage(&session.plan.entry_id, today, duration); // Set cooldown if configured - if let Some(entry) = self.policy.get_entry(&session.plan.entry_id) { - if let Some(cooldown) = entry.limits.cooldown { + if let Some(entry) = self.policy.get_entry(&session.plan.entry_id) + && let Some(cooldown) = entry.limits.cooldown { let until = now + chrono::Duration::from_std(cooldown).unwrap(); let _ = self.store.set_cooldown_until(&session.plan.entry_id, until); } - } // Log to audit let _ = self.store.append_audit(AuditEvent::new(AuditEventType::SessionEnded { @@ -510,8 +506,8 @@ impl CoreEngine { pub fn extend_current( &mut self, by: Duration, - now_mono: MonotonicInstant, - now: DateTime, + _now_mono: MonotonicInstant, + _now: DateTime, ) -> Option> { let session = self.current_session.as_mut()?; diff --git a/crates/shepherd-host-api/src/volume.rs b/crates/shepherd-host-api/src/volume.rs index efcba25..4f4f7eb 100644 --- a/crates/shepherd-host-api/src/volume.rs +++ b/crates/shepherd-host-api/src/volume.rs @@ -37,9 +37,7 @@ pub struct VolumeStatus { impl VolumeStatus { /// Get an icon name for the current volume status pub fn icon_name(&self) -> &'static str { - if self.muted { - "audio-volume-muted-symbolic" - } else if self.percent == 0 { + if self.muted || self.percent == 0 { "audio-volume-muted-symbolic" } else if self.percent < 33 { "audio-volume-low-symbolic" diff --git a/crates/shepherd-host-linux/src/adapter.rs b/crates/shepherd-host-linux/src/adapter.rs index f6a4f4a..1f4a7fb 100644 --- a/crates/shepherd-host-linux/src/adapter.rs +++ b/crates/shepherd-host-linux/src/adapter.rs @@ -126,11 +126,10 @@ impl HostAdapter for LinuxHost { // followed by any additional args. let mut argv = vec!["snap".to_string(), "run".to_string(), snap_name.clone()]; // If a custom command is specified (different from snap_name), add it - if let Some(cmd) = command { - if cmd != snap_name { + if let Some(cmd) = command + && cmd != snap_name { argv.push(cmd.clone()); } - } argv.extend(args.clone()); (argv, env.clone(), None, Some(snap_name.clone())) } @@ -336,8 +335,8 @@ mod tests { // Process should have exited match handle.payload() { - HostHandlePayload::Linux { pid, .. } => { - let procs = host.processes.lock().unwrap(); + HostHandlePayload::Linux { pid: _, .. } => { + let _procs = host.processes.lock().unwrap(); // Process may or may not still be tracked depending on monitor timing } _ => panic!("Expected Linux handle"), diff --git a/crates/shepherd-host-linux/src/process.rs b/crates/shepherd-host-linux/src/process.rs index be48d17..72cd8b5 100644 --- a/crates/shepherd-host-linux/src/process.rs +++ b/crates/shepherd-host-linux/src/process.rs @@ -264,7 +264,7 @@ impl ManagedProcess { unsafe { cmd.pre_exec(|| { nix::unistd::setsid().map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, e.to_string()) + std::io::Error::other(e.to_string()) })?; Ok(()) }); @@ -304,14 +304,12 @@ impl ManagedProcess { if let Some(paren_end) = stat.rfind(')') { let after_comm = &stat[paren_end + 2..]; let fields: Vec<&str> = after_comm.split_whitespace().collect(); - if fields.len() >= 2 { - if let Ok(ppid) = fields[1].parse::() { - if ppid == parent_pid { - descendants.push(pid); - to_check.push(pid); - } + if fields.len() >= 2 + && let Ok(ppid) = fields[1].parse::() + && ppid == parent_pid { + descendants.push(pid); + to_check.push(pid); } - } } } } diff --git a/crates/shepherd-host-linux/src/volume.rs b/crates/shepherd-host-linux/src/volume.rs index c83d5f6..d61e2f9 100644 --- a/crates/shepherd-host-linux/src/volume.rs +++ b/crates/shepherd-host-linux/src/volume.rs @@ -147,11 +147,10 @@ impl LinuxVolumeController { debug!("pactl get-sink-volume output: {}", stdout.trim()); // Output: "Volume: front-left: 65536 / 100% / -0.00 dB, front-right: ..." - if let Some(percent_str) = stdout.split('/').nth(1) { - if let Ok(percent) = percent_str.trim().trim_end_matches('%').parse::() { + if let Some(percent_str) = stdout.split('/').nth(1) + && let Ok(percent) = percent_str.trim().trim_end_matches('%').parse::() { status.percent = percent; } - } } // Check mute status @@ -184,13 +183,11 @@ impl LinuxVolumeController { for line in stdout.lines() { if line.contains("Playback") && line.contains('%') { // Extract percentage: [100%] - if let Some(start) = line.find('[') { - if let Some(end) = line[start..].find('%') { - if let Ok(percent) = line[start + 1..start + end].parse::() { - status.percent = percent; - } + if let Some(start) = line.find('[') + && let Some(end) = line[start..].find('%') + && let Ok(percent) = line[start + 1..start + end].parse::() { + status.percent = percent; } - } // Check mute status: [on] or [off] status.muted = line.contains("[off]"); break; diff --git a/crates/shepherd-hud/src/app.rs b/crates/shepherd-hud/src/app.rs index b4d9f9b..f36dbe5 100644 --- a/crates/shepherd-hud/src/app.rs +++ b/crates/shepherd-hud/src/app.rs @@ -56,9 +56,6 @@ impl HudApp { } }); - // Start periodic updates for battery/volume - start_metrics_updates(state.clone()); - // Subscribe to state changes let window_clone = window.clone(); let state_clone = state.clone(); @@ -727,9 +724,3 @@ fn run_event_loop(socket_path: PathBuf, state: SharedState) -> anyhow::Result<() } }) } - -fn start_metrics_updates(_state: SharedState) { - // Battery and volume are now updated in the main UI loop - // This function could be used for more expensive operations - // that don't need to run as frequently -} diff --git a/crates/shepherd-hud/src/battery.rs b/crates/shepherd-hud/src/battery.rs index bf6e917..57e86b3 100644 --- a/crates/shepherd-hud/src/battery.rs +++ b/crates/shepherd-hud/src/battery.rs @@ -34,19 +34,17 @@ impl BatteryStatus { let name_str = name.to_string_lossy(); // Check for battery - if name_str.starts_with("BAT") { - if let Some((percent, charging)) = read_battery_info(&path) { + if name_str.starts_with("BAT") + && let Some((percent, charging)) = read_battery_info(&path) { status.percent = Some(percent); status.charging = charging; } - } // Check for AC adapter - if name_str.starts_with("AC") || name_str.contains("ADP") { - if let Some(online) = read_ac_status(&path) { + if (name_str.starts_with("AC") || name_str.contains("ADP")) + && let Some(online) = read_ac_status(&path) { status.ac_connected = online; } - } } } @@ -70,6 +68,7 @@ impl BatteryStatus { } /// Check if battery is critically low + #[allow(dead_code)] pub fn is_critical(&self) -> bool { matches!(self.percent, Some(p) if p < 10 && !self.charging) } diff --git a/crates/shepherd-hud/src/main.rs b/crates/shepherd-hud/src/main.rs index 2668807..e0aeb96 100644 --- a/crates/shepherd-hud/src/main.rs +++ b/crates/shepherd-hud/src/main.rs @@ -11,7 +11,6 @@ mod volume; use anyhow::Result; use clap::Parser; -use gtk4::prelude::*; use std::path::PathBuf; use tracing_subscriber::EnvFilter; diff --git a/crates/shepherd-hud/src/state.rs b/crates/shepherd-hud/src/state.rs index a36b91b..412768f 100644 --- a/crates/shepherd-hud/src/state.rs +++ b/crates/shepherd-hud/src/state.rs @@ -2,8 +2,7 @@ //! //! The HUD subscribes to events from shepherdd and tracks session state. -use chrono::Local; -use shepherd_api::{Event, EventPayload, SessionEndReason, VolumeInfo, VolumeRestrictions, WarningSeverity}; +use shepherd_api::{Event, EventPayload, VolumeInfo, VolumeRestrictions, WarningSeverity}; use shepherd_util::{EntryId, SessionId}; use std::sync::Arc; use tokio::sync::watch; @@ -21,6 +20,7 @@ pub enum SessionState { entry_name: String, started_at: std::time::Instant, time_limit_secs: Option, + #[allow(dead_code)] time_remaining_secs: Option, }, @@ -64,19 +64,6 @@ impl SessionState { } } -/// System metrics for display -#[derive(Debug, Clone, Default)] -pub struct SystemMetrics { - /// Battery percentage (0-100) - pub battery_percent: Option, - /// Whether battery is charging - pub battery_charging: bool, - /// Volume percentage (0-100) - pub volume_percent: Option, - /// Whether volume is muted - pub volume_muted: bool, -} - /// Shared state for the HUD #[derive(Clone)] pub struct SharedState { @@ -84,10 +71,6 @@ pub struct SharedState { session_tx: Arc>, /// Session state receiver session_rx: watch::Receiver, - /// System metrics sender - metrics_tx: Arc>, - /// System metrics receiver - metrics_rx: watch::Receiver, /// Volume info sender (updated via events, not polling) volume_tx: Arc>>, /// Volume info receiver @@ -97,14 +80,11 @@ pub struct SharedState { impl SharedState { pub fn new() -> Self { let (session_tx, session_rx) = watch::channel(SessionState::NoSession); - let (metrics_tx, metrics_rx) = watch::channel(SystemMetrics::default()); let (volume_tx, volume_rx) = watch::channel(None); Self { session_tx: Arc::new(session_tx), session_rx, - metrics_tx: Arc::new(metrics_tx), - metrics_rx, volume_tx: Arc::new(volume_tx), volume_rx, } @@ -116,25 +96,16 @@ impl SharedState { } /// Subscribe to session state changes + #[allow(dead_code)] pub fn subscribe_session(&self) -> watch::Receiver { self.session_rx.clone() } - /// Subscribe to metrics changes - pub fn subscribe_metrics(&self) -> watch::Receiver { - self.metrics_rx.clone() - } - /// Update session state pub fn set_session_state(&self, state: SessionState) { let _ = self.session_tx.send(state); } - /// Update system metrics - pub fn set_metrics(&self, metrics: SystemMetrics) { - let _ = self.metrics_tx.send(metrics); - } - /// Get current volume info (cached from events) pub fn volume_info(&self) -> Option { self.volume_rx.borrow().clone() @@ -165,6 +136,7 @@ impl SharedState { } /// Update time remaining for current session + #[allow(dead_code)] pub fn update_time_remaining(&self, remaining_secs: u64) { self.session_tx.send_modify(|state| { if let SessionState::Active { @@ -246,8 +218,7 @@ impl SharedState { entry_name, .. } = state - { - if sid == session_id { + && sid == session_id { *state = SessionState::Warning { session_id: session_id.clone(), entry_id: entry_id.clone(), @@ -258,7 +229,6 @@ impl SharedState { severity: *severity, }; } - } }); } @@ -275,11 +245,11 @@ impl SharedState { if let Some(session) = &snapshot.current_session { let now = shepherd_util::now(); // For unlimited sessions (deadline=None), time_remaining is None - let time_remaining = session.deadline.and_then(|d| { + let time_remaining = session.deadline.map(|d| { if d > now { - Some((d - now).num_seconds().max(0) as u64) + (d - now).num_seconds().max(0) as u64 } else { - Some(0) + 0 } }); self.set_session_state(SessionState::Active { diff --git a/crates/shepherd-hud/src/volume.rs b/crates/shepherd-hud/src/volume.rs index b2e92df..b54692f 100644 --- a/crates/shepherd-hud/src/volume.rs +++ b/crates/shepherd-hud/src/volume.rs @@ -75,52 +75,6 @@ pub fn toggle_mute() -> anyhow::Result<()> { }) } -/// Increase volume by a step via shepherdd -pub fn volume_up(step: u8) -> anyhow::Result<()> { - let socket_path = get_socket_path(); - - let rt = Runtime::new()?; - - rt.block_on(async { - let mut client = IpcClient::connect(&socket_path).await?; - let response = client.send(Command::VolumeUp { step }).await?; - - match response.result { - shepherd_api::ResponseResult::Ok(ResponsePayload::VolumeSet) => Ok(()), - shepherd_api::ResponseResult::Ok(ResponsePayload::VolumeDenied { reason }) => { - Err(anyhow::anyhow!("Volume denied: {}", reason)) - } - shepherd_api::ResponseResult::Err(e) => { - Err(anyhow::anyhow!("Error: {}", e.message)) - } - _ => Err(anyhow::anyhow!("Unexpected response")), - } - }) -} - -/// Decrease volume by a step via shepherdd -pub fn volume_down(step: u8) -> anyhow::Result<()> { - let socket_path = get_socket_path(); - - let rt = Runtime::new()?; - - rt.block_on(async { - let mut client = IpcClient::connect(&socket_path).await?; - let response = client.send(Command::VolumeDown { step }).await?; - - match response.result { - shepherd_api::ResponseResult::Ok(ResponsePayload::VolumeSet) => Ok(()), - shepherd_api::ResponseResult::Ok(ResponsePayload::VolumeDenied { reason }) => { - Err(anyhow::anyhow!("Volume denied: {}", reason)) - } - shepherd_api::ResponseResult::Err(e) => { - Err(anyhow::anyhow!("Error: {}", e.message)) - } - _ => Err(anyhow::anyhow!("Unexpected response")), - } - }) -} - /// Set volume to a specific percentage via shepherdd pub fn set_volume(percent: u8) -> anyhow::Result<()> { let socket_path = get_socket_path(); diff --git a/crates/shepherd-ipc/src/server.rs b/crates/shepherd-ipc/src/server.rs index 2daf9aa..4292398 100644 --- a/crates/shepherd-ipc/src/server.rs +++ b/crates/shepherd-ipc/src/server.rs @@ -158,7 +158,7 @@ impl IpcServer { let client_id_clone = client_id.clone(); // Spawn reader task - let reader_handle = tokio::spawn(async move { + let _reader_handle = tokio::spawn(async move { let mut reader = BufReader::new(read_half); let mut line = String::new(); @@ -235,8 +235,8 @@ impl IpcServer { clients.get(&client_id_writer).map(|h| h.subscribed).unwrap_or(false) }; - if is_subscribed { - if let Ok(json) = serde_json::to_string(&event) { + if is_subscribed + && let Ok(json) = serde_json::to_string(&event) { let mut msg = json; msg.push('\n'); if let Err(e) = writer.write_all(msg.as_bytes()).await { @@ -244,7 +244,6 @@ impl IpcServer { break; } } - } } } } diff --git a/crates/shepherd-launcher-ui/src/app.rs b/crates/shepherd-launcher-ui/src/app.rs index 21d4f99..a9302ac 100644 --- a/crates/shepherd-launcher-ui/src/app.rs +++ b/crates/shepherd-launcher-ui/src/app.rs @@ -2,15 +2,13 @@ use gtk4::glib; use gtk4::prelude::*; -use std::cell::RefCell; use std::path::PathBuf; -use std::rc::Rc; use std::sync::Arc; use tokio::runtime::Runtime; use tokio::sync::mpsc; use tracing::{debug, error, info}; -use crate::client::{ClientCommand, CommandClient, ServiceClient}; +use crate::client::{CommandClient, ServiceClient}; use crate::grid::LauncherGrid; use crate::state::{LauncherState, SharedState}; @@ -166,7 +164,7 @@ impl LauncherApp { let runtime = Arc::new(Runtime::new().expect("Failed to create tokio runtime")); // Create command channel - let (command_tx, command_rx) = mpsc::unbounded_channel(); + let (_command_tx, command_rx) = mpsc::unbounded_channel(); // Create command client for sending commands let command_client = Arc::new(CommandClient::new(&socket_path)); diff --git a/crates/shepherd-launcher-ui/src/client.rs b/crates/shepherd-launcher-ui/src/client.rs index a1824cf..d05f756 100644 --- a/crates/shepherd-launcher-ui/src/client.rs +++ b/crates/shepherd-launcher-ui/src/client.rs @@ -1,19 +1,20 @@ //! IPC client wrapper for the launcher UI use anyhow::{Context, Result}; -use shepherd_api::{Command, Event, ReasonCode, Response, ResponsePayload, ResponseResult}; +use shepherd_api::{Command, ReasonCode, Response, ResponsePayload, ResponseResult}; use shepherd_ipc::IpcClient; use shepherd_util::EntryId; use std::path::Path; use std::time::Duration; use tokio::sync::mpsc; use tokio::time::sleep; -use tracing::{debug, error, info, warn}; +use tracing::{error, info, warn}; use crate::state::{LauncherState, SharedState}; /// Messages from UI to client task #[derive(Debug)] +#[allow(dead_code)] pub enum ClientCommand { /// Request to launch an entry Launch(EntryId), @@ -98,7 +99,7 @@ impl ServiceClient { info!("Shutdown requested"); return Ok(()); } - ClientCommand::Launch(entry_id) => { + ClientCommand::Launch(_entry_id) => { // We can't send commands after subscribing since client is consumed // Need to reconnect for commands warn!("Launch command received but cannot send after subscribe"); @@ -222,6 +223,7 @@ impl CommandClient { }).await.map_err(Into::into) } + #[allow(dead_code)] pub async fn stop_current(&self) -> Result { let mut client = IpcClient::connect(&self.socket_path).await?; client.send(Command::StopCurrent { @@ -234,6 +236,7 @@ impl CommandClient { client.send(Command::GetState).await.map_err(Into::into) } + #[allow(dead_code)] pub async fn list_entries(&self) -> Result { let mut client = IpcClient::connect(&self.socket_path).await?; client.send(Command::ListEntries { at_time: None }).await.map_err(Into::into) diff --git a/crates/shepherd-launcher-ui/src/grid.rs b/crates/shepherd-launcher-ui/src/grid.rs index d3c5161..1efcedf 100644 --- a/crates/shepherd-launcher-ui/src/grid.rs +++ b/crates/shepherd-launcher-ui/src/grid.rs @@ -13,10 +13,12 @@ use crate::tile::LauncherTile; mod imp { use super::*; + type LaunchCallback = Rc>>>; + pub struct LauncherGrid { pub flow_box: gtk4::FlowBox, pub tiles: RefCell>, - pub on_launch: Rc>>>, + pub on_launch: LaunchCallback, } impl Default for LauncherGrid { @@ -114,11 +116,10 @@ impl LauncherGrid { // Connect click handler let on_launch = imp.on_launch.clone(); tile.connect_clicked(move |tile| { - if let Some(entry_id) = tile.entry_id() { - if let Some(callback) = on_launch.borrow().as_ref() { + if let Some(entry_id) = tile.entry_id() + && let Some(callback) = on_launch.borrow().as_ref() { callback(entry_id); } - } }); imp.flow_box.insert(&tile, -1); diff --git a/crates/shepherd-launcher-ui/src/main.rs b/crates/shepherd-launcher-ui/src/main.rs index 55d4973..5b66603 100644 --- a/crates/shepherd-launcher-ui/src/main.rs +++ b/crates/shepherd-launcher-ui/src/main.rs @@ -11,7 +11,6 @@ mod tile; use anyhow::Result; use clap::Parser; -use gtk4::prelude::*; use std::path::PathBuf; use tracing_subscriber::EnvFilter; diff --git a/crates/shepherd-launcher-ui/src/state.rs b/crates/shepherd-launcher-ui/src/state.rs index b66e679..91f4bcd 100644 --- a/crates/shepherd-launcher-ui/src/state.rs +++ b/crates/shepherd-launcher-ui/src/state.rs @@ -2,37 +2,36 @@ use shepherd_api::{ServiceStateSnapshot, EntryView, Event, EventPayload}; use shepherd_util::SessionId; -use std::sync::Arc; use std::time::Duration; use tokio::sync::watch; /// Current state of the launcher UI -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub enum LauncherState { /// Not connected to shepherdd + #[default] Disconnected, /// Connected, waiting for initial state Connecting, /// Connected, no session running - show grid Idle { entries: Vec }, /// Launch requested, waiting for response - Launching { entry_id: String }, + Launching { + #[allow(dead_code)] + entry_id: String + }, /// Session is running SessionActive { + #[allow(dead_code)] session_id: SessionId, entry_label: String, + #[allow(dead_code)] time_remaining: Option, }, /// Error state Error { message: String }, } -impl Default for LauncherState { - fn default() -> Self { - Self::Disconnected - } -} - /// Shared state container #[derive(Clone)] pub struct SharedState { diff --git a/crates/shepherd-store/src/sqlite.rs b/crates/shepherd-store/src/sqlite.rs index aaa3b37..ca3b097 100644 --- a/crates/shepherd-store/src/sqlite.rs +++ b/crates/shepherd-store/src/sqlite.rs @@ -1,6 +1,6 @@ //! SQLite-based store implementation -use chrono::{DateTime, Local, NaiveDate, TimeZone}; +use chrono::{DateTime, Local, NaiveDate}; use rusqlite::{params, Connection, OptionalExtension}; use shepherd_util::EntryId; use std::path::Path; @@ -8,7 +8,7 @@ use std::sync::Mutex; use std::time::Duration; use tracing::{debug, warn}; -use crate::{AuditEvent, SessionSnapshot, StateSnapshot, Store, StoreError, StoreResult}; +use crate::{AuditEvent, StateSnapshot, Store, StoreResult}; /// SQLite-based store pub struct SqliteStore { diff --git a/crates/shepherdd/src/main.rs b/crates/shepherdd/src/main.rs index ae56059..7f56246 100644 --- a/crates/shepherdd/src/main.rs +++ b/crates/shepherdd/src/main.rs @@ -12,22 +12,21 @@ use anyhow::{Context, Result}; use clap::Parser; use shepherd_api::{ - Command, ServiceStateSnapshot, ErrorCode, ErrorInfo, Event, EventPayload, HealthStatus, + Command, ErrorCode, ErrorInfo, Event, EventPayload, HealthStatus, Response, ResponsePayload, SessionEndReason, StopMode, VolumeInfo, VolumeRestrictions, - API_VERSION, }; -use shepherd_config::{load_config, Policy, VolumePolicy}; +use shepherd_config::{load_config, VolumePolicy}; use shepherd_core::{CoreEngine, CoreEvent, LaunchDecision, StopDecision}; use shepherd_host_api::{HostAdapter, HostEvent, StopMode as HostStopMode, VolumeController}; use shepherd_host_linux::{LinuxHost, LinuxVolumeController}; use shepherd_ipc::{IpcServer, ServerMessage}; use shepherd_store::{AuditEvent, AuditEventType, SqliteStore, Store}; -use shepherd_util::{ClientId, EntryId, MonotonicInstant, RateLimiter}; +use shepherd_util::{ClientId, MonotonicInstant, RateLimiter}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; -use tracing::{debug, error, info, warn, Level}; +use tracing::{debug, error, info, warn}; use tracing_subscriber::EnvFilter; /// shepherdd - Policy enforcement service for child-focused computing @@ -243,8 +242,8 @@ impl Service { .and_then(|s| s.host_handle.clone()) }; - if let Some(handle) = handle { - if let Err(e) = host + if let Some(handle) = handle + && let Err(e) = host .stop( &handle, HostStopMode::Graceful { @@ -256,7 +255,6 @@ impl Service { warn!(error = %e, "Failed to stop session gracefully, forcing"); let _ = host.stop(&handle, HostStopMode::Force).await; } - } ipc.broadcast_event(Event::new(EventPayload::SessionExpiring { session_id: session_id.clone(), @@ -336,13 +334,12 @@ impl Service { info!(has_event = core_event.is_some(), "notify_session_exited result"); - if let Some(event) = core_event { - if let CoreEvent::SessionEnded { - session_id, - entry_id, - reason, - duration, - } = event + if let Some(CoreEvent::SessionEnded { + session_id, + entry_id, + reason, + duration, + }) = core_event { info!( session_id = %session_id, @@ -366,7 +363,6 @@ impl Service { info!("Broadcasting StateChanged"); ipc.broadcast_event(Event::new(EventPayload::StateChanged(state))); } - } } HostEvent::WindowReady { handle } => { @@ -443,6 +439,7 @@ impl Service { } } + #[allow(clippy::too_many_arguments)] async fn handle_command( engine: &Arc>, host: &Arc, @@ -530,13 +527,12 @@ impl Service { Err(e) => { // Notify session ended with error and broadcast to subscribers let mut eng = engine.lock().await; - if let Some(event) = eng.notify_session_exited(Some(-1), now_mono, now) { - if let CoreEvent::SessionEnded { - session_id, - entry_id, - reason, - duration, - } = event + if let Some(CoreEvent::SessionEnded { + session_id, + entry_id, + reason, + duration, + }) = eng.notify_session_exited(Some(-1), now_mono, now) { ipc.broadcast_event(Event::new(EventPayload::SessionEnded { session_id, @@ -549,7 +545,6 @@ impl Service { let state = eng.get_state(); ipc.broadcast_event(Event::new(EventPayload::StateChanged(state))); } - } Response::error( request_id, @@ -629,14 +624,13 @@ impl Service { Command::ReloadConfig => { // Check permission - if let Some(info) = ipc.get_client_info(client_id).await { - if !info.role.can_reload_config() { + if let Some(info) = ipc.get_client_info(client_id).await + && !info.role.can_reload_config() { return Response::error( request_id, ErrorInfo::new(ErrorCode::PermissionDenied, "Admin role required"), ); } - } // TODO: Reload from original config path Response::error( @@ -672,14 +666,13 @@ impl Service { Command::ExtendCurrent { by } => { // Check permission - if let Some(info) = ipc.get_client_info(client_id).await { - if !info.role.can_extend() { + if let Some(info) = ipc.get_client_info(client_id).await + && !info.role.can_extend() { return Response::error( request_id, ErrorInfo::new(ErrorCode::PermissionDenied, "Admin role required"), ); } - } let mut eng = engine.lock().await; match eng.extend_current(by, now_mono, now) { @@ -694,7 +687,7 @@ impl Service { } Command::GetVolume => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; + let restrictions = Self::get_current_volume_restrictions(engine).await; match volume.get_status().await { Ok(status) => { @@ -722,7 +715,7 @@ impl Service { } Command::SetVolume { percent } => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; + let restrictions = Self::get_current_volume_restrictions(engine).await; if !restrictions.allow_change { return Response::success( @@ -755,80 +748,8 @@ impl Service { } } - Command::VolumeUp { step } => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; - - if !restrictions.allow_change { - return Response::success( - request_id, - ResponsePayload::VolumeDenied { - reason: "Volume changes are not allowed".into(), - }, - ); - } - - // Get current volume and check if we'd exceed max - let current = volume.get_status().await.map(|s| s.percent).unwrap_or(0); - let target = current.saturating_add(step); - let clamped = restrictions.clamp_volume(target); - - match volume.set_volume(clamped).await { - Ok(()) => { - if let Ok(status) = volume.get_status().await { - ipc.broadcast_event(Event::new(EventPayload::VolumeChanged { - percent: status.percent, - muted: status.muted, - })); - } - Response::success(request_id, ResponsePayload::VolumeSet) - } - Err(e) => Response::success( - request_id, - ResponsePayload::VolumeDenied { - reason: e.to_string(), - }, - ), - } - } - - Command::VolumeDown { step } => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; - - if !restrictions.allow_change { - return Response::success( - request_id, - ResponsePayload::VolumeDenied { - reason: "Volume changes are not allowed".into(), - }, - ); - } - - // Get current volume and check if we'd go below min - let current = volume.get_status().await.map(|s| s.percent).unwrap_or(0); - let target = current.saturating_sub(step); - let clamped = restrictions.clamp_volume(target); - - match volume.set_volume(clamped).await { - Ok(()) => { - if let Ok(status) = volume.get_status().await { - ipc.broadcast_event(Event::new(EventPayload::VolumeChanged { - percent: status.percent, - muted: status.muted, - })); - } - Response::success(request_id, ResponsePayload::VolumeSet) - } - Err(e) => Response::success( - request_id, - ResponsePayload::VolumeDenied { - reason: e.to_string(), - }, - ), - } - } - Command::ToggleMute => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; + let restrictions = Self::get_current_volume_restrictions(engine).await; if !restrictions.allow_mute { return Response::success( @@ -859,7 +780,7 @@ impl Service { } Command::SetMute { muted } => { - let restrictions = Self::get_current_volume_restrictions(&engine).await; + let restrictions = Self::get_current_volume_restrictions(engine).await; if !restrictions.allow_mute { return Response::success( @@ -900,13 +821,11 @@ impl Service { let eng = engine.lock().await; // Check if there's an active session with volume restrictions - if let Some(session) = eng.current_session() { - if let Some(entry) = eng.policy().get_entry(&session.plan.entry_id) { - if let Some(ref vol_policy) = entry.volume { - return Self::convert_volume_policy(vol_policy); - } + if let Some(session) = eng.current_session() + && let Some(entry) = eng.policy().get_entry(&session.plan.entry_id) + && let Some(ref vol_policy) = entry.volume { + return Self::convert_volume_policy(vol_policy); } - } // Fall back to global policy Self::convert_volume_policy(&eng.policy().volume)