use crate::{Client, Error, IntoPackageIdent, IntoVersionIdent, Result, models::*};
use async_stream::try_stream;
use flate2::read::GzDecoder;
use futures_core::Stream;
use futures_util::{TryStreamExt, pin_mut};
use std::{fmt::Display, io::Read};
use tokio::sync::mpsc;
impl Client {
pub async fn get_metrics(
&self,
community: impl AsRef<str>,
package: impl IntoPackageIdent<'_>,
) -> Result<PackageMetrics> {
let url = self.v1_url(
community,
format_args!("/package-metrics/{}", package.into_id()?.path()),
);
self.get_json(url).await
}
pub async fn get_downloads(
&self,
community: impl AsRef<str>,
version: impl IntoVersionIdent<'_>,
) -> Result<u64> {
let url = self.v1_url(
community,
format_args!("/package-metrics/{}", version.into_id()?.path()),
);
let response: PackageVersionMetrics = self.get_json(url).await?;
Ok(response.downloads)
}
pub async fn list_packages_v1(&self, community: impl AsRef<str>) -> Result<Vec<PackageV1>> {
let url = self.v1_url(community, "/package/");
self.get_json(url).await
}
fn v1_url(&self, community: impl AsRef<str>, path: impl Display) -> String {
format!("{}/c/{}/api/v1{}", self.base_url, community.as_ref(), path)
}
pub async fn stream_packages_v1(
&self,
community: impl AsRef<str>,
) -> Result<impl Stream<Item = Result<PackageV1>>> {
let url = self.v1_url(community.as_ref(), "/package/");
let mut response = self.get(url).await?;
Ok(try_stream! {
let mut buffer = Vec::new();
let mut string = String::new();
let mut is_first = true;
while let Some(chunk) = response.chunk().await? {
buffer.extend_from_slice(&chunk);
let chunk = match std::str::from_utf8(&buffer) {
Ok(chunk) => chunk,
Err(_) => continue,
};
if is_first {
is_first = false;
string.extend(chunk.chars().skip(1)); } else {
string.push_str(chunk);
}
buffer.clear();
while let Some(index) = string.find("}]}") {
let (json, _) = string.split_at(index + 3);
yield serde_json::from_str::<PackageV1>(json)?;
string.replace_range(..index + 4, "");
}
}
debug_assert!(string.is_empty(), "remaining string: {string:?}");
debug_assert!(buffer.is_empty(), "remaining buffer: {buffer:?}");
})
}
pub async fn stream_package_index_v1<'a>(
&'a self,
community: impl AsRef<str>,
) -> Result<impl Stream<Item = Result<Vec<PackageV1>>> + 'a> {
let url = self.v1_url(community, "/package-listing-index/");
let encoded_urls = self.get(url).await?.bytes().await?;
let urls: Vec<String> = serde_json::from_reader(GzDecoder::new(&encoded_urls[..]))?;
let (tx, mut rx) = mpsc::channel(urls.len());
let this = self.clone();
let handle = tokio::spawn(async move {
for url in urls {
let bytes = this.get(url).await?.bytes().await?;
tx.send(bytes)
.await
.map_err(|_| Error::ChannelClosedTooEarly)?;
}
Ok::<_, Error>(())
});
Ok(try_stream! {
let mut buf = Vec::<u8>::new();
while let Some(bytes) = rx.recv().await {
GzDecoder::new(&bytes[..]).read_to_end(&mut buf)?;
let packages: Vec<PackageV1> = serde_json::from_slice(&buf)?;
buf.clear();
yield packages;
}
handle.await.expect("request task panicked")?;
})
}
pub async fn get_package_index_v1(&self, community: impl AsRef<str>) -> Result<Vec<PackageV1>> {
let stream = self.stream_package_index_v1(community).await?;
pin_mut!(stream);
let mut packages = Vec::new();
while let Some(package) = stream.try_next().await? {
packages.extend(package);
}
Ok(packages)
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::{TryStreamExt, pin_mut};
#[tokio::test]
async fn stream_packages_v1() -> Result<()> {
let client = Client::new();
let stream = client.stream_packages_v1("muck").await?;
pin_mut!(stream);
let mut count = 0;
while let Some(_) = stream.try_next().await? {
count += 1;
}
assert!(count > 0);
Ok(())
}
#[tokio::test]
async fn stream_package_listing_index() -> Result<()> {
let client = Client::new();
let stream = client.stream_package_index_v1("muck").await?;
pin_mut!(stream);
let mut count = 0;
while let Some(_) = stream.try_next().await? {
count += 1;
}
assert!(count > 0);
Ok(())
}
#[tokio::test]
async fn get_package_listing_index() -> Result<()> {
let client = Client::new();
let packages = client.get_package_index_v1("muck").await?;
assert!(packages.len() > 0);
println!("{packages:#?}");
Ok(())
}
}