Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ActivityWatch to Ignoring / Filtering #302

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
921791b
Allow ActivityWatch Ignoring
NathanaelA Sep 18, 2022
298e798
build(deps): bump dirs from 4.0.0 to 5.0.1 (#439)
dependabot[bot] Dec 2, 2023
df0abb1
build(deps): updated rocket to v0.5.0 and rocket_cors to v0.6.0
ErikBjare Dec 2, 2023
96f2d0a
build(deps): bump rusqlite from 0.28.0 to 0.30.0 (#445)
dependabot[bot] Dec 2, 2023
fb58336
build(deps): updated deps (#448)
ErikBjare Dec 2, 2023
b87e32e
build(deps): bump fancy-regex from 0.11.0 to 0.12.0 (#444)
dependabot[bot] Dec 2, 2023
41b030a
docs(sync): updated README for aw-sync with proper usage instructions
ErikBjare Jan 7, 2024
1b56e03
fix: added sync dir config via global `--sync-dir` cli param and `AW_…
ErikBjare Jan 7, 2024
1886ebe
build(deps): bump ahash
ErikBjare Apr 2, 2024
c056e50
feat: added query function in aw-client (#477)
ErikBjare Apr 5, 2024
75ca368
build(deps): updated aw-webui
ErikBjare May 6, 2024
f1f63c3
fix: fix for served paths in new aw-webui build config (no more /stat…
ErikBjare May 6, 2024
9cc06c7
build(deps): update aw-webui
ErikBjare May 6, 2024
6318204
build(deps): update aw-webui
ErikBjare May 7, 2024
124192b
build(deps): update aw-webui
ErikBjare May 7, 2024
14165e1
fix: fixed compilation warnings & deprecations (#479)
ErikBjare May 11, 2024
20ab632
build(deps): update aw-webui
ErikBjare May 11, 2024
051bb64
build(deps): update aw-webui
ErikBjare May 11, 2024
18a3496
build(deps): added vendored openssl for aw-sync (fixes https://github…
ErikBjare May 13, 2024
9275009
build: added `cargo:rerun-if-env-changed` for AW_WEBUI_DIR
ErikBjare May 13, 2024
3531ead
build(deps): updated aw-webui
ErikBjare Jun 10, 2024
b8330f1
chore: bumped aw-server version to 0.13.1
ErikBjare Jun 10, 2024
5a51fb9
build(deps): remove unused multipart dependency (#481)
wojnilowicz Jun 10, 2024
82345cf
build(deps): updated Cargo.lock, with version bumped to v0.13.1 and m…
ErikBjare Jun 10, 2024
bb787fd
build(deps): replace rocket with url crate for URI parsing in aw-tran…
2e3s Jun 11, 2024
8d7ce89
fix(linux): add sd-notify for better systemd interop (#489)
wojnilowicz Aug 24, 2024
aab4ac2
build(deps): updated dependencies for Rust 1.80 compat
ErikBjare Aug 24, 2024
b574baf
build(deps): updated aw-webui
ErikBjare Aug 31, 2024
5738246
fix(sync): remove bug-causing workaround for spurious small db files …
ErikBjare Sep 23, 2024
85c08a3
Allow ActivityWatch Ignoring
NathanaelA Sep 18, 2022
7b2508a
fixes
NathanaelA Oct 4, 2024
dba809b
Update Ignore code to use new key/value system
NathanaelA Oct 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,321 changes: 665 additions & 656 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion aw-client-rust/src/blocking.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::future::Future;
use std::vec::Vec;
use std::{collections::HashMap, error::Error};

use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -62,6 +61,12 @@ impl AwClient {
stop: Option<DateTime<Utc>>,
limit: Option<u64>
);
proxy_method!(
query,
Vec<serde_json::Value>,
query: &str,
timeperiods: Vec<(DateTime<Utc>, DateTime<Utc>)>
);
proxy_method!(insert_event, (), bucketname: &str, event: &Event);
proxy_method!(insert_events, (), bucketname: &str, events: Vec<Event>);
proxy_method!(
Expand Down
30 changes: 28 additions & 2 deletions aw-client-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ extern crate tokio;

pub mod blocking;

use std::vec::Vec;
use std::{collections::HashMap, error::Error};

use chrono::{DateTime, Utc};
use serde_json::Map;
use serde_json::{json, Map};

pub use aw_models::{Bucket, BucketMetadata, Event};

Expand Down Expand Up @@ -98,6 +97,33 @@ impl AwClient {
Ok(())
}

pub async fn query(
&self,
query: &str,
timeperiods: Vec<(DateTime<Utc>, DateTime<Utc>)>,
) -> Result<Vec<serde_json::Value>, reqwest::Error> {
let url = reqwest::Url::parse(format!("{}/api/0/query", self.baseurl).as_str()).unwrap();

// Format timeperiods as ISO8601 strings, separated by /
let timeperiods_str: Vec<String> = timeperiods
.iter()
.map(|(start, stop)| (start.to_rfc3339(), stop.to_rfc3339()))
.map(|(start, stop)| format!("{}/{}", start, stop))
.collect();

// Result is a sequence, one element per timeperiod
self.client
.post(url)
.json(&json!({
"query": query.split('\n').collect::<Vec<&str>>(),
"timeperiods": timeperiods_str,
}))
.send()
.await?
.json()
.await
}

pub async fn get_events(
&self,
bucketname: &str,
Expand Down
16 changes: 16 additions & 0 deletions aw-client-rust/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ mod test {
println!("Events: {events:?}");
assert!(events[0].duration == Duration::seconds(1));

// Query
let query = format!(
"events = query_bucket(\"{}\");
RETURN = events;",
bucket.id
);
let start: DateTime<Utc> = DateTime::parse_from_rfc3339("1996-12-19T00:00:00-08:00")
.unwrap()
.into();
let end: DateTime<Utc> = DateTime::parse_from_rfc3339("2020-12-19T00:00:00-08:00")
.unwrap()
.into();
let timeperiods = (start, end);
let query_result = client.query(&query, vec![timeperiods]).unwrap();
println!("Query result: {query_result:?}");

client
.delete_event(&bucketname, events[0].id.unwrap())
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion aw-datastore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ appdirs = "0.2"
serde = "1.0"
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
rusqlite = { version = "0.28", features = ["chrono", "serde_json", "bundled"] }
rusqlite = { version = "0.30", features = ["chrono", "serde_json", "bundled"] }
mpsc_requests = "0.3"
log = "0.4"

Expand Down
116 changes: 97 additions & 19 deletions aw-datastore/src/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use std::collections::HashMap;

use chrono::DateTime;
use chrono::Duration;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono::Utc;

use rusqlite::Connection;
Expand Down Expand Up @@ -173,6 +171,8 @@ fn _migrate_v3_to_v4(conn: &Connection) {

pub struct DatastoreInstance {
buckets_cache: HashMap<String, Bucket>,
ignored_apps: Vec<String>,
ignored_titles: Vec<String>,
first_init: bool,
pub db_version: i32,
}
Expand Down Expand Up @@ -201,13 +201,57 @@ impl DatastoreInstance {

let mut ds = DatastoreInstance {
buckets_cache: HashMap::new(),
ignored_apps: vec![],
ignored_titles: vec![],
first_init,
db_version,
};
ds.get_stored_buckets(conn)?;
ds.get_stored_ignores(conn)?;
Ok(ds)
}

fn get_stored_ignores(&mut self, conn: &Connection) -> Result<(), DatastoreError> {
let result = self.get_key_value(conn, "IGNORE_FILTERS_APPS");
if result.is_ok() {
let unwrapped_key_value = &result.unwrap();
let parts = unwrapped_key_value.split(",");
for part in parts {
self.ignored_apps.push(part.to_uppercase());
}

/*let apps_exist = unwrapped_key_value.value.get("APPS");
if apps_exist.is_some() {
let apps = apps_exist.unwrap().as_array().unwrap();
for key in apps {
let value = key.as_str().unwrap().to_string();
self.ignored_apps.push(value.to_uppercase());
}
}*/
}

let result = self.get_key_value(conn, "IGNORE_FILTERS_TITLES");
if result.is_ok() {
let unwrapped_key_value = &result.unwrap();
let parts = unwrapped_key_value.split(",");
for part in parts {
self.ignored_titles.push(part.to_uppercase());
}

/* let titles_exist = unwrapped_key_value.value.get("TITLES");
if titles_exist.is_some() {
let titles = titles_exist.unwrap().as_array().unwrap();
for key in titles {
let value = key.as_str().unwrap().to_string();
self.ignored_titles.push(value.to_uppercase());
}
} */

info!("Ignoring {} titles & {} apps.", self.ignored_titles.len(), self.ignored_apps.len());
}
Ok(())
}

fn get_stored_buckets(&mut self, conn: &Connection) -> Result<(), DatastoreError> {
let mut stmt = match conn.prepare(
"
Expand All @@ -233,10 +277,7 @@ impl DatastoreInstance {
Some(starttime_ns) => {
let seconds: i64 = starttime_ns / 1_000_000_000;
let subnanos: u32 = (starttime_ns % 1_000_000_000) as u32;
Some(TimeZone::from_utc_datetime(
&Utc,
&NaiveDateTime::from_timestamp_opt(seconds, subnanos).unwrap(),
))
Some(DateTime::from_timestamp(seconds, subnanos).unwrap())
}
None => None,
};
Expand All @@ -246,10 +287,7 @@ impl DatastoreInstance {
Some(endtime_ns) => {
let seconds: i64 = endtime_ns / 1_000_000_000;
let subnanos: u32 = (endtime_ns % 1_000_000_000) as u32;
Some(TimeZone::from_utc_datetime(
&Utc,
&NaiveDateTime::from_timestamp_opt(seconds, subnanos).unwrap(),
))
Some(DateTime::from_timestamp(seconds, subnanos).unwrap())
}
None => None,
};
Expand Down Expand Up @@ -450,8 +488,34 @@ impl DatastoreInstance {
)))
}
};
for event in &mut events {

'event: for event in &mut events {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be the datastores responsibility to ignore apps/titles imo. It should not manipulate any data. I think it's better to have this logic in the rest endpoint for insert_event and heartbeat.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to re-look but I could have sworn there was more than just insert_event and heartbeat that could trigger the insert, which is why I was trying to consolidate the code only in two paths rather than have multiple places for the code... But maybe I miss read the code and only insert_event and heardbeat call these two datastore code paths...

let app = match event.data.get("app") {
Some(data) => data.as_str().unwrap().to_string().to_uppercase(),
None => String::new()
};
let title = match event.data.get("title") {
Some(data) => data.as_str().unwrap().to_string().to_uppercase(),
None => String::new()
};

if !app.is_empty() {
for key in self.ignored_apps.iter().cloned() {
if app.contains(&key) {
continue 'event;
}
}
}
if !title.is_empty() {
for key in self.ignored_titles.iter().cloned() {
if title.contains(&key) {
continue 'event;
}
}
}

let starttime_nanos = event.timestamp.timestamp_nanos_opt().unwrap();

let duration_nanos = match event.duration.num_nanoseconds() {
Some(nanos) => nanos,
None => {
Expand Down Expand Up @@ -561,6 +625,26 @@ impl DatastoreInstance {
bucket_id: &str,
event: &Event,
) -> Result<(), DatastoreError> {

let app_exists = event.data.get("app");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste of the same code above. Better to put it in a generic function. Unittests also wouldn't hurt.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this should be made into a generic gunction -- the second path was because in my original implementation I found there was the second path that also created data entries (heartbeat)... So I duplicated the code to verify it would cover all the cases... Revamping the code is fine and I will check into moving this up to a higher layer, I just want to make sure I don't miss any code paths that plugins or other entities can call that will generate this data....

if app_exists.is_some() {
let app = app_exists.unwrap().as_str().unwrap().to_string().to_uppercase();
for key in self.ignored_apps.iter().cloned() {
if app.contains(&key) {
return Ok(());
}
}
}
let title_exists = event.data.get("title");
if title_exists.is_some() {
let title = title_exists.unwrap().as_str().unwrap().to_string().to_uppercase();
for key in self.ignored_titles.iter().cloned() {
if title.contains(&key) {
return Ok(());
}
}
}

let mut bucket = self.get_bucket(bucket_id)?;

let mut stmt = match conn.prepare(
Expand Down Expand Up @@ -689,10 +773,7 @@ impl DatastoreInstance {

Ok(Event {
id: Some(id),
timestamp: TimeZone::from_utc_datetime(
&Utc,
&NaiveDateTime::from_timestamp_opt(time_seconds, time_subnanos).unwrap(),
),
timestamp: DateTime::from_timestamp(time_seconds, time_subnanos).unwrap(),
duration: Duration::nanoseconds(duration_ns),
data,
})
Expand Down Expand Up @@ -784,10 +865,7 @@ impl DatastoreInstance {

Ok(Event {
id: Some(id),
timestamp: TimeZone::from_utc_datetime(
&Utc,
&NaiveDateTime::from_timestamp_opt(time_seconds, time_subnanos).unwrap(),
),
timestamp: DateTime::from_timestamp(time_seconds, time_subnanos).unwrap(),
duration: Duration::nanoseconds(duration_ns),
data,
})
Expand Down
2 changes: 1 addition & 1 deletion aw-query/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
plex = "0.3.0"
log = "0.4"
fancy-regex = "0.11.0"
fancy-regex = "0.12.0"
aw-datastore = { path = "../aw-datastore" }
aw-models = { path = "../aw-models" }
aw-transform = { path = "../aw-transform" }
Expand Down
1 change: 0 additions & 1 deletion aw-query/src/datatype.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;

use super::functions;
Expand Down
3 changes: 0 additions & 3 deletions aw-query/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,6 @@ pub fn fill_env(env: &mut VarEnv) {
}

mod qfunctions {
use std::convert::TryFrom;
use std::convert::TryInto;

use aw_datastore::Datastore;
use aw_models::Event;
use aw_transform::classify::Rule;
Expand Down
4 changes: 2 additions & 2 deletions aw-server.service
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
#

[Service]
Type=simple
Type=notify
ExecStart=aw-server

[Unit]
Description=ActivityWatch Server (Rust implementation)
Wants=network.target
After=network.target

[Install]
WantedBy=default.target
10 changes: 6 additions & 4 deletions aw-server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aw-server"
version = "0.12.1"
version = "0.13.1"
authors = ["Johan Bjäreholt <[email protected]>", "Erik Bjäreholt <[email protected]>"]
edition = "2021"

Expand All @@ -14,9 +14,8 @@ name = "aw-server"
path = "src/main.rs"

[dependencies]
rocket = { version = "0.5.0-rc.3", features = ["json"] }
rocket_cors = { version = "0.6.0-alpha2" }
multipart = { version = "0.18", default-features = false, features = ["server"] }
rocket = { version = "0.5.0", features = ["json"] }
rocket_cors = { version = "0.6.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
Expand All @@ -36,6 +35,9 @@ aw-models = { path = "../aw-models" }
aw-transform = { path = "../aw-transform" }
aw-query = { path = "../aw-query" }

[target.'cfg(target_os="linux")'.dependencies]
sd-notify = "0.4.2"

[target.'cfg(all(target_os="linux", target_arch="x86"))'.dependencies]
jemallocator = "0.5.0"

Expand Down
6 changes: 6 additions & 0 deletions aw-server/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ fn main() -> Result<(), Box<dyn Error>> {
);
}

// Rebuild if the webui directory changes
println!("cargo:rerun-if-env-changed=AW_WEBUI_DIR");
if webui_var.is_ok() {
println!("cargo:rerun-if-changed={}", webui_var.unwrap());
}

Ok(())
}
Loading