tracing and changes

This commit is contained in:
2026-09-13 16:25:47 +01:00
parent 74f59be3f8
commit 57201bc0ff
17 changed files with 306 additions and 47 deletions
Generated
+109
View File
@@ -31,6 +31,15 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.6.1" version = "0.6.1"
@@ -514,6 +523,8 @@ dependencies = [
"services", "services",
"tokio", "tokio",
"toml", "toml",
"tracing",
"tracing-subscriber",
] ]
[[package]] [[package]]
@@ -1913,6 +1924,12 @@ dependencies = [
"wayland-protocols-wlr", "wayland-protocols-wlr",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.189" version = "0.2.189"
@@ -2046,6 +2063,15 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.3" version = "2.8.3"
@@ -2097,11 +2123,13 @@ dependencies = [
name = "modules" name = "modules"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"common", "common",
"iced", "iced",
"serde", "serde",
"thiserror 2.0.20", "thiserror 2.0.20",
"toml", "toml",
"tracing",
] ]
[[package]] [[package]]
@@ -2187,6 +2215,15 @@ dependencies = [
"jni-sys 0.3.1", "jni-sys 0.3.1",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -2882,6 +2919,23 @@ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
] ]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]] [[package]]
name = "renderdoc-sys" name = "renderdoc-sys"
version = "1.1.0" version = "1.1.0"
@@ -3217,6 +3271,16 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tracing",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
] ]
[[package]] [[package]]
@@ -3595,6 +3659,15 @@ dependencies = [
"syn 3.0.5", "syn 3.0.5",
] ]
[[package]]
name = "thread_local"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.11.4"
@@ -3821,6 +3894,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@@ -3904,6 +4007,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
+2
View File
@@ -10,6 +10,8 @@ edition = "2021"
iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool", "advanced", "tokio"] } iced = { version = "0.14", default-features = false, features = ["wgpu", "wayland", "crisp", "web-colors", "thread-pool", "advanced", "tokio"] }
iced_layershell = { version = "0.19", default-features = false } iced_layershell = { version = "0.19", default-features = false }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = "0.4" chrono = "0.4"
toml = "1.1" toml = "1.1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@@ -47,7 +47,7 @@ pub struct HourlyUnits {
pub relative_humidity_2m: String, pub relative_humidity_2m: String,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct Hourly { pub struct Hourly {
#[serde(deserialize_with = "naive_dt::deserialize")] #[serde(deserialize_with = "naive_dt::deserialize")]
pub time: Vec<chrono::NaiveDateTime>, pub time: Vec<chrono::NaiveDateTime>,
+2
View File
@@ -11,6 +11,8 @@ common = { path = "../common" }
iced = { workspace = true } iced = { workspace = true }
iced_layershell = { workspace = true } iced_layershell = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
clap = {workspace = true} clap = {workspace = true}
toml = {workspace =true} toml = {workspace =true}
+7 -1
View File
@@ -39,10 +39,16 @@ impl Bar {
.collect(); .collect();
// One inbox per distinct service any module wants. // One inbox per distinct service any module wants.
let wanted: BTreeSet<&'static str> = routes.values().flatten().map(|s| s.0).collect(); let wanted: BTreeSet<&'static str> = routes.values().flatten().map(|s| s.0).collect();
let inputs = wanted tracing::debug!(?routes, "module routes: wanted msg types per module");
let inputs: BTreeMap<&'static str, Inbox> = wanted
.into_iter() .into_iter()
.map(|key| (key, Inbox::new(key))) .map(|key| (key, Inbox::new(key)))
.collect(); .collect();
tracing::debug!(
modules = modules.len(),
services = ?inputs.keys().copied().collect::<Vec<_>>(),
"spawning wanted services"
);
Self { Self {
active_popup: None, active_popup: None,
modules, modules,
+22 -6
View File
@@ -27,27 +27,43 @@ pub(crate) use msg::{Message, Msg};
/// Height of the bar in logical pixels. /// Height of the bar in logical pixels.
const BAR_HEIGHT: u32 = 36; const BAR_HEIGHT: u32 = 36;
fn main() -> Result<(), iced_layershell::Error> { /// Default log filter per build profile. Debug builds log barbar's own crates
let args = args::Args::parse(); /// at `debug` while dependency noise (iced_layershell/sctk/cosmic_text/wgpu…)
/// stays at `warn`; release builds log `warn` and above. `RUST_LOG` overrides.
const DEFAULT_FILTER: &str = if cfg!(debug_assertions) {
"warn,core=debug,common=debug,modules=debug,services=debug"
} else {
"warn"
};
let config_handle = File::open(args.config); fn main() -> Result<(), iced_layershell::Error> {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(DEFAULT_FILTER));
tracing_subscriber::fmt().with_env_filter(filter).init();
let args = args::Args::parse();
tracing::debug!(config = %args.config.display(), "parsed args");
let config_handle = File::open(&args.config);
let config: BarbarConfig; let config: BarbarConfig;
if let Ok(mut config_file) = config_handle { if let Ok(mut config_file) = config_handle {
let mut string = String::new(); let mut string = String::new();
config_file.read_to_string(&mut string).unwrap(); config_file.read_to_string(&mut string).unwrap();
config = toml::from_str(&string).unwrap(); config = toml::from_str(&string).unwrap();
tracing::info!(config = %args.config.display(), "config loaded");
} else { } else {
println!("cant open config file"); tracing::warn!(config = %args.config.display(), "cannot open config file");
return Ok(()); return Ok(());
} }
let start_mode = match config.monitor { let start_mode = match &config.monitor {
Some(x) => StartMode::TargetScreen(x), Some(x) => StartMode::TargetScreen(x.clone()),
None => StartMode::Active, None => StartMode::Active,
}; };
let modules = config.modules.clone().unwrap_or_default(); let modules = config.modules.clone().unwrap_or_default();
let order = config.order; let order = config.order;
tracing::info!(monitor = ?config.monitor, "starting barbar daemon");
daemon( daemon(
move || { move || {
+2
View File
@@ -48,6 +48,7 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<M
let (id, task) = popup_open(settings); let (id, task) = popup_open(settings);
bar.popup_id = Some(id); bar.popup_id = Some(id);
tracing::debug!(module = %module_id, ?id, size = ?(w, h), "popup opened");
task task
} }
@@ -55,6 +56,7 @@ pub fn open_popup(bar: &mut Bar, module_id: String, bounds: Rectangle) -> Task<M
/// `Msg::WindowClosed`, so popup content keeps rendering until then. /// `Msg::WindowClosed`, so popup content keeps rendering until then.
pub fn close_popup(bar: &mut Bar) -> Task<Message> { pub fn close_popup(bar: &mut Bar) -> Task<Message> {
if let Some(id) = bar.popup_id { if let Some(id) = bar.popup_id {
tracing::debug!(?id, "requesting popup close");
return Task::done(Message::Effect(ModuleEffect::ClosePopup(id))); return Task::done(Message::Effect(ModuleEffect::ClosePopup(id)));
} }
Task::none() Task::none()
+17 -4
View File
@@ -26,6 +26,7 @@ fn handle_event(bar: &mut Bar, event: Msg) -> Task<Message> {
if bar.popup_id == Some(id) { if bar.popup_id == Some(id) {
bar.popup_id = None; bar.popup_id = None;
bar.active_popup = None; bar.active_popup = None;
tracing::debug!(?id, "popup window closed");
} }
Task::none() Task::none()
} }
@@ -37,12 +38,19 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
match wire.to { match wire.to {
Target::Module(id) => match bar.modules.get_mut(id) { Target::Module(id) => match bar.modules.get_mut(id) {
Some(module) => module.update(wire).map(Message::Effect), Some(module) => module.update(wire).map(Message::Effect),
None => Task::none(), None => {
tracing::warn!(module = id, "wire routed to unknown module");
Task::none()
}
}, },
Target::Service(key) => { Target::Service(key) => {
if let Some(inbox) = bar.inputs.get(key) { if let Some(inbox) = bar.inputs.get(key) {
inbox.send(wire); if !inbox.send(wire) {
tracing::warn!(service = key, "service inbox closed");
}
} else {
tracing::warn!(service = key, "wire routed to unknown service");
} }
Task::none() Task::none()
} }
@@ -51,7 +59,8 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
// Fan out by route key; the payload is opaque here, each module // Fan out by route key; the payload is opaque here, each module
// downcasts it. New services never touch this file. // downcasts it. New services never touch this file.
let topic = Service(key); let topic = Service(key);
bar.modules let tasks: Vec<_> = bar
.modules
.iter_mut() .iter_mut()
.filter(|(id, _)| { .filter(|(id, _)| {
bar.routes bar.routes
@@ -59,7 +68,9 @@ fn route(bar: &mut Bar, wire: Wire) -> Task<Message> {
.is_some_and(|keys| keys.contains(&topic)) .is_some_and(|keys| keys.contains(&topic))
}) })
.map(|(_, module)| module.update(wire.clone()).map(Message::Effect)) .map(|(_, module)| module.update(wire.clone()).map(Message::Effect))
.fold(Task::none(), |acc, t| acc.chain(t)) .collect();
tracing::debug!(topic = key, subscribers = tasks.len(), "fan-out");
tasks.into_iter().fold(Task::none(), |acc, t| acc.chain(t))
} }
} }
} }
@@ -70,8 +81,10 @@ fn handle_effect(bar: &mut Bar, effect: ModuleEffect) -> Task<Message> {
ModuleEffect::RequestPopup(module_id, element_id) => { ModuleEffect::RequestPopup(module_id, element_id) => {
// Toggle: close if already open for this module, else open. // Toggle: close if already open for this module, else open.
if bar.active_popup.as_deref() == Some(module_id.as_str()) { if bar.active_popup.as_deref() == Some(module_id.as_str()) {
tracing::debug!(module = %module_id, "closing popup");
popup::close_popup(bar) popup::close_popup(bar)
} else { } else {
tracing::debug!(module = %module_id, "opening popup");
popup::capture_bounds(module_id, element_id) popup::capture_bounds(module_id, element_id)
} }
} }
+34 -6
View File
@@ -1,5 +1,5 @@
use common::Wire; use common::Wire;
use iced::widget::{container, row, text}; use iced::widget::{container, row, space, text};
use iced::{window, Alignment, Element, Length, Theme}; use iced::{window, Alignment, Element, Length, Theme};
use crate::app::Bar; use crate::app::Bar;
@@ -33,21 +33,49 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> {
.view(None) .view(None)
.map(|w| Message::Event(Msg::Wire(w))) .map(|w| Message::Event(Msg::Wire(w)))
}) })
.collect::<Vec<Element<Message>>>()); .collect::<Vec<Element<Message>>>())
.spacing(8);
let middle = row(bar let middle = row(bar
.order .order
.1 .1
.iter() .iter()
.map(|x| bar.modules.get(x.to_string()).unwrap().view(None))); .map(|x| {
bar.modules
.get(x.to_string())
.unwrap()
.view(None)
.map(|w| Message::Event(Msg::Wire(w)))
})
.collect::<Vec<Element<Message>>>())
.spacing(8);
let right = row(bar let right = row(bar
.order .order
.2 .2
.iter() .iter()
.map(|x| bar.modules.get(x.to_string()).unwrap().view(None))); .map(|x| {
bar.modules
.get(x.to_string())
.unwrap()
.view(None)
.map(|w| Message::Event(Msg::Wire(w)))
})
.collect::<Vec<Element<Message>>>())
.spacing(8);
container(left.width(Length::Fill).align_y(Alignment::Center)) let row = row![
left,
space::horizontal(),
middle,
space::horizontal(),
right,
]
.width(Length::Fill);
container(row)
.padding(8) .padding(8)
.align_x(Alignment::Center) .width(Length::Fill)
.align_y(Alignment::Center)
.into() .into()
} }
+2
View File
@@ -11,5 +11,7 @@ path = "src/lib.rs"
common = { path = "../common" } common = { path = "../common" }
iced = { workspace = true } iced = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
serde = {workspace = true} serde = {workspace = true}
+3 -2
View File
@@ -29,7 +29,7 @@ pub struct Clock {
#[derive(Deserialize, Default)] #[derive(Deserialize, Default)]
struct ClockConfig { struct ClockConfig {
#[serde(default)] #[serde(default)]
format: bool, show_seconds: bool,
} }
impl Clock { impl Clock {
@@ -37,7 +37,7 @@ impl Clock {
match config_clock(config) { match config_clock(config) {
Some(x) => Self { Some(x) => Self {
value: "--:--:--".to_string(), value: "--:--:--".to_string(),
show_seconds: x.format, show_seconds: x.show_seconds,
}, },
None => Self::default(), None => Self::default(),
} }
@@ -102,6 +102,7 @@ impl BarModule for Clock {
match msg.downcast::<ClockMsg>() { match msg.downcast::<ClockMsg>() {
Some(ClockMsg::ToggleSeconds) => { Some(ClockMsg::ToggleSeconds) => {
self.show_seconds = !self.show_seconds; self.show_seconds = !self.show_seconds;
tracing::debug!(show_seconds = self.show_seconds, "clock toggle");
let want = if self.show_seconds { let want = if self.show_seconds {
ClockKind::Seconds ClockKind::Seconds
} else { } else {
+54 -6
View File
@@ -1,6 +1,10 @@
use std::error::Error; use std::error::Error;
use common::{BarModule, Endpoint, ModuleEffect, Service, WeatherKind, WeatherMsg, Wire}; use chrono::Timelike;
use common::{
BarModule, ClockKind, ClockPayload, Endpoint, Hourly, ModuleEffect, Service, WeatherKind,
WeatherMsg, WeatherPayload, WeatherResponse, Wire,
};
use iced::{ use iced::{
widget::{container, mouse_area, text}, widget::{container, mouse_area, text},
Element, Task, Element, Task,
@@ -8,11 +12,37 @@ use iced::{
pub struct WeatherModule { pub struct WeatherModule {
text: String, text: String,
/// Cached hourly forecast from the last weather update.
hourly: Option<Hourly>,
/// Current hour, mirrored from the datetime service's clock ticks.
hour: Option<u32>,
}
impl WeatherModule {
/// Shows the forecast temperature for the tracked current hour.
fn refresh(&mut self) {
let (Some(hourly), Some(hour)) = (&self.hourly, self.hour) else {
return;
};
if let Some(i) = hourly.time.iter().position(|t| t.hour() == hour) {
if let Some(&temp) = hourly.temperature_2m.get(i) {
let value = format!("{}°", temp as i32);
if self.text != value {
self.text = value;
tracing::info!(hour, temp, "weather: current hour");
}
}
}
}
} }
impl Default for WeatherModule { impl Default for WeatherModule {
fn default() -> Self { fn default() -> Self {
Self { text: "hi".into() } Self {
text: "waiting ".into(),
hourly: None,
hour: None,
}
} }
} }
@@ -32,18 +62,36 @@ impl BarModule for WeatherModule {
} }
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> { fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
// Weather state: cache the hourly forecast, then show the current hour.
if let Some(env) = msg.downcast::<WeatherPayload>() {
match env.kind {
WeatherMsg::State => match env.payload.downcast_ref::<WeatherResponse>() {
Some(resp) => {
self.hourly = Some(resp.hourly.clone());
self.refresh();
}
None => tracing::warn!("weather payload was not a WeatherResponse"),
},
}
}
// Clock tick from the datetime service: track the current hour.
if let Some(payload) = msg.downcast::<ClockPayload>() {
self.hour = payload.value.split(':').next().and_then(|h| h.parse().ok());
self.refresh();
}
match msg.downcast::<WeatherKind>() { match msg.downcast::<WeatherKind>() {
Some(WeatherKind::Poke) => { Some(WeatherKind::Poke) => {
tracing::debug!("weather poked");
self.text = "poked".into(); self.text = "poked".into();
}
None => (),
}
Task::none() Task::none()
} }
None => Task::none(),
}
}
/// Route keys this module subscribes to. /// Route keys this module subscribes to.
fn services(&self) -> Vec<Service> { fn services(&self) -> Vec<Service> {
vec![WeatherMsg::State.key()] vec![WeatherMsg::State.key(), ClockKind::Seconds.key()]
} }
/// Optional: read `module.weather` from the config table. /// Optional: read `module.weather` from the config table.
+1
View File
@@ -11,6 +11,7 @@ path = "src/lib.rs"
common = { path = "../common" } common = { path = "../common" }
iced = { workspace = true } iced = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
+3
View File
@@ -39,12 +39,15 @@ impl ClockTicker {
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move { iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
let mut clock = ClockTicker::new(); let mut clock = ClockTicker::new();
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1)); let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
tracing::info!(key, "clock service started");
loop { loop {
tokio::select! { tokio::select! {
_ = interval.tick() => { _ = interval.tick() => {
if let Some(payload) = clock.tick() { if let Some(payload) = clock.tick() {
tracing::debug!(key, kind = ?payload.kind, "publishing clock");
let wire = Wire::topic(Endpoint::service(key), key, payload); let wire = Wire::topic(Endpoint::service(key), key, payload);
if sender.send(wire).await.is_err() { if sender.send(wire).await.is_err() {
tracing::warn!(key, "clock send failed; stopping service");
return; return;
} }
} }
+8 -4
View File
@@ -20,10 +20,14 @@ pub trait IntoSubscription {
impl IntoSubscription for Service { impl IntoSubscription for Service {
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> { fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
match self.0 { let key = self.0;
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox), match key {
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox), k if k.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
_ => Subscription::none(), k if k.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
_ => {
tracing::warn!(service = key, "no service impl for route key");
Subscription::none()
}
} }
} }
} }
+37 -16
View File
@@ -1,7 +1,10 @@
use std::time::Duration; use std::{sync::Arc, time::Duration};
use common::{Inbox, WeatherResponse, Wire}; use common::{Endpoint, Inbox, WeatherMsg, WeatherPayload, WeatherResponse, Wire};
use iced::{futures::channel::mpsc, Subscription}; use iced::{
futures::{channel::mpsc, SinkExt},
Subscription,
};
use tokio::time::interval; use tokio::time::interval;
pub const NAMESPACE: &str = "weather."; pub const NAMESPACE: &str = "weather.";
@@ -12,28 +15,37 @@ const FORECAST_URL: &str = "https://api.open-meteo.com/v1/forecast\
pub struct WeatherService { pub struct WeatherService {
client: reqwest::Client, client: reqwest::Client,
latest: Option<WeatherResponse>,
} }
impl WeatherService { impl WeatherService {
fn new() -> Self { fn new() -> Self {
Self { Self {
client: reqwest::Client::default(), client: reqwest::Client::default(),
latest: None,
} }
} }
pub fn run(inbox: Inbox) -> Subscription<Wire> { pub fn run(inbox: Inbox) -> Subscription<Wire> {
Subscription::run_with(inbox, |inbox| { Subscription::run_with(inbox, |inbox| {
let _key = inbox.key(); let key = inbox.key();
let mut _rx = inbox.take().expect("cant take"); let mut _rx = inbox.take().expect("cant take");
iced::stream::channel(0, move |mut _sender: mpsc::Sender<Wire>| async move { iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
let mut weather = WeatherService::new(); let mut weather = WeatherService::new();
let mut interval = interval(Duration::from_mins(30)); let mut interval = interval(Duration::from_mins(30));
tracing::info!(key = key, "weather service started");
loop { loop {
tokio::select! { tokio::select! {
_ = interval.tick() => { _ = interval.tick() => {
weather.latest = weather.get_weather().await; if let Some(resp) = weather.get_weather().await {
let env = WeatherPayload {
kind: WeatherMsg::State,
payload: Arc::new(resp),
};
let wire = Wire::topic(Endpoint::service(key), key, env);
if sender.send(wire).await.is_err() {
tracing::warn!(key, "weather send failed; stopping service");
return;
}
}
} }
} }
} }
@@ -41,13 +53,22 @@ impl WeatherService {
}) })
} }
async fn get_weather(&self) -> Option<WeatherResponse> { async fn get_weather(&self) -> Option<WeatherResponse> {
self.client tracing::debug!(url = FORECAST_URL, "fetching weather");
.get(FORECAST_URL) match self.client.get(FORECAST_URL).send().await {
.send() Ok(resp) => match resp.json::<WeatherResponse>().await {
.await Ok(parsed) => {
.ok()? tracing::debug!("weather fetched");
.json::<WeatherResponse>() Some(parsed)
.await }
.ok() Err(e) => {
tracing::warn!(error = %e, "weather response decode failed");
None
}
},
Err(e) => {
tracing::warn!(error = %e, "weather request failed");
None
}
}
} }
} }
+3 -2
View File
@@ -1,8 +1,9 @@
monitor = "DP-2" monitor = "DP-2"
[order] [order]
left = ["weather", "clock"] left = ["weather"]
middle = [] middle = []
right = ["clock"] right = ["clock"]
[modules.clock] [modules.clock]
format = true show_seconds = false