Add feed item limit support
* The feed item limit defaults to the default page size (50) if not provided * Move caching from response to URL fetch results; add helper functions * Add a helper function to set the paging query of an URL * Modify paging so we don't retrieve more than the feed item limit
This commit is contained in:
parent
1bcc83bbfa
commit
485f605940
11
src/lib.rs
11
src/lib.rs
|
@ -92,10 +92,15 @@ pub(crate) async fn download(file: PathBuf, backend: &str) -> Result<Redirect> {
|
|||
}
|
||||
|
||||
/// Handler for retrieving the RSS feed of user on a certain back-end.
|
||||
#[get("/feed/<backend>/<username>")]
|
||||
async fn feed(backend: &str, username: &str, config: &State<Config>) -> Result<RssFeed> {
|
||||
#[get("/feed/<backend>/<username>?<limit>")]
|
||||
async fn feed(
|
||||
backend: &str,
|
||||
username: &str,
|
||||
limit: Option<usize>,
|
||||
config: &State<Config>,
|
||||
) -> Result<RssFeed> {
|
||||
let user = mixcloud::user(username).await?;
|
||||
let cloudcasts = mixcloud::cloudcasts(username).await?;
|
||||
let cloudcasts = mixcloud::cloudcasts(username, limit).await?;
|
||||
let mut last_build = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc);
|
||||
|
||||
let category = CategoryBuilder::default()
|
||||
|
|
|
@ -37,7 +37,7 @@ pub(crate) struct Pictures {
|
|||
}
|
||||
|
||||
/// The Mixcloud cloudcasts response.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub(crate) struct CloudcastsResponse {
|
||||
/// The contained cloudcast items.
|
||||
|
@ -49,7 +49,7 @@ pub(crate) struct CloudcastsResponse {
|
|||
}
|
||||
|
||||
/// The Mixcloud paging info.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub(crate) struct CloudcastsPaging {
|
||||
/// The API URL of the next page.
|
||||
|
@ -109,7 +109,7 @@ const DEFAULT_BITRATE: u32 = 64 * 1024;
|
|||
const DEFAULT_FILE_TYPE: &str = "audio/mpeg";
|
||||
|
||||
/// The default page size.
|
||||
const DEFAULT_PAGE_SIZE: &str = "50";
|
||||
const DEFAULT_PAGE_SIZE: usize = 50;
|
||||
|
||||
/// Returns the default file type used by Mixcloud.
|
||||
pub(crate) fn default_file_type() -> &'static str {
|
||||
|
@ -124,54 +124,95 @@ pub(crate) fn estimated_file_size(duration: u32) -> u32 {
|
|||
}
|
||||
|
||||
/// Retrieves the user data using the Mixcloud API.
|
||||
#[cached(
|
||||
key = "String",
|
||||
convert = r#"{ username.to_owned() }"#,
|
||||
time = 3600,
|
||||
result = true
|
||||
)]
|
||||
pub(crate) async fn user(username: &str) -> Result<User> {
|
||||
let mut url = Url::parse(API_BASE_URL).expect("URL can always be parsed");
|
||||
url.set_path(username);
|
||||
|
||||
println!("⏬ Retrieving user {username} from {url}...");
|
||||
fetch_user(url).await
|
||||
}
|
||||
|
||||
/// Fetches the user from the URL.
|
||||
#[cached(
|
||||
key = "String",
|
||||
convert = r#"{ url.to_string() }"#,
|
||||
time = 86400,
|
||||
result = true
|
||||
)]
|
||||
///
|
||||
/// If the result is [`Ok`], the user will be cached for 24 hours for the given username.
|
||||
async fn fetch_user(url: Url) -> Result<User> {
|
||||
let response = reqwest::get(url).await?.error_for_status()?;
|
||||
let user = response.json().await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
/// Retrieves the cloudcasts of the user using the Mixcloud API.
|
||||
#[cached(
|
||||
key = "String",
|
||||
convert = r#"{ username.to_owned() }"#,
|
||||
time = 3600,
|
||||
result = true
|
||||
)]
|
||||
pub(crate) async fn cloudcasts(username: &str) -> Result<Vec<Cloudcast>> {
|
||||
/// Retrieves the cloudcasts data of the user using the Mixcloud API.
|
||||
pub(crate) async fn cloudcasts(username: &str, limit: Option<usize>) -> Result<Vec<Cloudcast>> {
|
||||
let mut limit = limit.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||
let mut offset = 0;
|
||||
let mut url = Url::parse(API_BASE_URL).expect("URL can always be parsed");
|
||||
url.set_path(&format!("{username}/cloudcasts/"));
|
||||
url.query_pairs_mut()
|
||||
.append_pair("limit", DEFAULT_PAGE_SIZE)
|
||||
.append_pair("offset", "0");
|
||||
|
||||
println!("⏬ Retrieving cloudcasts of user {username} from {url}...");
|
||||
|
||||
set_paging_query(&mut url, limit, offset);
|
||||
let mut cloudcasts = Vec::with_capacity(50); // The initial limit
|
||||
loop {
|
||||
let response = reqwest::get(url).await?.error_for_status()?;
|
||||
let cloudcasts_res: CloudcastsResponse = response.json().await?;
|
||||
let cloudcasts_res: CloudcastsResponse = fetch_cloudcasts(url).await?;
|
||||
let count = cloudcasts_res.items.len();
|
||||
cloudcasts.extend(cloudcasts_res.items);
|
||||
|
||||
// Continue onto the next URL in the paging, if there is one.
|
||||
limit = limit.saturating_sub(count);
|
||||
offset += count;
|
||||
match cloudcasts_res.paging.next {
|
||||
Some(next_url) => url = Url::parse(&next_url)?,
|
||||
Some(next_url) => {
|
||||
url = Url::parse(&next_url)?;
|
||||
set_paging_query(&mut url, limit, offset);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
|
||||
// We have reached the limit.
|
||||
if limit == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cloudcasts)
|
||||
}
|
||||
|
||||
/// Fetches cloudcasts from the URL.
|
||||
///
|
||||
/// If the result is [`Ok`], the cloudcasts will be cached for 24 hours for the given username.
|
||||
#[cached(
|
||||
key = "String",
|
||||
convert = r#"{ url.to_string() }"#,
|
||||
time = 86400,
|
||||
result = true
|
||||
)]
|
||||
async fn fetch_cloudcasts(url: Url) -> Result<CloudcastsResponse> {
|
||||
let response = reqwest::get(url).await?.error_for_status()?;
|
||||
let cloudcasts_res = response.json().await?;
|
||||
|
||||
Ok(cloudcasts_res)
|
||||
}
|
||||
|
||||
/// Set paging query pairs for URL.
|
||||
///
|
||||
/// The limit is capped to the default page size. Another request will be necessary to retrieve
|
||||
/// more.
|
||||
fn set_paging_query(url: &mut Url, limit: usize, offset: usize) {
|
||||
url.query_pairs_mut()
|
||||
.clear()
|
||||
.append_pair(
|
||||
"limit",
|
||||
&format!("{}", std::cmp::min(limit, DEFAULT_PAGE_SIZE)),
|
||||
)
|
||||
.append_pair("offset", &format!("{}", offset));
|
||||
}
|
||||
|
||||
/// Retrieves the redirect URL for the provided Mixcloud cloudcast key.
|
||||
#[cached(
|
||||
key = "String",
|
||||
|
|
Loading…
Reference in New Issue