Add daemon weatherd

- Add daemon `weatherd` to fetch and cache weather data
- Update API libraries to serialize to json
- Add "cache_file" parameter to "general" config
- Cargo `fmt` and `clippy`
This commit is contained in:
Candifloss 2025-10-07 15:01:11 +05:30
parent bb2d0c910d
commit a2bdf239e0
11 changed files with 105 additions and 20 deletions

View File

@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = [
"owm_api25", "owm_widg_config",
"owm_api25", "owm_widg_config", "weatherd",
"widget",
]

View File

@ -1,6 +1,6 @@
use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ForecastWeather {
pub id: u32,
pub main: String,
@ -8,14 +8,14 @@ pub struct ForecastWeather {
pub icon: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ForecastMain {
pub temp: f32,
pub pressure: f32,
pub humidity: u8,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ForecastEntry {
pub dt: u64,
pub main: ForecastMain,
@ -23,7 +23,7 @@ pub struct ForecastEntry {
pub dt_txt: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ForecastResponse {
pub cod: String,
pub cnt: u32,

View File

@ -1,3 +1,3 @@
pub mod forecast;
pub mod weather;
pub mod query;
pub mod weather;

View File

@ -22,7 +22,7 @@ impl fmt::Display for Units {
}
}
/// Query parameters for OpenWeatherMap Free API v2.5 endpoints
/// Query parameters for `OpenWeatherMap` Free API v2.5 endpoints
#[derive(Debug)]
pub struct QueryParams {
pub api_key: String,

View File

@ -1,27 +1,27 @@
use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct Weather {
pub main: String,
pub icon: String,
pub description: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct Main {
pub temp: f32,
pub pressure: u32,
pub humidity: u8,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct Sys {
pub country: String,
pub sunrise: u64,
pub sunset: u64,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct WeatherResponse {
pub name: String,
pub sys: Sys,

View File

@ -1,6 +1,6 @@
use serde::Deserialize;
use crate::general::General;
use crate::free25::Free25Query;
use crate::general::General;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Config {

View File

@ -1,5 +1,5 @@
use serde::Deserialize;
use owm_api25::query::{QueryParams, Units};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Free25Query {

View File

@ -3,4 +3,5 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct General {
pub api_version: String, // "free_2.5", "onecall_3.0", etc.
pub cache_file: Option<String>, // Default: "~/.cache/candydesktop/owm_widget.json"
}

View File

@ -1,3 +1,3 @@
pub mod general;
pub mod free25;
pub mod config;
pub mod free25;
pub mod general;

13
weatherd/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "weatherd"
version = "0.0.1"
edition = "2024"
[dependencies]
reqwest = {version = "0.12.23", features = ["blocking", "json"] }
toml = "0.9.7"
dirs = "6.0.0"
serde = { version = "1.0.225", features = ["derive"] }
serde_json = "1.0.145"
owm_api25 = { path = "../owm_api25" }
owm_widg_config = { path = "../owm_widg_config" }

71
weatherd/src/main.rs Normal file
View File

@ -0,0 +1,71 @@
use owm_api25::weather::WeatherResponse;
use owm_widg_config::config::Config;
use reqwest::blocking;
use std::error::Error;
use std::{fs, path::PathBuf, thread, time::Duration};
fn main() -> Result<(), Box<dyn Error>> {
println!("weatherd: starting daemon...");
loop {
if let Err(e) = fetch_and_cache() {
eprintln!("weatherd error: {e}");
}
// sleep 15 minutes between updates (customize later)
thread::sleep(Duration::from_secs(900));
}
}
fn fetch_and_cache() -> Result<(), Box<dyn Error>> {
// load config
let path = dirs::config_dir()
.ok_or("No config dir found")?
.join("candywidgets/openweathermap.toml");
let toml_str = fs::read_to_string(&path)
.map_err(|_| format!("Failed to read config: {}", path.display()))?;
// Deserialize whole config
let cfg: Config = toml::from_str(&toml_str)?;
match cfg.general.api_version.as_str() {
"free_2.5" => {
let query = owm_api25::query::QueryParams::from(cfg.query_params);
let url = query.weather_url()?;
let resp = blocking::get(&url)?.json::<WeatherResponse>()?;
let json_str = serde_json::to_string_pretty(&resp)?;
// resolve cache path
let cache_path = expand_tilde(
cfg.general
.cache_file
.unwrap_or_else(|| "~/.cache/candydesktop/owm_widget.json".into()),
);
if let Some(parent) = cache_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&cache_path, json_str)?;
println!("weatherd: updated {}", cache_path.display());
}
other => {
eprintln!("Unsupported api_version: {other}");
}
}
Ok(())
}
/// Expand `~` to `$HOME`
fn expand_tilde(path: String) -> PathBuf {
if let Some(stripped) = path.strip_prefix("~/")
&& let Some(home) = dirs::home_dir()
{
return home.join(stripped);
}
PathBuf::from(path)
}