use reqwest::Method;
use url::Url;
use super::core::{PublicStorage, SessionStorage, dir_trailing_slash_error};
use crate::actors::storage::resource::{
IntoPubkyResource, IntoResourcePath, PubkyResource, ResourcePath,
};
use crate::util::check_http_status;
use crate::{Result, cross_log};
impl SessionStorage {
pub fn list<P: IntoResourcePath>(&self, path: P) -> Result<ListBuilder<'_>> {
let path: ResourcePath = path.into_abs_path()?;
if !path.as_str().ends_with('/') {
return Err(dir_trailing_slash_error().into());
}
let resource = PubkyResource::new(self.user.clone(), path.as_str())?;
let url = resource.to_transport_url()?;
Ok(ListBuilder::session(self, url))
}
}
impl PublicStorage {
pub fn list<A: IntoPubkyResource>(&self, addr: A) -> Result<ListBuilder<'_>> {
let resource: PubkyResource = addr.into_pubky_resource()?;
if !resource.path.as_str().ends_with('/') {
return Err(dir_trailing_slash_error().into());
}
let url = resource.to_transport_url()?;
Ok(ListBuilder::public(self, url))
}
}
#[derive(Debug)]
enum ListScope<'a> {
Session(&'a SessionStorage),
Public(&'a PublicStorage),
}
#[derive(Debug)]
#[must_use]
pub struct ListBuilder<'a> {
scope: ListScope<'a>,
url: Url,
reverse: bool,
shallow: bool,
limit: Option<u16>,
cursor: Option<String>,
}
impl<'a> ListBuilder<'a> {
#[inline]
const fn new(scope: ListScope<'a>, url: Url) -> Self {
Self {
scope,
url,
reverse: false,
shallow: false,
limit: None,
cursor: None,
}
}
#[inline]
const fn session(storage: &'a SessionStorage, url: Url) -> Self {
Self::new(ListScope::Session(storage), url)
}
#[inline]
const fn public(storage: &'a PublicStorage, url: Url) -> Self {
Self::new(ListScope::Public(storage), url)
}
pub const fn reverse(mut self, reverse: bool) -> Self {
self.reverse = reverse;
self
}
pub const fn shallow(mut self, shallow: bool) -> Self {
self.shallow = shallow;
self
}
pub const fn limit(mut self, limit: u16) -> Self {
self.limit = Some(limit);
self
}
pub fn cursor(mut self, cursor: &str) -> Self {
self.cursor = Some(cursor.to_string());
self
}
pub async fn send(self) -> Result<Vec<PubkyResource>> {
let mut url = self.url;
{
let mut q = url.query_pairs_mut();
if self.reverse {
q.append_key_only("reverse");
}
if self.shallow {
q.append_key_only("shallow");
}
if let Some(limit) = self.limit {
q.append_pair("limit", &limit.to_string());
}
if let Some(cursor) = self.cursor {
q.append_pair("cursor", &cursor);
}
}
let rb = match self.scope {
ListScope::Public(storage) => {
storage
.client
.cross_request(Method::GET, url.clone())
.await?
}
ListScope::Session(storage) => {
let rb = storage
.client
.cross_request(Method::GET, url.clone())
.await?;
#[cfg(not(target_arch = "wasm32"))]
let rb = storage.with_session_cookie(rb);
rb
}
};
let resp = rb.send().await?;
cross_log!(
debug,
"Request completed with status {} (LIST {})",
resp.status(),
resp.url()
);
let resp = check_http_status(resp).await?;
let bytes = resp.bytes().await?;
let mut out = Vec::new();
for line in String::from_utf8_lossy(&bytes).lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
out.push(Self::parse_resource_line(trimmed)?);
}
Ok(out)
}
fn parse_resource_line(line: &str) -> Result<PubkyResource> {
if line.starts_with("http://") || line.starts_with("https://") {
let url = Url::parse(line)?;
PubkyResource::from_transport_url(&url)
} else {
line.parse()
}
}
}