checkpoint
This commit is contained in:
parent
27470083dc
commit
3b79aa21d2
3 changed files with 56 additions and 15 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -148,6 +148,15 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.32"
|
||||
|
@ -1860,6 +1869,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"camino",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"clap",
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
|||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
camino = { version = "1.1.11", features = ["serde1"] }
|
||||
chrono = "0.4.41"
|
||||
chrono-tz = { version = "0.10.4", features = ["serde"] }
|
||||
clap = { version = "4.5.43", features = ["derive"] }
|
||||
|
|
60
src/main.rs
60
src/main.rs
|
@ -1,5 +1,6 @@
|
|||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use base64::Engine as _;
|
||||
use camino::Utf8PathBuf;
|
||||
use chrono::{DateTime, TimeZone as _, Utc};
|
||||
use clap::Parser as _;
|
||||
use icalendar::{Component as _, EventLike as _};
|
||||
|
@ -7,26 +8,49 @@ use serde::Deserialize;
|
|||
use std::{
|
||||
collections::BTreeSet, io::Write as _, path::PathBuf, str::FromStr as _, time::Duration,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct Downloadable {
|
||||
/// URL to scrape to download the JSON
|
||||
download_url: Option<Url>,
|
||||
|
||||
/// Disk location to cache the JSON file for debugging
|
||||
file_path: Utf8PathBuf,
|
||||
}
|
||||
|
||||
/// The Sierra Club uses a calendar software called Campfire, which luckily puts out nice JSON files
|
||||
///
|
||||
/// It's also deterministic, which is lovely. You get the same JSON byte-for-byte unless the events changed
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct ConfigCampfire {
|
||||
#[serde(flatten)]
|
||||
dl: Downloadable,
|
||||
|
||||
/// A canonical webpage we can direct users to
|
||||
html_url: Option<Url>,
|
||||
|
||||
/// Very short name for putting on each event
|
||||
short_name: String,
|
||||
}
|
||||
|
||||
/// Google Calendar has a public ics endpoint that we scrape for all upstream Google Calendars
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct ConfigIcal {
|
||||
/// Disk location to cache the ics file for debugging
|
||||
file_path: PathBuf,
|
||||
#[serde(flatten)]
|
||||
dl: Downloadable,
|
||||
|
||||
/// Magical ID we pass to Google to deep-link to Google Calendar events
|
||||
google_id: Option<String>,
|
||||
|
||||
/// A canonical webpage we can direct users to
|
||||
html_url: Option<url::Url>,
|
||||
html_url: Option<Url>,
|
||||
|
||||
/// Very short name for putting on each event
|
||||
short_name: String,
|
||||
|
||||
/// URL to scrape to download the ics file
|
||||
ics_url: Option<url::Url>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -50,6 +74,7 @@ struct ConfigOutput {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
campfires: Vec<ConfigCampfire>,
|
||||
icals: Vec<ConfigIcal>,
|
||||
output: ConfigOutput,
|
||||
}
|
||||
|
@ -315,8 +340,8 @@ impl ICal {
|
|||
Ok(cal)
|
||||
}
|
||||
|
||||
fn read_from_config(config: &ConfigIcal) -> Result<Self> {
|
||||
let s = std::fs::read_to_string(&config.file_path)?;
|
||||
fn read_from_downloadable(dl: &Downloadable) -> Result<Self> {
|
||||
let s = std::fs::read_to_string(&dl.file_path)?;
|
||||
Self::read_from_str(&s)
|
||||
}
|
||||
|
||||
|
@ -399,7 +424,7 @@ struct Data {
|
|||
fn read_data_from_disk(config: &Config) -> Result<Data> {
|
||||
let mut data = Data::default();
|
||||
for config in &config.icals {
|
||||
let cal = ICal::read_from_config(config)?;
|
||||
let cal = ICal::read_from_downloadable(&config.dl)?;
|
||||
data.icals.push((cal, config.clone()));
|
||||
}
|
||||
|
||||
|
@ -628,20 +653,25 @@ async fn do_everything(cli: &CliAuto) -> Result<()> {
|
|||
let client = reqwest::Client::builder()
|
||||
.user_agent(APP_USER_AGENT)
|
||||
.build()?;
|
||||
for ical in &config.icals {
|
||||
let Some(ics_url) = &ical.ics_url else {
|
||||
for dl in config
|
||||
.campfires
|
||||
.iter()
|
||||
.map(|cf| &cf.dl)
|
||||
.chain(config.icals.iter().map(|ical| &ical.dl))
|
||||
{
|
||||
let Some(download_url) = &dl.download_url else {
|
||||
continue;
|
||||
};
|
||||
tracing::info!(url = ics_url.to_string(), "requesting...");
|
||||
let resp = client.get(ics_url.clone()).send().await?;
|
||||
tracing::info!(url = download_url.to_string(), "requesting...");
|
||||
let resp = client.get(download_url.clone()).send().await?;
|
||||
if resp.status() != 200 {
|
||||
bail!("Bad status {}", resp.status());
|
||||
}
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
let temp_path = ical.file_path.with_extension(".ics.temp");
|
||||
let temp_path = dl.file_path.with_extension(".ics.temp");
|
||||
std::fs::write(&temp_path, &bytes)?;
|
||||
std::fs::rename(&temp_path, &ical.file_path)?;
|
||||
std::fs::rename(&temp_path, &dl.file_path)?;
|
||||
}
|
||||
|
||||
let data = read_data_from_disk(&config)?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue