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:
parent
bb2d0c910d
commit
a2bdf239e0
@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"owm_api25", "owm_widg_config",
|
||||
"owm_api25", "owm_widg_config", "weatherd",
|
||||
"widget",
|
||||
]
|
||||
|
@ -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,
|
||||
|
@ -1,3 +1,3 @@
|
||||
pub mod forecast;
|
||||
pub mod weather;
|
||||
pub mod query;
|
||||
pub mod weather;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use serde::Deserialize;
|
||||
use owm_api25::query::{QueryParams, Units};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Free25Query {
|
||||
@ -12,7 +12,7 @@ pub struct Free25Query {
|
||||
#[serde(default = "default_units")]
|
||||
pub units: String, // "metric", "imperial", "standard"
|
||||
#[serde(default = "default_lang")]
|
||||
pub lang: String, // default "en"
|
||||
pub lang: String, // default "en"
|
||||
}
|
||||
|
||||
fn default_units() -> String {
|
||||
|
@ -2,5 +2,6 @@ use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct General {
|
||||
pub api_version: String, // "free_2.5", "onecall_3.0", etc.
|
||||
pub api_version: String, // "free_2.5", "onecall_3.0", etc.
|
||||
pub cache_file: Option<String>, // Default: "~/.cache/candydesktop/owm_widget.json"
|
||||
}
|
||||
|
@ -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
13
weatherd/Cargo.toml
Normal 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
71
weatherd/src/main.rs
Normal 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user