Add TOML config file support

This commit is contained in:
Demmie 2023-04-22 02:53:15 -04:00
parent d710674d50
commit d5103bfe78
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
5 changed files with 273 additions and 29 deletions

117
Cargo.lock generated
View File

@ -219,10 +219,14 @@ dependencies = [
"aw-client-rust",
"chrono",
"clap",
"dirs 5.0.0",
"fern",
"gethostname 0.4.1",
"log",
"serde",
"serde_default",
"serde_json",
"toml",
"wayland-backend",
"wayland-client",
"wayland-scanner",
@ -472,6 +476,41 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn 1.0.109",
]
[[package]]
name = "derivative"
version = "2.2.0"
@ -499,7 +538,16 @@ version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
"dirs-sys 0.3.7",
]
[[package]]
name = "dirs"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
dependencies = [
"dirs-sys 0.4.0",
]
[[package]]
@ -513,6 +561,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
dependencies = [
"libc",
"redox_users",
"windows-sys 0.45.0",
]
[[package]]
name = "dlib"
version = "0.5.0"
@ -895,6 +954,12 @@ dependencies = [
"cxx-build",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
@ -1483,18 +1548,30 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.159"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.159"
name = "serde_default"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
checksum = "9fd4c77b86d9fb10363e52607ca6dc3043d8dfde6c790b702ed4ffafb34e7b99"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
@ -1534,6 +1611,15 @@ dependencies = [
"syn 2.0.12",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1724,11 +1810,26 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@ -1737,6 +1838,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@ -2268,7 +2371,7 @@ dependencies = [
"async-trait",
"byteorder",
"derivative",
"dirs",
"dirs 4.0.0",
"enumflags2",
"event-listener",
"futures-core",

View File

@ -12,7 +12,11 @@ wayland-backend = "0.1"
chrono = "0.4.24"
serde_json = "1.0.95"
zbus = "3.11.1"
clap = "4.2.1"
clap = { version = "4.2.1", features = ["string"] }
log = { version = "0.4.17", features = ["std"] }
fern = { version = "0.6.2", features = ["colored"] }
x11rb = { version = "0.11.1", features = ["screensaver"] }
toml = "0.7.3"
dirs = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_default = "0.1.0"

View File

@ -1,6 +1,13 @@
use std::{path::PathBuf, time::Duration};
use clap::{arg, parser::ValueSource, value_parser, ArgMatches, Command};
use serde::Deserialize;
use serde_default::DefaultFromSerde;
use std::{
io::ErrorKind,
path::{Path, PathBuf},
time::Duration,
};
use clap::{arg, value_parser, Command};
use crate::BoxedError;
pub struct Config {
pub port: u32,
@ -12,8 +19,134 @@ pub struct Config {
pub active_window_bucket_name: String,
}
fn default_idle_timeout_seconds() -> u32 {
180
}
fn default_poll_time_idle_seconds() -> u32 {
5
}
fn default_poll_time_window_seconds() -> u32 {
1
}
fn default_port() -> u32 {
5600
}
fn default_host() -> String {
"localhost".to_string()
}
#[derive(Deserialize, DefaultFromSerde)]
struct ServerConfig {
#[serde(default = "default_port")]
port: u32,
#[serde(default = "default_host")]
host: String,
}
#[derive(Deserialize, DefaultFromSerde)]
struct ClientConfig {
#[serde(default = "default_idle_timeout_seconds")]
idle_timeout_seconds: u32,
#[serde(default = "default_poll_time_idle_seconds")]
poll_time_idle_seconds: u32,
#[serde(default = "default_poll_time_window_seconds")]
poll_time_window_seconds: u32,
}
#[derive(Deserialize, Default)]
struct FileConfig {
#[serde(default)]
server: ServerConfig,
#[serde(default)]
client: ClientConfig,
}
impl FileConfig {
fn new(config_path: &Path) -> Result<Self, BoxedError> {
if config_path.exists() {
debug!("Reading config at {}", config_path.display());
let config_content = std::fs::read_to_string(config_path)
.map_err(|e| format!("Impossible to read config file: {e}"))?;
Ok(toml::from_str(&config_content)?)
} else {
let config = format!(
r#"# The commented values are the defaults on the file creation
[server]
# port = {}
# host = "{}"
[awatcher]
# idle-timeout-seconds={}
# poll-time-idle-seconds={}
# poll-time-window-seconds={}
"#,
default_port(),
default_host(),
default_idle_timeout_seconds(),
default_poll_time_idle_seconds(),
default_poll_time_window_seconds(),
);
let error = std::fs::create_dir(config_path.parent().unwrap());
if let Err(e) = error {
if e.kind() != ErrorKind::AlreadyExists {
Err(e)?;
}
}
debug!("Creading config at {}", config_path.display());
std::fs::write(config_path, config)?;
Ok(Self::default())
}
}
fn merge_cli(&mut self, matches: &ArgMatches) {
self.client.poll_time_idle_seconds = get_arg_value(
"poll-time-idle",
matches,
self.client.poll_time_idle_seconds,
);
self.client.poll_time_window_seconds = get_arg_value(
"poll-time-window",
matches,
self.client.poll_time_window_seconds,
);
self.client.idle_timeout_seconds =
get_arg_value("idle-timeout", matches, self.client.idle_timeout_seconds);
self.server.port = get_arg_value("port", matches, self.server.port);
self.server.host = get_arg_value("host", matches, self.server.host.clone());
}
fn get_idle_timeout(&self) -> Duration {
Duration::from_secs(u64::from(self.client.idle_timeout_seconds))
}
fn get_poll_time_idle(&self) -> Duration {
Duration::from_secs(u64::from(self.client.poll_time_idle_seconds))
}
fn get_poll_time_window(&self) -> Duration {
Duration::from_secs(u64::from(self.client.poll_time_window_seconds))
}
}
fn get_arg_value<T>(id: &str, matches: &ArgMatches, config_value: T) -> T
where
T: Clone + Send + Sync + 'static,
{
if let Some(ValueSource::CommandLine) = matches.value_source(id) {
matches.get_one::<T>(id).unwrap().clone()
} else {
config_value
}
}
impl Config {
pub fn from_cli() -> Self {
pub fn from_cli() -> Result<Self, BoxedError> {
let mut config_path: PathBuf = dirs::config_dir().ok_or("Config directory is unknown")?;
config_path.push("awatcher");
config_path.push("config.toml");
let matches = Command::new("Activity Watcher")
.version("0.1.0")
.about("A set of ActivityWatch desktop watchers")
@ -21,37 +154,37 @@ impl Config {
arg!(-c --config <FILE> "Custom config file").value_parser(value_parser!(PathBuf)),
arg!(--port <PORT> "Custom server port")
.value_parser(value_parser!(u32))
.default_value("5600"),
.default_value(default_port().to_string()),
arg!(--host <HOST> "Custom server host")
.value_parser(value_parser!(String))
.default_value("localhost"),
.default_value(default_host()),
arg!(--"idle-timeout" <SECONDS> "Time of inactivity to consider the user idle")
.value_parser(value_parser!(u32))
.default_value("180"),
.default_value(default_idle_timeout_seconds().to_string()),
arg!(--"poll-time-idle" <SECONDS> "Period between sending heartbeats to the server for idle activity")
.value_parser(value_parser!(u32))
.default_value("5"),
.default_value(default_poll_time_idle_seconds().to_string()),
arg!(--"poll-time-window" <SECONDS> "Period between sending heartbeats to the server for idle activity")
.value_parser(value_parser!(u32))
.default_value("1"),
.default_value(default_poll_time_window_seconds().to_string()),
])
.get_matches();
let mut config = FileConfig::new(config_path.as_path())?;
config.merge_cli(&matches);
let hostname = gethostname::gethostname().into_string().unwrap();
let idle_bucket_name = format!("aw-watcher-afk_{hostname}");
let active_window_bucket_name = format!("aw-watcher-window_{hostname}");
let poll_seconds_idle = *matches.get_one::<u32>("poll-time-idle").unwrap();
let poll_seconds_window = *matches.get_one::<u32>("poll-time-window").unwrap();
let idle_timeout_seconds = *matches.get_one::<u32>("idle-timeout").unwrap();
Self {
port: *matches.get_one("port").unwrap(),
host: String::clone(matches.get_one("host").unwrap()),
idle_timeout: Duration::from_secs(u64::from(idle_timeout_seconds)),
poll_time_idle: Duration::from_secs(u64::from(poll_seconds_idle)),
poll_time_window: Duration::from_secs(u64::from(poll_seconds_window)),
Ok(Self {
port: config.server.port,
host: config.server.host.clone(),
idle_timeout: config.get_idle_timeout(),
poll_time_idle: config.get_poll_time_idle(),
poll_time_window: config.get_poll_time_window(),
idle_bucket_name,
active_window_bucket_name,
}
})
}
}

View File

@ -103,7 +103,7 @@ fn setup_logger() -> Result<(), fern::InitError> {
fn main() -> Result<(), BoxedError> {
setup_logger()?;
let client = ReportClient::new(Config::from_cli())?;
let client = ReportClient::new(Config::from_cli()?)?;
let client = Arc::new(client);
info!(
@ -115,7 +115,11 @@ fn main() -> Result<(), BoxedError> {
client.config.idle_timeout.as_secs()
);
info!(
"Polling period: {} seconds",
"Idle polling period: {} seconds",
client.config.poll_time_idle.as_secs()
);
info!(
"Window polling period: {} seconds",
client.config.poll_time_idle.as_secs()
);

View File

@ -1,4 +1,4 @@
use std::{thread, time::Duration};
use std::thread;
use crate::{report_client::ReportClient, x11_connection::X11Connection, BoxedError, Watcher};