Skip to content

Commit

Permalink
docs: misc improved docs, including rust watcher example (#132)
Browse files Browse the repository at this point in the history
* feat: add configuration data foraw-server-rust

* feat: add a custom visualizations entry

* feat:remove the Lastfm importer mention

* fix: minor change

* fix: syntax

* feat: create rust examples

* feat: add rust sample watchers

* Apply suggestions from code review

---------

Co-authored-by: Erik Bjäreholt <[email protected]>
  • Loading branch information
0xbrayo and ErikBjare authored Jul 1, 2024
1 parent cace212 commit 5dac898
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 5 deletions.
4 changes: 3 additions & 1 deletion src/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ aw-server-python
aw-server-rust
--------------

TODO
- :code:`host` Hostname to start the server on. Currently only :code:`localhost` or :code:`127.0.0.1` are supported.
- :code:`port` Port number to start the server on.
- :code:`cors` List of allowed origins for CORS (Cross-Origin Resource Sharing). Useful in testing and development to let other origins access the ActivityWatch API, such as aw-webui in development mode on port 27180.

aw-client
---------
Expand Down
11 changes: 11 additions & 0 deletions src/examples/cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "aw-minimal-client-rs"
version = "0.1.0"
edition = "2021"

[dependencies]
aw-client-rust = { git = "https://github.com/ActivityWatch/aw-server-rust.git", branch = "master" }
aw-models = { git = "https://github.com/ActivityWatch/aw-server-rust.git", branch = "master" }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
chrono = "0.4.19"
2 changes: 1 addition & 1 deletion src/examples/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# so run this every time the clients starts up to verify that the bucket exists.
# If the client was unable to connect to aw-server or something failed
# during the creation of the bucket, an exception will be raised.
client.create_bucket(bucket_id, event_type="test")
client.create_bucket(bucket_id, event_type=event_type)

# Asynchronous loop example
# This context manager starts the queue dispatcher thread and stops it when done, always use it when setting queued=True.
Expand Down
94 changes: 94 additions & 0 deletions src/examples/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use aw_client_rust::AwClient;
use aw_models::{Bucket, Event};
use chrono::TimeDelta;
use serde_json::{Map, Value};

async fn create_bucket(
aw_client: &AwClient,
bucket_id: String,
event_type: String,
) -> Result<(), Box<dyn std::error::Error>> {
let res = aw_client
.create_bucket(&Bucket {
id: bucket_id,
bid: None,
_type: event_type,
data: Map::new(),
metadata: Default::default(),
last_updated: None,
hostname: "".to_string(),
client: "test-client".to_string(),
created: None,
events: None,
})
.await?;

Ok(res)
}

#[tokio::main]
async fn main() {
let port = 5666; // the testing port
let aw_client = AwClient::new("localhost", port, "test-client").unwrap();
let bucket_id = format!("test-client-bucket_{}", aw_client.hostname);
let event_type = "dummy_data".to_string();

create_bucket(&aw_client, bucket_id.clone(), event_type)
.await
.unwrap();

let sleeptime = 1.0;
for i in 0..5 {
// Create a sample event to send as heartbeat
let mut heartbeat_data = Map::new();
heartbeat_data.insert("label".to_string(), Value::String("heartbeat".to_string()));

let now = chrono::Utc::now();

let heartbeat_event = Event {
id: None,
timestamp: now,
duration: TimeDelta::seconds(1),
data: heartbeat_data,
};

println!("Sending heartbeat event {}", i);
// The rust client does not support queued heartbeats, or commit intervals
aw_client
.heartbeat(&bucket_id, &heartbeat_event, sleeptime + 1.0)
.await
.unwrap();

// Sleep a second until next heartbeat (eventually drifts due to time spent in the loop)
tokio::time::sleep(tokio::time::Duration::from_secs_f64(sleeptime)).await;
}

// Sleep a bit more to allow the last heartbeat to be sent
tokio::time::sleep(tokio::time::Duration::from_secs_f64(sleeptime)).await;

// Synchoronous example, insert an event
let mut event_data = Map::new();
event_data.insert(
"label".to_string(),
Value::String("non-heartbeat event".to_string()),
);
let now = chrono::Utc::now();
let event = Event {
id: None,
timestamp: now,
duration: TimeDelta::seconds(1),
data: event_data,
};
aw_client.insert_event(&bucket_id, &event).await.unwrap();

// fetch the last 10 events
// should include the first 5 heartbeats and the last event
let events = aw_client
.get_events(&bucket_id, None, None, Some(10))
.await
.unwrap();
println!("Events: {:?}", events);

// Delete the bucket
aw_client.delete_bucket(&bucket_id).await.unwrap();
}
55 changes: 55 additions & 0 deletions src/examples/minimal_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use aw_client_rust::AwClient;
use aw_models::{Bucket, Event};
use chrono::TimeDelta;
use serde_json::{Map, Value};

async fn create_bucket(
aw_client: &AwClient,
bucket_id: String,
) -> Result<(), Box<dyn std::error::Error>> {
let res = aw_client
.create_bucket(&Bucket {
id: bucket_id,
bid: None,
_type: "dummy_data".to_string(),
data: Map::new(),
metadata: Default::default(),
last_updated: None,
hostname: "".to_string(),
client: "test-client".to_string(),
created: None,
events: None,
})
.await?;

Ok(res)
}

#[tokio::main]
async fn main() {
let port = 5666; // the testing port
let aw_client = AwClient::new("localhost", port, "test-client").unwrap();
let bucket_id = format!("test-client-bucket_{}", aw_client.hostname);

create_bucket(&aw_client, bucket_id.clone()).await.unwrap();

let mut shutdown_data = Map::new();
shutdown_data.insert(
"label".to_string(),
Value::String("some interesting data".to_string()),
);

let now = chrono::Utc::now();
let shutdown_event = Event {
id: None,
timestamp: now,
duration: TimeDelta::seconds(420),
data: shutdown_data,
};
aw_client.insert_event(&bucket_id, &shutdown_event).await.unwrap();

let events = aw_client.get_events(&bucket_id, None, None, Some(1)).await.unwrap();
print!("{:?}", events); // prints a single event

aw_client.delete_bucket(&bucket_id).await.unwrap();
}
39 changes: 37 additions & 2 deletions src/examples/writing-watchers.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Writing your first watcher
==========================
Writing your first watcher in Python
====================================

Writing watchers for ActivityWatch is pretty easy, all you need is the :code:`aw-client` library.

Expand Down Expand Up @@ -34,3 +34,38 @@ This example will describe how to:

.. literalinclude:: client.py

Writing your first watcher in Rust
==================================

To get started with writing watchers in Rust, you need to add the ``aw-client-rust`` and ``aw-model`` crates to your ``Cargo.toml`` file.
The most up-to-date versions depend directly on :gh-aw:`aw-server-rust`.

.. literalinclude:: Cargo.toml

Minimal client
--------------

Below is a minimal template client to quickly get started. Mirrors the python example above.
This example will:

* create a bucket
* insert an event
* fetch an event from an aw-server bucket
* delete the bucket again

.. literalinclude:: minimal_client.rs

Reference client
----------------

Below is a example of a watcher with more in-depth comments. Mirrors the python example above.
This example will describe how to:
* create buckets
* send events by heartbeats
* insert events without heartbeats
* do synchronous as well as asyncronous requests
* fetch events from an aw-server bucket
* delete buckets

.. literalinclude:: client.rs

1 change: 0 additions & 1 deletion src/importers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ ActivityWatch can't track everything, so sometimes you might want to import data
- :gh-aw:`aw-import-ical`, supports importing from ``.ical`` files or syncing with Google Calendar.
- :gh-aw:`aw-importer-smartertime`, imports from `smartertime`_ (Android time tracker).
- :gh-aw:`aw-import-screentime`, attempt at importing from macOS's Screen Time (and potentially iOS through syncing)
- LastFM importer, :gh-user:`ErikBjare` has code for it somewhere, ask him if you're interested.


.. _smartertime: https://play.google.com/store/apps/details?id=com.smartertime&hl=en
2 changes: 2 additions & 0 deletions src/watchers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ If you want to more accurately track media consumption.
- :gh-aw:`aw-watcher-openvr` - (not working yet) Watches active VR applications.
- :gh:`RundownRhino/aw-watcher-mpv-sender` - (WIP) Watches mpv and reports the currently playing video.
- :gh:`2e3s/aw-watcher-media-player` - Watches the currently playing media which is reported by most players to the system.
- :gh:`brayo-pip/aw-watcher-lastfm` - Watches the currently playing track on Last.fm(supports most streaming services including Apple Music).

.. _other-watchers:

Expand Down Expand Up @@ -76,6 +77,7 @@ Custom watchers might not be supported by the default visualizations, but Activi

- :gh-aw:`aw-watcher-input`
- :gh:`Alwinator/aw-watcher-utilization`
- :gh:`2e3s/aw-watcher-media-player`

.. note::

Expand Down

0 comments on commit 5dac898

Please sign in to comment.