Skip to content

Commit

Permalink
Merge branch 'main' into aw-instance
Browse files Browse the repository at this point in the history
  • Loading branch information
powellnorma committed Oct 11, 2024
2 parents d733008 + 16942ea commit dd974db
Show file tree
Hide file tree
Showing 9 changed files with 956 additions and 923 deletions.
1,695 changes: 832 additions & 863 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ members = ["watchers"]
version = "0.3.0"

[workspace.dependencies]
anyhow = "1.0.83"
anyhow = "1.0.89"
log = { version = "0.4.21", features = ["std"] }
tokio = { version = "1.37.0" }
serde = "1.0.202"

[dev-dependencies]
rstest = "0.21.0"
rstest = "0.23.0"
tempfile = "3.10.1"

[dependencies]
watchers = { path = "./watchers", default-features = false }
chrono = "0.4.38"
toml = "0.8.13"
clap = { version = "4.5.4", features = ["string"] }
clap = { version = "4.5.20", features = ["string"] }
fern = { version = "0.6.2", features = ["colored"] }
log = { workspace = true }
anyhow = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/bundle/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down
6 changes: 3 additions & 3 deletions watchers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ crate-type = ["lib"]
path = "src/lib.rs"

[dev-dependencies]
rstest = "0.21.0"
rstest = "0.23.0"
tempfile = "3.10.1"

[dependencies]
Expand All @@ -25,10 +25,10 @@ chrono = "0.4.38"
toml = "0.8.13"
dirs = "5.0.1"
serde = { workspace = true, features = ["derive"] }
serde_default = "0.1.0"
serde_default = "0.2.0"
serde_json = "1.0.117"
regex = "1.10.4"
gethostname = "0.4.3"
gethostname = "0.5.0"
log = { workspace = true }
anyhow = { workspace = true }
async-trait = "0.1.80"
Expand Down
12 changes: 7 additions & 5 deletions watchers/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}
}
43 changes: 33 additions & 10 deletions watchers/src/config/file_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ impl FileConfig {

#[cfg(test)]
mod tests {
use crate::config::FilterResult;

use super::*;
use rstest::rstest;
use std::io::Write;
Expand Down Expand Up @@ -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]
Expand Down
41 changes: 29 additions & 12 deletions watchers/src/config/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand All @@ -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 {
Expand Down Expand Up @@ -70,9 +74,12 @@ impl Filter {
replacement.to_owned()
}

pub fn replacement(&self, app_id: &str, title: &str) -> Option<Replacement> {
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();
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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>),
Expand All @@ -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);
}
}
68 changes: 43 additions & 25 deletions watchers/src/report_client.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -106,31 +105,23 @@ impl ReportClient {
) -> 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
} 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));
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));

if let Some(extra) = extra_data {
for (key, value) in extra {
data.insert(key, Value::String(value));
if let Some(extra) = extra_data {
for (key, value) in extra {
data.insert(key, Value::String(value));
}
}
} else {
return Ok(());
}

let event = AwEvent {
Expand Down Expand Up @@ -158,6 +149,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,
Expand Down

0 comments on commit dd974db

Please sign in to comment.