Introduce anyhow errors

This commit is contained in:
Demmie 2023-04-25 01:25:14 -04:00
parent 11cfb7c54b
commit 3c51ad8685
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
16 changed files with 97 additions and 92 deletions

7
Cargo.lock generated
View File

@ -60,6 +60,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "async-broadcast"
version = "0.5.1"
@ -216,6 +222,7 @@ dependencies = [
name = "awatcher"
version = "0.1.0"
dependencies = [
"anyhow",
"aw-client-rust",
"chrono",
"clap",

View File

@ -21,3 +21,4 @@ dirs = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_default = "0.1.0"
regex = "1.8.1"
anyhow = "1.0.70"

View File

@ -2,7 +2,6 @@ mod defaults;
mod file_config;
mod filters;
use crate::BoxedError;
use clap::{arg, value_parser, ArgAction, Command};
use file_config::FileConfig;
use std::{path::PathBuf, time::Duration};
@ -22,7 +21,7 @@ pub struct Config {
}
impl Config {
pub fn from_cli() -> Result<Self, BoxedError> {
pub fn from_cli() -> anyhow::Result<Self> {
let matches = Command::new("Activity Watcher")
.version("0.1.0")
.about("A set of ActivityWatch desktop watchers")

View File

@ -1,3 +1,4 @@
use anyhow::{anyhow, Context};
use clap::{parser::ValueSource, ArgMatches};
use serde::Deserialize;
use serde_default::DefaultFromSerde;
@ -7,7 +8,7 @@ use std::{
time::Duration,
};
use crate::{config::defaults, BoxedError};
use crate::config::defaults;
use super::filters::Filter;
@ -56,8 +57,9 @@ pub struct FileConfig {
}
impl FileConfig {
pub fn new(matches: &ArgMatches) -> Result<Self, BoxedError> {
let mut config_path: PathBuf = dirs::config_dir().ok_or("Config directory is unknown")?;
pub fn new(matches: &ArgMatches) -> anyhow::Result<Self> {
let mut config_path: PathBuf =
dirs::config_dir().ok_or(anyhow!("Config directory is unknown"))?;
config_path.push("awatcher");
config_path.push("config.toml");
if matches.contains_id("config") {
@ -73,8 +75,9 @@ impl FileConfig {
let mut config = if config_path.exists() {
debug!("Reading config at {}", config_path.display());
let config_content = std::fs::read_to_string(config_path)
.map_err(|e| format!("Impossible to read config file: {e}"))?;
let config_content = std::fs::read_to_string(&config_path).with_context(|| {
format!("Impossible to read config file {}", config_path.display())
})?;
toml::from_str(&config_content)?
} else {

View File

@ -11,12 +11,10 @@ use config::Config;
use fern::colors::{Color, ColoredLevelConfig};
use report_client::ReportClient;
use std::env;
use std::{error::Error, str::FromStr, sync::Arc, thread};
use std::{sync::Arc, thread};
use crate::watchers::ConstructorFilter;
type BoxedError = Box<dyn Error>;
fn setup_logger() -> Result<(), fern::InitError> {
let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string());
@ -37,14 +35,14 @@ fn setup_logger() -> Result<(), fern::InitError> {
.level(log::LevelFilter::Warn)
.level_for(
"awatcher",
FromStr::from_str(&log_setting).unwrap_or(log::LevelFilter::Info),
log_setting.parse().unwrap_or(log::LevelFilter::Info),
)
.chain(std::io::stdout())
.apply()?;
Ok(())
}
fn main() -> Result<(), BoxedError> {
fn main() -> anyhow::Result<()> {
setup_logger()?;
let client = ReportClient::new(Config::from_cli()?)?;

View File

@ -1,7 +1,5 @@
use std::error::Error;
use super::BoxedError;
use super::Config;
use anyhow::Context;
use aw_client_rust::{AwClient, Event as AwEvent};
use chrono::{DateTime, Duration, Utc};
use serde_json::{Map, Value};
@ -12,7 +10,7 @@ pub struct ReportClient {
}
impl ReportClient {
pub fn new(config: Config) -> Result<Self, BoxedError> {
pub fn new(config: Config) -> anyhow::Result<Self> {
let client = AwClient::new(&config.host, &config.port.to_string(), "awatcher");
if !config.mock_server {
@ -28,7 +26,7 @@ impl ReportClient {
is_idle: bool,
timestamp: DateTime<Utc>,
duration: Duration,
) -> Result<(), Box<dyn Error>> {
) -> anyhow::Result<()> {
let mut data = Map::new();
data.insert(
"status".to_string(),
@ -49,10 +47,10 @@ impl ReportClient {
let pulsetime = (self.config.idle_timeout + self.config.poll_time_idle).as_secs_f64();
self.client
.heartbeat(&self.config.idle_bucket_name, &event, pulsetime)
.map_err(|_| "Failed to send heartbeat".into())
.with_context(|| "Failed to send heartbeat")
}
pub fn send_active_window(&self, app_id: &str, title: &str) -> Result<(), BoxedError> {
pub fn send_active_window(&self, app_id: &str, title: &str) -> anyhow::Result<()> {
let mut data = Map::new();
let mut data_insert = |k: &str, v: String| data.insert(k.to_string(), Value::String(v));
@ -89,16 +87,16 @@ impl ReportClient {
&event,
interval_margin,
)
.map_err(|_| "Failed to send heartbeat for active window".into())
.with_context(|| "Failed to send heartbeat for active window")
}
fn create_bucket(
client: &AwClient,
bucket_name: &str,
bucket_type: &str,
) -> Result<(), Box<dyn Error>> {
) -> anyhow::Result<()> {
client
.create_bucket_simple(bucket_name, bucket_type)
.map_err(|e| format!("Failed to create bucket {bucket_name}: {e}").into())
.with_context(|| format!("Failed to create bucket {bucket_name}"))
}
}

View File

@ -9,11 +9,11 @@ mod x11_connection;
mod x11_screensaver_idle;
mod x11_window;
use crate::{report_client::ReportClient, BoxedError};
use crate::report_client::ReportClient;
use std::sync::Arc;
pub trait Watcher: Send {
fn new() -> Result<Self, BoxedError>
fn new() -> anyhow::Result<Self>
where
Self: Sized;
fn watch(&mut self, client: &Arc<ReportClient>);
@ -21,7 +21,7 @@ pub trait Watcher: Send {
type BoxedWatcher = Box<dyn Watcher>;
type WatcherConstructor = (&'static str, fn() -> Result<BoxedWatcher, BoxedError>);
type WatcherConstructor = (&'static str, fn() -> anyhow::Result<BoxedWatcher>);
type WatcherConstructors = [WatcherConstructor];
pub trait ConstructorFilter {

View File

@ -1,5 +1,6 @@
use super::{idle, Watcher};
use crate::{report_client::ReportClient, BoxedError};
use crate::report_client::ReportClient;
use anyhow::Context;
use std::{sync::Arc, thread};
use zbus::blocking::Connection;
@ -8,7 +9,7 @@ pub struct IdleWatcher {
}
impl idle::SinceLastInput for IdleWatcher {
fn seconds_since_input(&self) -> Result<u32, BoxedError> {
fn seconds_since_input(&self) -> anyhow::Result<u32> {
let ms = self
.dbus_connection
.call_method(
@ -19,12 +20,12 @@ impl idle::SinceLastInput for IdleWatcher {
&(),
)?
.body::<u64>()?;
u32::try_from(ms / 1000).map_err(|_| format!("Number {ms} is invalid").into())
u32::try_from(ms / 1000).with_context(|| format!("Number {ms} is invalid"))
}
}
impl Watcher for IdleWatcher {
fn new() -> Result<Self, crate::BoxedError> {
fn new() -> anyhow::Result<Self> {
let watcher = Self {
dbus_connection: Connection::session()?,
};

View File

@ -1,16 +1,16 @@
use crate::{report_client::ReportClient, BoxedError};
use crate::report_client::ReportClient;
use chrono::{Duration, Utc};
use std::sync::Arc;
pub trait SinceLastInput {
fn seconds_since_input(&self) -> Result<u32, BoxedError>;
fn seconds_since_input(&self) -> anyhow::Result<u32>;
}
pub fn ping_since_last_input(
watcher: &impl SinceLastInput,
is_idle: bool,
client: &Arc<ReportClient>,
) -> Result<bool, BoxedError> {
) -> anyhow::Result<bool> {
// The logic is rewritten from the original Python code:
// https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73
let duration_1ms: Duration = Duration::milliseconds(1);

View File

@ -3,8 +3,9 @@
* For the moment of writing, KWin doesn't implement the appropriate protocols to get a top level window.
* Inspired by https://github.com/k0kubun/xremap/
*/
use super::{BoxedError, Watcher};
use super::Watcher;
use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::env::temp_dir;
use std::path::Path;
use std::sync::{mpsc::channel, Arc, Mutex};
@ -28,7 +29,7 @@ impl KWinScript {
}
}
fn load(&mut self) -> Result<(), BoxedError> {
fn load(&mut self) -> anyhow::Result<()> {
let path = temp_dir().join("kwin_window.js");
std::fs::write(&path, KWIN_SCRIPT).unwrap();
@ -41,7 +42,7 @@ impl KWinScript {
result
}
fn is_loaded(&self) -> Result<bool, BoxedError> {
fn is_loaded(&self) -> anyhow::Result<bool> {
self.dbus_connection
.call_method(
Some("org.kde.KWin"),
@ -54,10 +55,10 @@ impl KWinScript {
.map_err(std::convert::Into::into)
}
fn get_registered_number(&self, path: &Path) -> Result<i32, BoxedError> {
fn get_registered_number(&self, path: &Path) -> anyhow::Result<i32> {
let temp_path = path
.to_str()
.ok_or::<BoxedError>("Temporary file path is not valid".into())?;
.ok_or(anyhow!("Temporary file path is not valid"))?;
self.dbus_connection
.call_method(
@ -72,7 +73,7 @@ impl KWinScript {
.map_err(std::convert::Into::into)
}
fn unload(&self) -> Result<bool, BoxedError> {
fn unload(&self) -> anyhow::Result<bool> {
self.dbus_connection
.call_method(
Some("org.kde.KWin"),
@ -85,7 +86,7 @@ impl KWinScript {
.map_err(std::convert::Into::into)
}
fn start(&self, script_number: i32) -> Result<(), BoxedError> {
fn start(&self, script_number: i32) -> anyhow::Result<()> {
debug!("Starting KWin script {script_number}");
self.dbus_connection
.call_method(
@ -95,8 +96,8 @@ impl KWinScript {
"run",
&(),
)
.map_err(|e| format!("Error on starting the script {e}").into())
.map(|_| ())
.with_context(|| "Error on starting the script")?;
Ok(())
}
}
@ -114,12 +115,12 @@ impl Drop for KWinScript {
fn send_active_window(
client: &ReportClient,
active_window: &Arc<Mutex<ActiveWindow>>,
) -> Result<(), BoxedError> {
let active_window = active_window.lock().map_err(|e| format!("{e}"))?;
) -> anyhow::Result<()> {
let active_window = active_window.lock().expect("Lock cannot be acquired");
client
.send_active_window(&active_window.resource_class, &active_window.caption)
.map_err(|_| "Failed to send heartbeat for active window".into())
.with_context(|| "Failed to send heartbeat for active window")
}
struct ActiveWindow {
@ -153,7 +154,7 @@ pub struct WindowWatcher {
}
impl Watcher for WindowWatcher {
fn new() -> Result<Self, BoxedError> {
fn new() -> anyhow::Result<Self> {
let kwin_script = KWinScript::new(Connection::session()?);
if kwin_script.is_loaded()? {
warn!("KWin script is already loaded, unloading");

View File

@ -1,5 +1,5 @@
use super::wl_bindings;
use crate::BoxedError;
use anyhow::Context;
use wayland_client::{
globals::{registry_queue_init, GlobalList, GlobalListContents},
protocol::{wl_registry, wl_seat::WlSeat},
@ -38,9 +38,9 @@ where
+ Dispatch<wl_registry::WlRegistry, ()>
+ 'static,
{
pub fn connect() -> Result<Self, BoxedError> {
let connection =
Connection::connect_to_env().map_err(|_| "Unable to connect to Wayland compositor")?;
pub fn connect() -> anyhow::Result<Self> {
let connection = Connection::connect_to_env()
.with_context(|| "Unable to connect to Wayland compositor")?;
let display = connection.display();
let (globals, event_queue) = registry_queue_init::<T>(&connection)?;
@ -55,7 +55,7 @@ where
})
}
pub fn get_foreign_toplevel_manager(&self) -> Result<ZwlrForeignToplevelManagerV1, BoxedError>
pub fn get_foreign_toplevel_manager(&self) -> anyhow::Result<ZwlrForeignToplevelManagerV1>
where
T: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
{
@ -68,7 +68,7 @@ where
.map_err(std::convert::Into::into)
}
pub fn get_kwin_idle(&self) -> Result<OrgKdeKwinIdle, BoxedError>
pub fn get_kwin_idle(&self) -> anyhow::Result<OrgKdeKwinIdle>
where
T: Dispatch<OrgKdeKwinIdle, ()>,
{
@ -81,7 +81,7 @@ where
.map_err(std::convert::Into::into)
}
pub fn get_kwin_idle_timeout(&self, timeout: u32) -> Result<OrgKdeKwinIdleTimeout, BoxedError>
pub fn get_kwin_idle_timeout(&self, timeout: u32) -> anyhow::Result<OrgKdeKwinIdleTimeout>
where
T: Dispatch<OrgKdeKwinIdle, ()>
+ Dispatch<OrgKdeKwinIdleTimeout, ()>

View File

@ -5,9 +5,9 @@ use super::wl_bindings::wlr_foreign_toplevel::zwlr_foreign_toplevel_manager_v1::
Event as ManagerEvent, ZwlrForeignToplevelManagerV1, EVT_TOPLEVEL_OPCODE,
};
use super::wl_connection::WlEventConnection;
use super::BoxedError;
use super::{wl_connection::subscribe_state, Watcher};
use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::collections::HashMap;
use std::{sync::Arc, thread};
use wayland_client::{
@ -114,18 +114,18 @@ impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for ToplevelState {
}
impl ToplevelState {
fn send_active_window(&self) -> Result<(), BoxedError> {
fn send_active_window(&self) -> anyhow::Result<()> {
let active_window_id = self
.current_window_id
.as_ref()
.ok_or("Current window is unknown")?;
let active_window = self.windows.get(active_window_id).ok_or(format!(
.ok_or(anyhow!("Current window is unknown"))?;
let active_window = self.windows.get(active_window_id).ok_or(anyhow!(
"Current window is not found by ID {active_window_id}"
))?;
self.client
.send_active_window(&active_window.app_id, &active_window.title)
.map_err(|_| "Failed to send heartbeat for active window".into())
.with_context(|| "Failed to send heartbeat for active window")
}
}
@ -134,7 +134,7 @@ pub struct WindowWatcher {
}
impl Watcher for WindowWatcher {
fn new() -> Result<Self, BoxedError> {
fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?;
connection.get_foreign_toplevel_manager()?;

View File

@ -1,6 +1,5 @@
use super::wl_bindings;
use super::wl_connection::{subscribe_state, WlEventConnection};
use super::BoxedError;
use super::Watcher;
use crate::report_client::ReportClient;
use chrono::{DateTime, Duration, Utc};
@ -54,7 +53,7 @@ impl IdleState {
debug!("Resumed");
}
fn send_ping(&mut self) -> Result<(), BoxedError> {
fn send_ping(&mut self) -> anyhow::Result<()> {
let now = Utc::now();
if !self.is_idle {
self.last_input_time = now;
@ -122,7 +121,7 @@ pub struct IdleWatcher {
}
impl Watcher for IdleWatcher {
fn new() -> Result<Self, BoxedError> {
fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<IdleState> = WlEventConnection::connect()?;
connection.get_kwin_idle()?;

View File

@ -1,13 +1,11 @@
use std::{env, str};
use anyhow::{anyhow, bail, Context};
use log::warn;
use std::{env, str};
use x11rb::connection::Connection;
use x11rb::protocol::screensaver::ConnectionExt as ScreensaverConnectionExt;
use x11rb::protocol::xproto::{Atom, AtomEnum, ConnectionExt, GetPropertyReply, Window};
use x11rb::rust_connection::RustConnection;
use crate::BoxedError;
pub struct WindowData {
pub title: String,
pub app_id: String,
@ -19,7 +17,7 @@ pub struct X11Connection {
}
impl X11Connection {
pub fn new() -> Result<Self, BoxedError> {
pub fn new() -> anyhow::Result<Self> {
if env::var("DISPLAY").is_err() {
warn!("DISPLAY is not set, setting to the default value \":0\"");
env::set_var("DISPLAY", ":0");
@ -34,7 +32,7 @@ impl X11Connection {
})
}
pub fn seconds_since_last_input(&self) -> Result<u32, BoxedError> {
pub fn seconds_since_last_input(&self) -> anyhow::Result<u32> {
let reply = self
.connection
.screensaver_query_info(self.screen_root)?
@ -43,7 +41,7 @@ impl X11Connection {
Ok(reply.ms_since_user_input / 1000)
}
pub fn active_window_data(&self) -> Result<WindowData, BoxedError> {
pub fn active_window_data(&self) -> anyhow::Result<WindowData> {
let focus: Window = self.find_active_window()?;
let name = self.get_property(
@ -61,7 +59,7 @@ impl X11Connection {
u32::MAX,
)?;
let title = str::from_utf8(&name.value).map_err(|e| format!("Invalid title UTF: {e}"))?;
let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?;
Ok(WindowData {
title: title.to_string(),
@ -76,25 +74,25 @@ impl X11Connection {
property_name: &str,
property_type: Atom,
long_length: u32,
) -> Result<GetPropertyReply, BoxedError> {
) -> anyhow::Result<GetPropertyReply> {
self.connection
.get_property(false, window, property, property_type, 0, long_length)
.map_err(|e| format!("GetPropertyRequest[{property_name}] failed: {e}"))?
.with_context(|| format!("GetPropertyRequest[{property_name}] failed"))?
.reply()
.map_err(|e| format!("GetPropertyReply[{property_name}] failed: {e}").into())
.with_context(|| format!("GetPropertyReply[{property_name}] failed"))
}
fn intern_atom(&self, name: &str) -> Result<Atom, BoxedError> {
fn intern_atom(&self, name: &str) -> anyhow::Result<Atom> {
Ok(self
.connection
.intern_atom(false, name.as_bytes())
.map_err(|_| format!("InternAtomRequest[{name}] failed"))?
.with_context(|| format!("InternAtomRequest[{name}] failed"))?
.reply()
.map_err(|_| format!("InternAtomReply[{name}] failed"))?
.with_context(|| format!("InternAtomReply[{name}] failed"))?
.atom)
}
fn find_active_window(&self) -> Result<Window, BoxedError> {
fn find_active_window(&self) -> anyhow::Result<Window> {
let window: Atom = AtomEnum::WINDOW.into();
let net_active_window = self.intern_atom("_NET_ACTIVE_WINDOW")?;
let active_window = self.get_property(
@ -108,25 +106,25 @@ impl X11Connection {
if active_window.format == 32 && active_window.length == 1 {
active_window
.value32()
.ok_or("Invalid message. Expected value with format = 32")?
.ok_or(anyhow!("Invalid message. Expected value with format = 32"))?
.next()
.ok_or("Active window is not found".into())
.ok_or(anyhow!("Active window is not found"))
} else {
// Query the input focus
Ok(self
.connection
.get_input_focus()
.map_err(|e| format!("Failed to get input focus: {e}"))?
.with_context(|| "Failed to get input focus")?
.reply()
.map_err(|e| format!("Failed to read input focus from reply: {e}"))?
.with_context(|| "Failed to read input focus from reply")?
.focus)
}
}
}
fn parse_wm_class(property: &GetPropertyReply) -> Result<&str, BoxedError> {
fn parse_wm_class(property: &GetPropertyReply) -> anyhow::Result<&str> {
if property.format != 8 {
return Err("Malformed property: wrong format".into());
bail!("Malformed property: wrong format");
}
let value = &property.value;
// The property should contain two null-terminated strings. Find them.
@ -140,6 +138,6 @@ fn parse_wm_class(property: &GetPropertyReply) -> Result<&str, BoxedError> {
}
Ok(std::str::from_utf8(class)?)
} else {
Err("Missing null byte".into())
bail!("Missing null byte")
}
}

View File

@ -1,20 +1,19 @@
use std::{sync::Arc, thread};
use super::{idle, x11_connection::X11Connection, BoxedError, Watcher};
use super::{idle, x11_connection::X11Connection, Watcher};
use crate::report_client::ReportClient;
use std::{sync::Arc, thread};
pub struct IdleWatcher {
connection: X11Connection,
}
impl idle::SinceLastInput for IdleWatcher {
fn seconds_since_input(&self) -> Result<u32, BoxedError> {
fn seconds_since_input(&self) -> anyhow::Result<u32> {
self.connection.seconds_since_last_input()
}
}
impl Watcher for IdleWatcher {
fn new() -> Result<Self, BoxedError> {
fn new() -> anyhow::Result<Self> {
let connection = X11Connection::new()?;
// Check if screensaver extension is supported

View File

@ -1,5 +1,6 @@
use super::{x11_connection::X11Connection, BoxedError, Watcher};
use super::{x11_connection::X11Connection, Watcher};
use crate::report_client::ReportClient;
use anyhow::Context;
use std::thread;
pub struct WindowWatcher {
@ -9,7 +10,7 @@ pub struct WindowWatcher {
}
impl WindowWatcher {
fn send_active_window(&mut self, client: &ReportClient) -> Result<(), BoxedError> {
fn send_active_window(&mut self, client: &ReportClient) -> anyhow::Result<()> {
let data = self.connection.active_window_data()?;
if data.app_id != self.last_app_id || data.title != self.last_title {
@ -23,12 +24,12 @@ impl WindowWatcher {
client
.send_active_window(&self.last_app_id, &self.last_title)
.map_err(|_| "Failed to send heartbeat for active window".into())
.with_context(|| "Failed to send heartbeat for active window")
}
}
impl Watcher for WindowWatcher {
fn new() -> Result<Self, crate::BoxedError> {
fn new() -> anyhow::Result<Self> {
let connection = X11Connection::new()?;
connection.active_window_data()?;