From 16942ea16376e15893167e6f376541fa532c6c9c Mon Sep 17 00:00:00 2001 From: Demmie <2e3s19@gmail.com> Date: Thu, 10 Oct 2024 23:07:35 -0400 Subject: [PATCH] Allow not reporting data by filter --- README.md | 3 +- src/bundle/modules.rs | 5 ++- watchers/src/config.rs | 12 +++--- watchers/src/config/file_config.rs | 43 ++++++++++++++++----- watchers/src/config/filters.rs | 41 ++++++++++++++------ watchers/src/report_client.rs | 61 ++++++++++++++++++++---------- 6 files changed, 115 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 8ad34e5..72a04dc 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,8 @@ Copy the section as many times as needed for every given filter. - `replace-title` replaces the window title with the provided value. The first matching filter stops the replacement. -There should be at least 1 match field, and at least 1 replace field for a valid filter. +There should be at least 1 match field for a filter to be valid. +If the replacement is not specified, the data is not reported when matched. Matches are case sensitive regular expressions between implicit ^ and $: - `.` matches 1 any character - `.*` matches any number of any characters diff --git a/src/bundle/modules.rs b/src/bundle/modules.rs index 90fa666..a647290 100644 --- a/src/bundle/modules.rs +++ b/src/bundle/modules.rs @@ -289,9 +289,12 @@ mod tests { assert_eq!(manager.path_watchers[0].name(), "aw-test"); assert!(manager.path_watchers[0].handle.is_none()); // no starting in config - assert!(manager.start_watcher(&temp_dir.path().join("aw-test"))); + let watcher_path = &temp_dir.path().join("aw-test"); + assert!(manager.start_watcher(watcher_path)); assert!(manager.path_watchers[0].handle.is_some()); assert_autostart_content(&manager, &["aw-test"]); + + manager.stop_watcher(watcher_path); } fn assert_autostart_content(manager: &Manager, watchers: &[&str]) { diff --git a/watchers/src/config.rs b/watchers/src/config.rs index 715d078..29055d8 100644 --- a/watchers/src/config.rs +++ b/watchers/src/config.rs @@ -2,9 +2,10 @@ pub mod defaults; mod file_config; mod filters; -use self::filters::{Filter, Replacement}; +use self::filters::Filter; use chrono::Duration; pub use file_config::FileConfig; +pub use filters::FilterResult; pub struct Config { pub port: u16, @@ -17,13 +18,14 @@ pub struct Config { } impl Config { - pub fn window_data_replacement(&self, app_id: &str, title: &str) -> Replacement { + pub fn match_window_data(&self, app_id: &str, title: &str) -> FilterResult { for filter in &self.filters { - if let Some(replacement) = filter.replacement(app_id, title) { - return replacement; + let result = filter.apply(app_id, title); + if matches!(result, FilterResult::Match | FilterResult::Replace(_)) { + return result; } } - Replacement::default() + FilterResult::Skip } } diff --git a/watchers/src/config/file_config.rs b/watchers/src/config/file_config.rs index 464f9d2..9a9f0ef 100644 --- a/watchers/src/config/file_config.rs +++ b/watchers/src/config/file_config.rs @@ -147,6 +147,8 @@ impl FileConfig { #[cfg(test)] mod tests { + use crate::config::FilterResult; + use super::*; use rstest::rstest; use std::io::Write; @@ -192,16 +194,37 @@ replace-title = "Title" assert_eq!(12, config.client.poll_time_window_seconds); assert_eq!(2, config.client.filters.len()); - let replacement1 = config.client.filters[0] - .replacement("firefox", "any") - .unwrap(); - assert_eq!(None, replacement1.replace_app_id); - assert_eq!(Some("Unknown".to_string()), replacement1.replace_title); - let replacement2 = config.client.filters[1] - .replacement("code", "title") - .unwrap(); - assert_eq!(Some("VSCode".to_string()), replacement2.replace_app_id); - assert_eq!(Some("Title".to_string()), replacement2.replace_title); + + let replacement1 = config.client.filters[0].apply("firefox", "any"); + assert!(matches!(replacement1, FilterResult::Replace(ref r) if + r.replace_app_id.is_none() && + r.replace_title == Some("Unknown".to_string()) + )); + + let replacement2 = config.client.filters[1].apply("code", "title"); + assert!(matches!(replacement2, FilterResult::Replace(ref r) if + r.replace_app_id == Some("VSCode".to_string()) && + r.replace_title == Some("Title".to_string()) + )); + } + + #[rstest] + fn match_filter() { + let mut file = NamedTempFile::new().unwrap(); + write!( + file, + r#" +[[awatcher.filters]] +match-app-id = "firefox" + "# + ) + .unwrap(); + + let config = FileConfig::new(Some(file.path().to_path_buf())).unwrap(); + + assert_eq!(1, config.client.filters.len()); + let replacement1 = config.client.filters[0].apply("firefox", "any"); + assert!(matches!(replacement1, FilterResult::Match)); } #[rstest] diff --git a/watchers/src/config/filters.rs b/watchers/src/config/filters.rs index aa38e28..6274a24 100644 --- a/watchers/src/config/filters.rs +++ b/watchers/src/config/filters.rs @@ -31,6 +31,13 @@ where } } +#[derive(Debug, PartialEq)] +pub enum FilterResult { + Replace(Replacement), + Match, + Skip, +} + #[derive(Default, Debug, PartialEq)] pub struct Replacement { pub replace_app_id: Option, @@ -39,10 +46,7 @@ pub struct Replacement { impl Filter { fn is_valid(&self) -> bool { - let is_match_set = self.match_app_id.is_some() || self.match_title.is_some(); - let is_replacement_set = self.replace_app_id.is_some() || self.replace_title.is_some(); - - is_match_set && is_replacement_set + self.match_app_id.is_some() || self.match_title.is_some() } fn is_match(&self, app_id: &str, title: &str) -> bool { @@ -70,9 +74,12 @@ impl Filter { replacement.to_owned() } - pub fn replacement(&self, app_id: &str, title: &str) -> Option { + pub fn apply(&self, app_id: &str, title: &str) -> FilterResult { if !self.is_valid() || !self.is_match(app_id, title) { - return None; + return FilterResult::Skip; + } + if self.replace_app_id.is_none() && self.replace_title.is_none() { + return FilterResult::Match; } let mut replacement = Replacement::default(); @@ -83,7 +90,7 @@ impl Filter { if let Some(new_title) = &self.replace_title { replacement.replace_title = Some(Self::replace(&self.match_title, title, new_title)); } - Some(replacement) + FilterResult::Replace(replacement) } } @@ -141,6 +148,12 @@ mod tests { ("org.kde.dolphin", "/home/user"), None )] + #[case::match_only( + (Some("org\\.kde\\.(.*)"), None), + (None, None), + ("org.kde.dolphin", "/home/user"), + Some((None, None)) + )] fn replacement( #[case] matches: (Option<&str>, Option<&str>), #[case] replaces: (Option<&str>, Option<&str>), @@ -159,11 +172,15 @@ mod tests { replace_title: replace_title.map(option_string), }; - let replacement = filter.replacement(app_id, title); - let expect_replacement = expect_replacement.map(|r| Replacement { - replace_app_id: r.0.map(option_string), - replace_title: r.1.map(option_string), - }); + let replacement = filter.apply(app_id, title); + let expect_replacement = match expect_replacement { + None => FilterResult::Skip, + Some((None, None)) => FilterResult::Match, + Some((replace_app_id, replace_title)) => FilterResult::Replace(Replacement { + replace_app_id: replace_app_id.map(Into::into), + replace_title: replace_title.map(Into::into), + }), + }; assert_eq!(expect_replacement, replacement); } } diff --git a/watchers/src/report_client.rs b/watchers/src/report_client.rs index 153500d..fb2fcf7 100644 --- a/watchers/src/report_client.rs +++ b/watchers/src/report_client.rs @@ -1,6 +1,5 @@ +use super::config::{Config, FilterResult}; use crate::watchers::idle::Status; - -use super::config::Config; use anyhow::Context; use aw_client_rust::{AwClient, Event as AwEvent}; use chrono::{DateTime, TimeDelta, Utc}; @@ -96,26 +95,19 @@ impl ReportClient { pub async fn send_active_window(&self, app_id: &str, title: &str) -> anyhow::Result<()> { let mut data = Map::new(); - let replacement = self.config.window_data_replacement(app_id, title); - let inserted_app_id = if let Some(new_app_id) = replacement.replace_app_id { - trace!("Replacing app_id by {new_app_id}"); - new_app_id - } else { - app_id.to_string() - }; - let inserted_title = if let Some(new_title) = replacement.replace_title { - trace!("Replacing title of {inserted_app_id} by {new_title}"); - new_title + if let Some((inserted_app_id, inserted_title)) = self.get_filtered_data(app_id, title) { + trace!( + "Reporting app_id: {}, title: {}", + inserted_app_id, + inserted_title + ); + + data.insert("app".to_string(), Value::String(inserted_app_id)); + data.insert("title".to_string(), Value::String(inserted_title)); } else { - title.to_string() - }; - trace!( - "Reporting app_id: {}, title: {}", - inserted_app_id, - inserted_title - ); - data.insert("app".to_string(), Value::String(inserted_app_id)); - data.insert("title".to_string(), Value::String(inserted_title)); + return Ok(()); + } + let event = AwEvent { id: None, timestamp: Utc::now(), @@ -141,6 +133,33 @@ impl ReportClient { .with_context(|| "Failed to send heartbeat for active window") } + fn get_filtered_data(&self, app_id: &str, title: &str) -> Option<(String, String)> { + let filter_result = self.config.match_window_data(app_id, title); + match filter_result { + FilterResult::Replace(replacement) => { + let app_id = if let Some(replace_app_id) = replacement.replace_app_id { + trace!("Replacing app_id by {}", replace_app_id); + replace_app_id + } else { + app_id.to_string() + }; + let title = if let Some(replace_title) = replacement.replace_title { + trace!("Replacing title by {}", replace_title); + replace_title + } else { + title.to_string() + }; + + Some((app_id, title)) + } + FilterResult::Match => { + trace!("Matched a filter, not reported"); + None + } + FilterResult::Skip => Some((app_id.to_string(), title.to_string())), + } + } + async fn create_bucket( client: &AwClient, bucket_name: &str,