#![warn(missing_docs)]
#![warn(clippy::pedantic)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::expect_used)]
use std::{env, sync::Arc};
use anyhow::{Context, Result};
use chrono::Utc;
use dotenvy::dotenv;
use log::{error, info};
use tokio::{
task,
time::{self, Instant},
};
use backup::backup;
use config::parse_config;
use crate::prelude::*;
mod aws;
mod backup;
mod config;
mod prelude;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
dotenv().ok();
if env::var("RUST_LOG").is_ok() {
env_logger::init();
} else {
env_logger::Builder::new()
.parse_filters("awsbck=info,aws_config=warn,aws_credential_types=warn,tracing=warn")
.init();
}
let params = Arc::new(parse_config().await?);
if let Some(schedule) = ¶ms.schedule {
info!(
"Will backup \"{}\" on cron schedule: \"{schedule}\"",
params.folder.to_string_lossy()
);
let task = task::spawn({
let params = Arc::clone(¶ms);
async move {
loop {
let Some(deadline) = params.schedule.as_ref().or_panic().upcoming(Utc).next()
else {
error!("Could not get next execution time for cron schedule");
return;
};
info!("Next backup scheduled for {}", deadline.to_rfc2822());
let Ok(wait_time) = (deadline - Utc::now()).to_std() else {
error!("Could not convert duration to std Duration");
return;
};
let Some(deadline) = Instant::now().checked_add(wait_time) else {
error!("Could not convert deadline to tokio Instant");
return;
};
time::sleep_until(deadline).await;
match backup(¶ms).await {
Ok(()) => {
info!("Backup succeeded");
}
Err(e) => {
error!("Backup error: {e:#}");
}
}
}
}
});
let ctrl_c = tokio::spawn(async move {
tokio::signal::ctrl_c().await.or_panic();
});
tokio::select! {
_ = ctrl_c => {
info!("Ctrl-C received, exiting");
}
_ = task => {
info!("Backup task exited, exiting");
}
}
} else {
info!("Backing up \"{}\" once", params.folder.to_string_lossy());
backup(¶ms).await.context("Backup error")?;
info!("Backup succeeded");
}
Ok(())
}