Add a bundle feature

This commit is contained in:
Demmie 2023-05-08 00:28:10 -04:00
parent 856a7afee6
commit 216b2a1334
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
11 changed files with 2011 additions and 138 deletions

View File

@ -21,6 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: sudo apt-get install -y libdbus-1-dev
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
with: with:
components: clippy components: clippy

1955
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,16 @@ fern = { version = "0.6.2", features = ["colored"] }
log = { workspace = true } log = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
ksni = {version = "0.2.0", optional = true}
aw-server = { git = "https://github.com/ActivityWatch/aw-server-rust.git", optional = true }
aw-datastore = { git = "https://github.com/ActivityWatch/aw-server-rust.git", optional = true }
smol = {version = "1.3.0", optional = true }
async-compat = { version = "0.2.1", optional = true }
webbrowser = { version = "0.8.9", optional = true }
zip-extract = { version = "0.1.2", default-features = false, features = ["deflate"], optional = true }
[features] [features]
default = ["gnome", "kwin_window"] default = ["gnome", "kwin_window"]
gnome = ["watchers/gnome"] gnome = ["watchers/gnome"]
kwin_window = ["watchers/kwin_window"] kwin_window = ["watchers/kwin_window"]
bundle = ["ksni", "smol", "async-compat", "aw-server", "aw-datastore", "webbrowser", "zip-extract"]

View File

@ -24,6 +24,19 @@ add `--features=?` ("gnome" or "kwin_window") on top of that if you want to enab
To track your activities in browsers install the plugin for your browser from To track your activities in browsers install the plugin for your browser from
[here](https://github.com/ActivityWatch/aw-watcher-web) (Firefox, Chrome etc). [here](https://github.com/ActivityWatch/aw-watcher-web) (Firefox, Chrome etc).
#### Compile with bundle
The executable can be bundled with a tray icon, ActivityWatch server and, optionally, Web UI (if steps 1-2 are done):
1. Clone and follow the instruction in [ActivityWatch/aw-webui](https://github.com/ActivityWatch/aw-webui)
to build the "dist" folder,
1. Then zip it with `zip -r dist.zip aw-webui/dist`.
2. Build the executable with `--features=bundle`.
This should be compiled on nightly. The complete bundled version is also built and released.
Gnome needs [the extension](https://extensions.gnome.org/extension/615/appindicator-support/) to support StatusNotifierItem specification.
## Supported environments ## Supported environments
ActivityWatch server should be run before `awatcher` is running. ActivityWatch server should be run before `awatcher` is running.

21
src/bundle.rs Normal file
View File

@ -0,0 +1,21 @@
mod menu;
mod server;
mod site_data;
pub use menu::Tray;
use site_data::unpack_data;
use watchers::config::Config;
pub fn run(config: &Config) -> anyhow::Result<()> {
let service = ksni::TrayService::new(Tray {
server_host: config.host.clone(),
server_port: config.port,
});
service.spawn();
let port = config.port;
let data_dir = unpack_data()?;
server::run(data_dir, port);
Ok(())
}

BIN
src/bundle/dist.zip Normal file

Binary file not shown.

41
src/bundle/menu.rs Normal file
View File

@ -0,0 +1,41 @@
#[derive(Debug)]
pub struct Tray {
pub server_host: String,
pub server_port: u32,
}
impl ksni::Tray for Tray {
fn icon_name(&self) -> String {
// Taken from https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
"appointment-new".into()
}
fn title(&self) -> String {
"Awatcher".into()
}
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
vec![
ksni::menu::StandardItem {
label: "Open".into(),
icon_name: "document-properties".into(),
activate: {
let url = format!("http://{}:{}", self.server_host, self.server_port);
Box::new(move |_| {
webbrowser::open(&url).unwrap();
})
},
..Default::default()
}
.into(),
ksni::menu::StandardItem {
label: "Exit".into(),
icon_name: "application-exit".into(),
activate: Box::new(|_| {
std::process::exit(0);
}),
..Default::default()
}
.into(),
]
}
}

29
src/bundle/server.rs Normal file
View File

@ -0,0 +1,29 @@
use anyhow::anyhow;
use async_compat::Compat;
use aw_server::endpoints::build_rocket;
use std::{path::PathBuf, sync::Mutex};
pub fn run(asset_path: PathBuf, port: u32) {
std::thread::spawn(move || {
let db_path = aw_server::dirs::db_path(false)
.map_err(|_| anyhow!("DB path is not found: {}", asset_path.display()))
.unwrap()
.to_str()
.unwrap()
.to_string();
let device_id = aw_server::device_id::get_device_id();
let mut config = aw_server::config::create_config(false);
config.address = "127.0.0.1".to_string();
config.port = u16::try_from(port).unwrap();
let legacy_import = false;
let server_state = aw_server::endpoints::ServerState {
datastore: Mutex::new(aw_datastore::Datastore::new(db_path, legacy_import)),
asset_path: asset_path.join("dist"),
device_id,
};
let future = build_rocket(server_state, config).launch();
smol::block_on(Compat::new(future)).unwrap();
});
}

14
src/bundle/site_data.rs Normal file
View File

@ -0,0 +1,14 @@
use std::{fs, io::Cursor, path::PathBuf};
const SITE_DATA: &[u8] = include_bytes!("./dist.zip");
pub fn unpack_data() -> anyhow::Result<PathBuf> {
let target_dir = std::env::temp_dir().join("awatcher");
if target_dir.exists() {
fs::remove_dir_all(&target_dir)?;
}
zip_extract::extract(Cursor::new(SITE_DATA), &target_dir, false)?;
Ok(target_dir)
}

View File

@ -6,15 +6,10 @@ use clap::{arg, value_parser, Arg, ArgAction, ArgMatches, Command};
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use log::LevelFilter; use log::LevelFilter;
use watchers::config::defaults; use watchers::config::defaults;
use watchers::config::Config as WatchersConfig; use watchers::config::Config;
use watchers::config::FileConfig; use watchers::config::FileConfig;
pub struct Config { pub fn setup_logger(verbosity: LevelFilter) -> Result<(), fern::InitError> {
pub watchers_config: WatchersConfig,
verbosity: LevelFilter,
}
pub fn setup_logger(config: &Config) -> Result<(), fern::InitError> {
fern::Dispatch::new() fern::Dispatch::new()
.format(|out, message, record| { .format(|out, message, record| {
let colors = ColoredLevelConfig::new() let colors = ColoredLevelConfig::new()
@ -29,9 +24,9 @@ pub fn setup_logger(config: &Config) -> Result<(), fern::InitError> {
message message
)); ));
}) })
.level(log::LevelFilter::Error) .level(log::LevelFilter::Warn)
.level_for("watchers", config.verbosity) .level_for("watchers", verbosity)
.level_for("awatcher", config.verbosity) .level_for("awatcher", verbosity)
.chain(std::io::stdout()) .chain(std::io::stdout())
.apply()?; .apply()?;
Ok(()) Ok(())
@ -77,9 +72,9 @@ pub fn from_cli() -> anyhow::Result<Config> {
3 => LevelFilter::Debug, 3 => LevelFilter::Debug,
_ => LevelFilter::Trace, _ => LevelFilter::Trace,
}; };
setup_logger(verbosity)?;
Ok(Config { Ok(Config {
watchers_config: WatchersConfig {
port: config.server.port, port: config.server.port,
host: config.server.host, host: config.server.host,
idle_timeout: config.client.get_idle_timeout(), idle_timeout: config.client.get_idle_timeout(),
@ -87,8 +82,6 @@ pub fn from_cli() -> anyhow::Result<Config> {
poll_time_window: config.client.get_poll_time_window(), poll_time_window: config.client.get_poll_time_window(),
filters: config.client.filters, filters: config.client.filters,
no_server: *matches.get_one("no-server").unwrap(), no_server: *matches.get_one("no-server").unwrap(),
},
verbosity,
}) })
} }

View File

@ -3,6 +3,8 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[cfg(feature = "bundle")]
mod bundle;
mod config; mod config;
use std::sync::Arc; use std::sync::Arc;
@ -11,35 +13,28 @@ use watchers::ReportClient;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let config = config::from_cli()?; let config = config::from_cli()?;
config::setup_logger(&config)?;
let client = ReportClient::new(config.watchers_config)?; if config.no_server {
let client = Arc::new(client); warn!("Not sending to server {}:{}", config.host, config.port);
if client.config.no_server {
warn!(
"Not sending to server {}:{}",
client.config.host, client.config.port
);
} else { } else {
info!( info!("Sending to server {}:{}", config.host, config.port);
"Sending to server {}:{}",
client.config.host, client.config.port
);
} }
info!( info!("Idle timeout: {} seconds", config.idle_timeout.as_secs());
"Idle timeout: {} seconds",
client.config.idle_timeout.as_secs()
);
info!( info!(
"Idle polling period: {} seconds", "Idle polling period: {} seconds",
client.config.poll_time_idle.as_secs() config.poll_time_idle.as_secs()
); );
info!( info!(
"Window polling period: {} seconds", "Window polling period: {} seconds",
client.config.poll_time_window.as_secs() config.poll_time_window.as_secs()
); );
#[cfg(feature = "bundle")]
bundle::run(&config)?;
let client = ReportClient::new(config)?;
let client = Arc::new(client);
let mut thread_handlers = Vec::new(); let mut thread_handlers = Vec::new();
if let Some(idle_handler) = watchers::IDLE.run_first_supported(&client) { if let Some(idle_handler) = watchers::IDLE.run_first_supported(&client) {