deal with not-modified responses

This commit is contained in:
Robin Appelman 2024-12-26 01:16:44 +01:00
commit c325eaba62
4 changed files with 33 additions and 15 deletions

View file

@ -4,6 +4,7 @@ version = "0.2.0"
authors = ["Robin Appelman <robin@icewind.nl>"] authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018" edition = "2018"
rust-version = "1.71.1" rust-version = "1.71.1"
repository = "https://github.com/icewind1991/rss-webhook-trigger"
license = "MIT" license = "MIT"
[dependencies] [dependencies]

View file

@ -84,8 +84,8 @@ pub struct FetchPlan {
} }
impl FetchPlan { impl FetchPlan {
pub fn elapsed(&self) -> bool { pub fn is_elapsed(&self) -> bool {
Instant::now() > self.time Instant::now() >= self.time
} }
} }

View file

@ -1,6 +1,6 @@
use crate::error::HubError; use crate::error::HubError;
use crate::fetcher::{CacheHeaders, FetchResponse}; use crate::fetcher::{CacheHeaders, FetchResponse};
use reqwest::Client; use reqwest::{Client, StatusCode};
use reqwest::header::{HeaderValue, USER_AGENT}; use reqwest::header::{HeaderValue, USER_AGENT};
use serde::Deserialize; use serde::Deserialize;
use time::OffsetDateTime; use time::OffsetDateTime;
@ -15,10 +15,10 @@ pub async fn tags(
cache_headers: &CacheHeaders, cache_headers: &CacheHeaders,
) -> FetchResponse<Vec<HubTag>, HubError> { ) -> FetchResponse<Vec<HubTag>, HubError> {
let result = client let result = client
.get(format!( .get(dbg!(format!(
"https://hub.docker.com/v2/repositories/{}/{}/tags", "https://hub.docker.com/v2/repositories/{}/{}/tags",
user, repo user, repo
)) )))
.headers(cache_headers.headers()) .headers(cache_headers.headers())
.header(USER_AGENT, HeaderValue::from_static(FETCHER_USER_AGENT)) .header(USER_AGENT, HeaderValue::from_static(FETCHER_USER_AGENT))
.send() .send()
@ -28,6 +28,9 @@ pub async fn tags(
.map_err(HubError::Network) .map_err(HubError::Network)
.check_status_code(HubError::ClientError, HubError::ServerError) .check_status_code(HubError::ClientError, HubError::ServerError)
.map(|response| async { .map(|response| async {
if response.status() == StatusCode::NOT_MODIFIED {
return Ok(Vec::new());
}
response response
.text() .text()
.await .await

View file

@ -7,7 +7,7 @@ use crate::config::{Config, FeedConfig};
use crate::error::{FetchError, FetchFeedError, HubError, ParseFeedError}; use crate::error::{FetchError, FetchFeedError, HubError, ParseFeedError};
use crate::fetcher::{next_fetch, CacheHeaders, FetchPlan, FetchResponse}; use crate::fetcher::{next_fetch, CacheHeaders, FetchPlan, FetchResponse};
use main_error::MainResult; use main_error::MainResult;
use reqwest::{Client, Response}; use reqwest::{Client, Response, StatusCode};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap; use std::collections::HashMap;
use std::future::ready; use std::future::ready;
@ -106,7 +106,10 @@ impl FeedFetcher {
} }
pub fn should_update(&self, feed: &str) -> bool { pub fn should_update(&self, feed: &str) -> bool {
self.fetch_plans.get(feed).filter(|plan| FetchPlan::elapsed(plan)).is_some() match self.fetch_plans.get(feed) {
Some(plan) => plan.is_elapsed(),
None => true,
}
} }
#[instrument(skip(self))] #[instrument(skip(self))]
@ -130,7 +133,7 @@ impl FeedFetcher {
}; };
Ok(match (self.cache.get_mut(feed), new_key) { Ok(match (self.cache.get_mut(feed), new_key) {
(Some(cached), Some(new_key)) => { (Some(cached), Some(Some(new_key))) => {
debug!(cached, new_key, "checked existing feed"); debug!(cached, new_key, "checked existing feed");
if new_key != *cached { if new_key != *cached {
*cached = new_key; *cached = new_key;
@ -139,13 +142,17 @@ impl FeedFetcher {
false false
} }
} }
(None, Some(new_key)) => { (None, Some(Some(new_key))) => {
debug!(feed, "new feed"); debug!(feed, "new feed");
self.cache.insert(feed.into(), new_key); self.cache.insert(feed.into(), new_key);
// don't trigger the actions on start // don't trigger the actions on start
false false
} }
(_, Some(None)) => {
debug!("not modified response");
false
}
(_, None) => { (_, None) => {
warn!("rate limited by server"); warn!("rate limited by server");
false false
@ -158,18 +165,21 @@ impl FeedFetcher {
&self, &self,
feed: &str, feed: &str,
cache_headers: &CacheHeaders, cache_headers: &CacheHeaders,
) -> FetchResponse<u64, FetchError> { ) -> FetchResponse<Option<u64>, FetchError> {
if let Some(hub) = feed.strip_prefix("docker-hub://") { if let Some(hub) = feed.strip_prefix("docker-hub://") {
if let Some((user, repo)) = hub.split_once('/') { if let Some((user, repo)) = hub.split_once('/') {
hub::tags(&self.client, user, repo, cache_headers) hub::tags(&self.client, user, repo, cache_headers)
.await .await
.map(|tags| { .map(|tags| {
if tags.is_empty() {
return ready(None);
}
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
for tag in tags { for tag in tags {
tag.id.hash(&mut hasher); tag.id.hash(&mut hasher);
tag.last_updated.hash(&mut hasher); tag.last_updated.hash(&mut hasher);
} }
ready(hasher.finish()) ready(Some(hasher.finish()))
}).await }).await
.map_err(FetchError::Hub) .map_err(FetchError::Hub)
} else { } else {
@ -190,7 +200,7 @@ impl FeedFetcher {
&self, &self,
feed: &str, feed: &str,
cache_headers: &CacheHeaders, cache_headers: &CacheHeaders,
) -> FetchResponse<u64, FetchFeedError> { ) -> FetchResponse<Option<u64>, FetchFeedError> {
let response = self let response = self
.client .client
.get(feed) .get(feed)
@ -209,7 +219,11 @@ impl FeedFetcher {
} }
} }
async fn parse_rss_response(response: Response) -> Result<u64, FetchFeedError> { async fn parse_rss_response(response: Response) -> Result<Option<u64>, FetchFeedError> {
if response.status() == StatusCode::NOT_MODIFIED {
return Ok(None);
}
let content = response.text().await?; let content = response.text().await?;
let channel = Feed::from_str(&content).map_err(ParseFeedError::Parse)?; let channel = Feed::from_str(&content).map_err(ParseFeedError::Parse)?;
@ -235,5 +249,5 @@ async fn parse_rss_response(response: Response) -> Result<u64, FetchFeedError> {
} }
} }
Ok(hasher.finish()) Ok(Some(hasher.finish()))
} }