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]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"owm_api25", "owm_widg_config",
|
"owm_api25", "owm_widg_config", "weatherd",
|
||||||
"widget",
|
"widget",
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ForecastWeather {
|
pub struct ForecastWeather {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub main: String,
|
pub main: String,
|
||||||
@ -8,14 +8,14 @@ pub struct ForecastWeather {
|
|||||||
pub icon: String,
|
pub icon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ForecastMain {
|
pub struct ForecastMain {
|
||||||
pub temp: f32,
|
pub temp: f32,
|
||||||
pub pressure: f32,
|
pub pressure: f32,
|
||||||
pub humidity: u8,
|
pub humidity: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ForecastEntry {
|
pub struct ForecastEntry {
|
||||||
pub dt: u64,
|
pub dt: u64,
|
||||||
pub main: ForecastMain,
|
pub main: ForecastMain,
|
||||||
@ -23,7 +23,7 @@ pub struct ForecastEntry {
|
|||||||
pub dt_txt: String,
|
pub dt_txt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ForecastResponse {
|
pub struct ForecastResponse {
|
||||||
pub cod: String,
|
pub cod: String,
|
||||||
pub cnt: u32,
|
pub cnt: u32,
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
pub mod forecast;
|
pub mod forecast;
|
||||||
pub mod weather;
|
|
||||||
pub mod query;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct QueryParams {
|
pub struct QueryParams {
|
||||||
pub api_key: String,
|
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 struct Weather {
|
||||||
pub main: String,
|
pub main: String,
|
||||||
pub icon: String,
|
pub icon: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Main {
|
pub struct Main {
|
||||||
pub temp: f32,
|
pub temp: f32,
|
||||||
pub pressure: u32,
|
pub pressure: u32,
|
||||||
pub humidity: u8,
|
pub humidity: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Sys {
|
pub struct Sys {
|
||||||
pub country: String,
|
pub country: String,
|
||||||
pub sunrise: u64,
|
pub sunrise: u64,
|
||||||
pub sunset: u64,
|
pub sunset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct WeatherResponse {
|
pub struct WeatherResponse {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sys: Sys,
|
pub sys: Sys,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
use crate::general::General;
|
|
||||||
use crate::free25::Free25Query;
|
use crate::free25::Free25Query;
|
||||||
|
use crate::general::General;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
use owm_api25::query::{QueryParams, Units};
|
use owm_api25::query::{QueryParams, Units};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Free25Query {
|
pub struct Free25Query {
|
||||||
@ -12,7 +12,7 @@ pub struct Free25Query {
|
|||||||
#[serde(default = "default_units")]
|
#[serde(default = "default_units")]
|
||||||
pub units: String, // "metric", "imperial", "standard"
|
pub units: String, // "metric", "imperial", "standard"
|
||||||
#[serde(default = "default_lang")]
|
#[serde(default = "default_lang")]
|
||||||
pub lang: String, // default "en"
|
pub lang: String, // default "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_units() -> String {
|
fn default_units() -> String {
|
||||||
|
@ -2,5 +2,6 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct General {
|
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 config;
|
||||||
pub mod free25;
|
pub mod free25;
|
||||||
pub mod config;
|
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