Retrieve all pages by following the next URL

* Derserialize the paging information
* Parse each next URL; handle URL parse errors
* Use a default page size of 50; pass offset 0 to count by item index
This commit is contained in:
Paul van Tilburg 2022-05-26 22:06:22 +02:00
parent 09ee0b9ba9
commit 78fc93fedf
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
4 changed files with 41 additions and 8 deletions

1
Cargo.lock generated
View File

@ -1598,6 +1598,7 @@ dependencies = [
"rocket_dyn_templates",
"rss",
"thiserror",
"url",
"youtube_dl",
]

View File

@ -15,6 +15,7 @@ rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
rss = "2.0.1"
thiserror = "1.0.31"
url = "2.2.2"
youtube_dl = { version = "0.7.0", features = ["tokio"] }
[package.metadata.deb]

View File

@ -43,6 +43,9 @@ pub(crate) enum Error {
#[error("Unknown supported back-end: {0}")]
UnsupportedBackend(String),
#[error("URL parse error: {0}")]
UrlParse(#[from] url::ParseError),
#[error("Youtube_dl failed: {0}")]
YoutubeDl(#[from] youtube_dl::Error),
}

View File

@ -11,7 +11,7 @@ use youtube_dl::{YoutubeDl, YoutubeDlOutput};
use super::{Error, Result};
/// A Mixcloud user.
/// A Mixcloud user (response).
#[derive(Clone, Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub(crate) struct User {
@ -36,12 +36,24 @@ pub(crate) struct Pictures {
pub(crate) large: String,
}
/// The Mixcloud cloudcasts container.
/// The Mixcloud cloudcasts response.
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub(crate) struct CloudcastData {
/// The contained cloudcasts.
data: Vec<Cloudcast>,
pub(crate) struct CloudcastsResponse {
/// The contained cloudcast items.
#[serde(rename = "data")]
items: Vec<Cloudcast>,
/// The paging information.
paging: CloudcastsPaging,
}
/// The Mixcloud paging info.
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub(crate) struct CloudcastsPaging {
/// The API URL of the next page.
next: Option<String>,
}
/// A Mixcloud cloudcast.
@ -96,6 +108,9 @@ const DEFAULT_BITRATE: u32 = 64 * 1024;
/// The default file (MIME) type used by Mixcloud.
const DEFAULT_FILE_TYPE: &str = "audio/mpeg";
/// The default page size.
const DEFAULT_PAGE_SIZE: &str = "50";
/// Returns the default file type used by Mixcloud.
pub(crate) const fn default_file_type() -> &'static str {
DEFAULT_FILE_TYPE
@ -136,12 +151,25 @@ pub(crate) async fn user(username: &str) -> Result<User> {
pub(crate) async fn cloudcasts(username: &str) -> Result<Vec<Cloudcast>> {
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}...");
let response = reqwest::get(url).await?.error_for_status()?;
let cloudcasts: CloudcastData = response.json().await?;
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?;
cloudcasts.extend(cloudcasts_res.items);
Ok(cloudcasts.data)
// Continue onto the next URL in the paging, if there is one.
match cloudcasts_res.paging.next {
Some(next_url) => url = Url::parse(&next_url)?,
None => break,
}
}
Ok(cloudcasts)
}
/// Retrieves the redirect URL for the provided Mixcloud cloudcast key.