WIP: llm-based prototype #1
8 changed files with 113 additions and 17 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -142,6 +142,7 @@ dependencies = [
|
|||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ toml = "0.8"
|
|||
# Only keep one of blocking+async if all features converted
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls" ] }
|
||||
dirs = "5"
|
||||
chrono = "0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
serde_json = "1.0"
|
||||
chrono-tz = "0.8"
|
||||
tokio = { version = "1.37", features = ["full"] }
|
||||
|
|
52
src/cache.rs
Normal file
52
src/cache.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Cache<T> {
|
||||
pub timestamp: u64,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
fn get_cache_path(report_type: &str) -> PathBuf {
|
||||
let home = std::env::var("HOME").expect("HOME not set");
|
||||
let cache_dir = PathBuf::from(format!("{}/.local/state/trmnl", home));
|
||||
let _ = fs::create_dir_all(&cache_dir);
|
||||
cache_dir.join(format!("{}.json", report_type))
|
||||
}
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
pub fn load_cache<T: Serialize + DeserializeOwned>(
|
||||
report_type: &str,
|
||||
expiry_secs: u64,
|
||||
) -> Option<T> {
|
||||
let path = get_cache_path(report_type);
|
||||
let contents = fs::read_to_string(&path).ok()?;
|
||||
let parsed: Cache<T> = serde_json::from_str(&contents).ok()?;
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
if now - parsed.timestamp <= expiry_secs {
|
||||
println!("(using cached {} report)", report_type);
|
||||
Some(parsed.data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_cache<T: Serialize>(report_type: &str, data: &T) {
|
||||
let path = get_cache_path(report_type);
|
||||
let to_save = Cache {
|
||||
timestamp: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
data: data,
|
||||
};
|
||||
let json = serde_json::to_string_pretty(&to_save).unwrap();
|
||||
let _ = fs::write(path, json);
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
use chrono::{DateTime, FixedOffset, NaiveDate, Offset, TimeZone, Utc};
|
||||
use ical::IcalParser;
|
||||
use rrule::{RRule, RRuleSet, Unvalidated};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CalendarEventSummary {
|
||||
pub start: Option<DateTime<FixedOffset>>,
|
||||
pub summary: String,
|
||||
|
|
|
@ -5,6 +5,9 @@ use std::path::PathBuf;
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub cache_weather_secs: Option<u64>,
|
||||
pub cache_pollen_secs: Option<u64>,
|
||||
pub cache_calendar_secs: Option<u64>,
|
||||
pub weather_api_key: String,
|
||||
pub pollen_api_key: String,
|
||||
pub calendar_api_key: String,
|
||||
|
|
63
src/main.rs
63
src/main.rs
|
@ -1,3 +1,4 @@
|
|||
mod cache;
|
||||
mod calendar;
|
||||
mod config;
|
||||
mod pollen;
|
||||
|
@ -20,12 +21,21 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Weather - with cache
|
||||
let cache_weather_secs = config.cache_weather_secs.unwrap_or(300);
|
||||
let weather: Option<WeatherSummary> =
|
||||
match fetch_weather(&config.location, &config.weather_api_key).await {
|
||||
Ok(data) => Some(data),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to fetch weather: {e}");
|
||||
None
|
||||
if let Some(w) = cache::load_cache::<WeatherSummary>("weather", cache_weather_secs) {
|
||||
Some(w)
|
||||
} else {
|
||||
match fetch_weather(&config.location, &config.weather_api_key).await {
|
||||
Ok(data) => {
|
||||
cache::save_cache("weather", &data);
|
||||
Some(data)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to fetch weather: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
let (_lat, _lon) = if weather.is_some() {
|
||||
|
@ -66,7 +76,19 @@ async fn main() {
|
|||
println!("Weather: N/A");
|
||||
}
|
||||
|
||||
match fetch_pollen_api(&config.pollen_zip).await {
|
||||
// Pollen - with cache
|
||||
let cache_pollen_secs = config.cache_pollen_secs.unwrap_or(300);
|
||||
match if let Some(p) = cache::load_cache::<pollen::PollenSummary>("pollen", cache_pollen_secs) {
|
||||
Ok(p)
|
||||
} else {
|
||||
match fetch_pollen_api(&config.pollen_zip).await {
|
||||
Ok(data) => {
|
||||
cache::save_cache("pollen", &data);
|
||||
Ok(data)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} {
|
||||
Ok(p) => {
|
||||
if let Ok(dt) = DateTime::parse_from_rfc3339(&p.forecast_date) {
|
||||
match config.timezone.parse::<Tz>() {
|
||||
|
@ -110,16 +132,29 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("Upcoming calendar events:");
|
||||
// Calendar - with cache
|
||||
let cache_calendar_secs = config.cache_calendar_secs.unwrap_or(300);
|
||||
println!("\nUpcoming calendar events:");
|
||||
|
||||
match fetch_next_events(
|
||||
&config.calendar_url,
|
||||
config.calendar_event_count,
|
||||
&config.timezone,
|
||||
)
|
||||
.await
|
||||
match if let Some(events) =
|
||||
cache::load_cache::<Vec<calendar::CalendarEventSummary>>("calendar", cache_calendar_secs)
|
||||
{
|
||||
Ok(events)
|
||||
} else {
|
||||
match fetch_next_events(
|
||||
&config.calendar_url,
|
||||
config.calendar_event_count,
|
||||
&config.timezone,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(data) => {
|
||||
cache::save_cache("calendar", &data);
|
||||
Ok(data)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} {
|
||||
Ok(events) if !events.is_empty() => {
|
||||
for (i, event) in events.iter().enumerate() {
|
||||
let day = event
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
|
@ -34,11 +34,13 @@ struct Trigger {
|
|||
genus: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PollenPeriod {
|
||||
pub index: f32,
|
||||
pub triggers: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PollenSummary {
|
||||
pub location: String,
|
||||
pub forecast_date: String,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -72,6 +72,7 @@ pub struct OneCallResult {
|
|||
pub daily: Vec<WeatherDaily>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WeatherSummary {
|
||||
pub temp: String,
|
||||
pub current_desc: String,
|
||||
|
|
Loading…
Add table
Reference in a new issue