use bon::bon;
use futures::stream::Stream;
use reqwest::Url;
use super::files::{extract_commit_hash, extract_etag, extract_file_size, extract_xet_hash};
use super::{FileMetadataInfo, HFRepository, RepoTreeEntry, RepoType};
use crate::client::encode_ref;
use crate::error::{HFError, HFResult};
use crate::{constants, retry};
#[bon]
impl<T: RepoType> HFRepository<T> {
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_tree(
&self,
#[builder(into)]
revision: Option<String>,
#[builder(into)]
path_in_repo: Option<String>,
#[builder(default)]
recursive: bool,
#[builder(default)]
expand: bool,
limit: Option<usize>,
) -> HFResult<impl Stream<Item = HFResult<RepoTreeEntry>> + '_> {
let revision = revision.as_deref().unwrap_or(constants::DEFAULT_REVISION);
let url_str = format!(
"{}/tree/{}",
self.hf_client.api_url(self.repo_type.plural(), &self.repo_path()),
encode_ref(revision)
);
let mut url = Url::parse(&url_str)?;
if let Some(path) = path_in_repo.as_deref() {
crate::client::append_path_segments(&mut url, path)?;
}
let mut query: Vec<(String, String)> = Vec::new();
if recursive {
query.push(("recursive".into(), "true".into()));
}
if expand {
query.push(("expand".into(), "true".into()));
}
Ok(self.hf_client.paginate(url, query, limit))
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub async fn get_paths_info(
&self,
paths: Vec<String>,
#[builder(into)]
revision: Option<String>,
) -> HFResult<Vec<RepoTreeEntry>> {
let revision = revision.as_deref().unwrap_or(constants::DEFAULT_REVISION);
let url = format!(
"{}/paths-info/{}",
self.hf_client.api_url(self.repo_type.plural(), &self.repo_path()),
encode_ref(revision)
);
let body = serde_json::json!({ "paths": paths });
let headers = self.hf_client.auth_headers();
let response = retry::retry(self.hf_client.retry_config(), || {
self.hf_client
.http_client()
.post(&url)
.headers(headers.clone())
.json(&body)
.send()
})
.await?;
let repo_path = self.repo_path();
let response = self
.hf_client
.check_response(response, Some(&repo_path), crate::error::NotFoundContext::Entry { path: paths.join(", ") })
.await?;
Ok(response.json().await?)
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub async fn get_file_metadata(
&self,
#[builder(into)]
filepath: String,
revision: Option<&str>,
) -> HFResult<FileMetadataInfo> {
let filename = filepath;
let revision = revision.unwrap_or(constants::DEFAULT_REVISION);
let repo_path = self.repo_path();
let url = self
.hf_client
.download_url(self.repo_type.url_prefix(), &repo_path, revision, &filename)?;
let headers = self.hf_client.auth_headers();
let response = retry::retry(self.hf_client.retry_config(), || {
self.hf_client.http_client().head(&url).headers(headers.clone()).send()
})
.await?;
let response = self
.hf_client
.check_response(response, Some(&repo_path), crate::error::NotFoundContext::Entry { path: filename.clone() })
.await?;
let etag = extract_etag(&response).ok_or_else(|| {
HFError::malformed_response_at(format!("missing ETag header for {filename}"), url.to_string())
})?;
let commit_hash = extract_commit_hash(&response).ok_or_else(|| {
HFError::malformed_response_at(format!("missing X-Repo-Commit header for {filename}"), url.to_string())
})?;
let xet_hash = extract_xet_hash(&response);
let file_size = extract_file_size(&response).unwrap_or_else(|| {
tracing::warn!(
file = %filename,
"missing or invalid Content-Length/X-Linked-Size header, defaulting file size to 0"
);
0
});
let location = Some(response.url().to_string());
Ok(FileMetadataInfo {
filename,
etag,
commit_hash,
xet_hash,
file_size,
location,
})
}
}
#[cfg(feature = "blocking")]
use futures::stream::StreamExt as _;
#[cfg(feature = "blocking")]
#[bon]
impl<T: RepoType> crate::blocking::HFRepositorySync<T> {
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn list_tree(
&self,
#[builder(into)] revision: Option<String>,
#[builder(into)] path_in_repo: Option<String>,
#[builder(default)] recursive: bool,
#[builder(default)] expand: bool,
limit: Option<usize>,
) -> HFResult<Vec<RepoTreeEntry>> {
self.runtime.block_on(async move {
let stream = self
.inner
.list_tree()
.maybe_revision(revision)
.maybe_path_in_repo(path_in_repo)
.recursive(recursive)
.expand(expand)
.maybe_limit(limit)
.send()?;
futures::pin_mut!(stream);
let mut items = Vec::new();
while let Some(item) = stream.next().await {
items.push(item?);
}
Ok(items)
})
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn get_paths_info(
&self,
paths: Vec<String>,
#[builder(into)] revision: Option<String>,
) -> HFResult<Vec<RepoTreeEntry>> {
self.runtime
.block_on(self.inner.get_paths_info().paths(paths).maybe_revision(revision).send())
}
#[builder(finish_fn = send, derive(Debug, Clone))]
pub fn get_file_metadata(
&self,
#[builder(into)] filepath: String,
revision: Option<&str>,
) -> HFResult<FileMetadataInfo> {
self.runtime.block_on(
self.inner
.get_file_metadata()
.filepath(filepath)
.maybe_revision(revision)
.send(),
)
}
}