feat(downloader): implement etag-based difference check for schedule

This commit is contained in:
2025-09-25 03:14:39 +04:00
parent 6c71bc19f5
commit 3780fb3136
2 changed files with 32 additions and 16 deletions

View File

@@ -51,9 +51,9 @@ pub mod error {
/// Errors that may occur during the creation of a schedule snapshot. /// Errors that may occur during the creation of a schedule snapshot.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
pub enum SnapshotCreationError { pub enum SnapshotCreationError {
/// The URL is the same as the one already being used (no update needed). /// The ETag is the same (no update needed).
#[display("The URL is the same as the one already being used.")] #[display("The ETag is the same.")]
SameUrl, Same,
/// The URL query for the XLS file failed to execute, either due to network issues or invalid API parameters. /// The URL query for the XLS file failed to execute, either due to network issues or invalid API parameters.
#[display("Failed to fetch URL: {_0}")] #[display("Failed to fetch URL: {_0}")]
@@ -86,10 +86,6 @@ impl Updater {
downloader: &mut XlsDownloader, downloader: &mut XlsDownloader,
url: String, url: String,
) -> Result<ScheduleSnapshot, SnapshotCreationError> { ) -> Result<ScheduleSnapshot, SnapshotCreationError> {
if downloader.url.as_ref().is_some_and(|_url| _url.eq(&url)) {
return Err(SnapshotCreationError::SameUrl);
}
let head_result = downloader.set_url(&url).await.map_err(|error| { let head_result = downloader.set_url(&url).await.map_err(|error| {
if let FetchError::Unknown(error) = &error { if let FetchError::Unknown(error) = &error {
sentry::capture_error(&error); sentry::capture_error(&error);
@@ -98,6 +94,10 @@ impl Updater {
SnapshotCreationError::FetchFailed(error) SnapshotCreationError::FetchFailed(error)
})?; })?;
if downloader.etag == Some(head_result.etag) {
return Err(SnapshotCreationError::Same);
}
let xls_data = downloader let xls_data = downloader
.fetch(false) .fetch(false)
.await .await
@@ -249,7 +249,7 @@ impl Updater {
let snapshot = match Self::new_snapshot(&mut self.downloader, url).await { let snapshot = match Self::new_snapshot(&mut self.downloader, url).await {
Ok(snapshot) => snapshot, Ok(snapshot) => snapshot,
Err(SnapshotCreationError::SameUrl) => { Err(SnapshotCreationError::Same) => {
let mut clone = current_snapshot.clone(); let mut clone = current_snapshot.clone();
clone.update(); clone.update();

View File

@@ -66,25 +66,30 @@ pub struct FetchOk {
/// Date data received. /// Date data received.
pub requested_at: DateTime<Utc>, pub requested_at: DateTime<Utc>,
/// Etag.
pub etag: String,
/// File data. /// File data.
pub data: Option<Vec<u8>>, pub data: Option<Vec<u8>>,
} }
impl FetchOk { impl FetchOk {
/// Result without file content. /// Result without file content.
pub fn head(uploaded_at: DateTime<Utc>) -> Self { pub fn head(uploaded_at: DateTime<Utc>, etag: String) -> Self {
FetchOk { FetchOk {
uploaded_at, uploaded_at,
requested_at: Utc::now(), requested_at: Utc::now(),
etag,
data: None, data: None,
} }
} }
/// Full result. /// Full result.
pub fn get(uploaded_at: DateTime<Utc>, data: Vec<u8>) -> Self { pub fn get(uploaded_at: DateTime<Utc>, etag: String, data: Vec<u8>) -> Self {
FetchOk { FetchOk {
uploaded_at, uploaded_at,
requested_at: Utc::now(), requested_at: Utc::now(),
etag,
data: Some(data), data: Some(data),
} }
} }
@@ -94,11 +99,15 @@ pub type FetchResult = Result<FetchOk, FetchError>;
pub struct XlsDownloader { pub struct XlsDownloader {
pub url: Option<String>, pub url: Option<String>,
pub etag: Option<String>,
} }
impl XlsDownloader { impl XlsDownloader {
pub fn new() -> Self { pub fn new() -> Self {
XlsDownloader { url: None } XlsDownloader {
url: None,
etag: None,
}
} }
async fn fetch_specified(url: &str, head: bool) -> FetchResult { async fn fetch_specified(url: &str, head: bool) -> FetchResult {
@@ -124,9 +133,12 @@ impl XlsDownloader {
.get("Content-Type") .get("Content-Type")
.ok_or(FetchError::bad_headers("Content-Type"))?; .ok_or(FetchError::bad_headers("Content-Type"))?;
if !headers.contains_key("etag") { let etag = headers
return Err(FetchError::bad_headers("etag")); .get("etag")
} .ok_or(FetchError::bad_headers("etag"))?
.to_str()
.or(Err(FetchError::bad_headers("etag")))?
.to_string();
let last_modified = headers let last_modified = headers
.get("last-modified") .get("last-modified")
@@ -141,9 +153,13 @@ impl XlsDownloader {
.with_timezone(&Utc); .with_timezone(&Utc);
Ok(if head { Ok(if head {
FetchOk::head(last_modified) FetchOk::head(last_modified, etag)
} else { } else {
FetchOk::get(last_modified, response.bytes().await.unwrap().to_vec()) FetchOk::get(
last_modified,
etag,
response.bytes().await.unwrap().to_vec(),
)
}) })
} }