Implement window data filtration

This commit is contained in:
Demmie 2023-04-22 19:05:30 -04:00
parent f63033b6f3
commit 69130e47a8
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
6 changed files with 137 additions and 8 deletions

13
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
@ -223,6 +223,7 @@ dependencies = [
"fern",
"gethostname 0.4.1",
"log",
"regex",
"serde",
"serde_default",
"serde_json",
@ -1405,9 +1406,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.3"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
@ -1416,9 +1417,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.29"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "reqwest"

View File

@ -20,3 +20,4 @@ toml = "0.7.3"
dirs = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_default = "0.1.0"
regex = "1.8.1"

View File

@ -1,11 +1,14 @@
mod defaults;
mod file_config;
mod filters;
use crate::BoxedError;
use clap::{arg, value_parser, Command};
use file_config::FileConfig;
use std::{path::PathBuf, time::Duration};
use self::filters::{Filter, Replacement};
pub struct Config {
pub port: u32,
pub host: String,
@ -14,6 +17,7 @@ pub struct Config {
pub poll_time_window: Duration,
pub idle_bucket_name: String,
pub active_window_bucket_name: String,
filters: Vec<Filter>,
}
impl Config {
@ -55,6 +59,17 @@ impl Config {
poll_time_window: config.client.get_poll_time_window(),
idle_bucket_name,
active_window_bucket_name,
filters: config.client.filters,
})
}
pub fn window_data_replacement(&self, app_id: &str, title: &str) -> Replacement {
for filter in &self.filters {
if let Some(replacement) = filter.replacement(app_id, title) {
return replacement;
}
}
Replacement::default()
}
}

View File

@ -9,6 +9,8 @@ use std::{
use crate::{config::defaults, BoxedError};
use super::filters::Filter;
#[derive(Deserialize, DefaultFromSerde)]
pub struct ServerConfig {
#[serde(default = "defaults::port")]
@ -18,6 +20,7 @@ pub struct ServerConfig {
}
#[derive(Deserialize, DefaultFromSerde)]
#[serde(rename_all = "kebab-case")]
pub struct ClientConfig {
#[serde(default = "defaults::idle_timeout_seconds")]
idle_timeout_seconds: u32,
@ -25,6 +28,8 @@ pub struct ClientConfig {
poll_time_idle_seconds: u32,
#[serde(default = "defaults::poll_time_window_seconds")]
poll_time_window_seconds: u32,
#[serde(default)]
pub filters: Vec<Filter>,
}
impl ClientConfig {
@ -83,6 +88,19 @@ impl FileConfig {
# idle-timeout-seconds={}
# poll-time-idle-seconds={}
# poll-time-window-seconds={}
# Add as many filters as needed. The first matching filter stops the replacement.
# There should be at least 1 match field, and at least 1 replace field.
# Matches are case sensitive regular expressions between implici ^ and $, e.g.
# - "." matches 1 any character
# - ".*" matches any number of any characters
# - ".+" matches 1 or more any characters.
# - "word" is an exact match.
# [[awatcher.filters]]
# match-app-id = "navigator"
# match-title = ".*Firefox.*"
# replace-app-id = "firefox"
# replace-title = "Unknown"
"#,
defaults::port(),
defaults::host(),

79
src/config/filters.rs Normal file
View File

@ -0,0 +1,79 @@
use regex::Regex;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Filter {
#[serde(default)]
#[serde(deserialize_with = "string_to_regex")]
match_app_id: Option<Regex>,
#[serde(default)]
#[serde(deserialize_with = "string_to_regex")]
match_title: Option<Regex>,
replace_app_id: Option<String>,
replace_title: Option<String>,
}
fn string_to_regex<'de, D>(d: D) -> Result<Option<Regex>, D::Error>
where
D: Deserializer<'de>,
{
let s = <Option<String>>::deserialize(d)?;
if let Some(s) = s {
match format!("^{s}$").parse() {
Ok(regex) => Ok(Some(regex)),
Err(err) => Err(D::Error::custom(err)),
}
} else {
Ok(None)
}
}
#[derive(Default)]
pub struct Replacement {
pub replace_app_id: Option<String>,
pub replace_title: Option<String>,
}
impl Filter {
fn is_valid(&self) -> bool {
(self.match_app_id.is_some() || self.match_title.is_some())
&& (self.replace_app_id.is_some() || self.replace_title.is_some())
}
fn is_match(&self, app_id: &str, title: &str) -> bool {
if let Some(match_app_id) = &self.match_app_id {
if !match_app_id.is_match(app_id) {
return false;
};
};
if let Some(match_title) = &self.match_title {
if !match_title.is_match(title) {
return false;
};
};
true
}
pub fn replacement(&self, app_id: &str, title: &str) -> Option<Replacement> {
if !self.is_valid() {
return None;
}
if self.is_match(app_id, title) {
let mut replacement = Replacement::default();
if let Some(new_app_id) = &self.replace_app_id {
replacement.replace_app_id = Some(new_app_id.to_string());
}
if let Some(new_title) = &self.replace_title {
replacement.replace_title = Some(new_title.to_string());
}
Some(replacement)
} else {
None
}
}
}

View File

@ -49,8 +49,23 @@ impl ReportClient {
pub fn send_active_window(&self, app_id: &str, title: &str) -> Result<(), BoxedError> {
let mut data = Map::new();
data.insert("app".to_string(), Value::String(app_id.to_string()));
data.insert("title".to_string(), Value::String(title.to_string()));
let mut data_insert = |k: &str, v: String| data.insert(k.to_string(), Value::String(v));
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()
};
data_insert("app", inserted_app_id);
data_insert("title", inserted_title);
let event = AwEvent {
id: None,
timestamp: Utc::now(),