use std::cell::RefCell;
use std::time::{Duration, Instant};
use crate::api::ApiClient;
pub trait RemoteEndpoint {
fn get_remote_url(&self) -> crate::Result<String>;
}
pub struct RemoteJobStorageEndpoint {
job_uuid: String,
job_auth_token: String,
storage_type: String,
presigned_url: RefCell<Option<String>>,
fetched_at: RefCell<Option<Instant>>,
}
const PRESIGNED_URL_TTL: Duration = Duration::from_secs(8 * 60);
impl RemoteJobStorageEndpoint {
pub fn new(job_uuid: String, job_auth_token: String, storage_type: String) -> Self {
Self {
job_uuid,
job_auth_token,
storage_type,
presigned_url: RefCell::new(None),
fetched_at: RefCell::new(None),
}
}
}
impl RemoteEndpoint for RemoteJobStorageEndpoint {
fn get_remote_url(&self) -> crate::Result<String> {
let needs_refresh = {
let url = self.presigned_url.borrow();
let fetched = self.fetched_at.borrow();
url.is_none() || fetched.is_none_or(|t| t.elapsed() > PRESIGNED_URL_TTL)
};
if needs_refresh {
let config = crate::Config::load();
let mut client = ApiClient::new(&config)?;
let mut last_err = None;
for attempt in 0..3 {
match client.get_job_storage_download_url(
&self.job_uuid,
&self.job_auth_token,
&self.storage_type,
) {
Ok(url) => {
crate::logging::debug(&format!(
"Job \"{}\" fetched presigned URL (TTL {}s)",
self.job_uuid,
PRESIGNED_URL_TTL.as_secs()
));
*self.presigned_url.borrow_mut() = Some(url);
*self.fetched_at.borrow_mut() = Some(Instant::now());
last_err = None;
break;
}
Err(err) => {
crate::logging::warning(&format!(
"Failed to fetch presigned URL for job \"{}\" (attempt {}): {}",
self.job_uuid,
attempt + 1,
err
));
last_err = Some(err);
std::thread::sleep(Duration::from_secs(3 * (attempt + 1)));
}
}
}
if let Some(err) = last_err {
return Err(err);
}
}
Ok(self.presigned_url.borrow().clone().unwrap())
}
}