diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index 0d8fa35..67d626b 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -5,7 +5,7 @@ use std::{borrow::Cow, fmt::Result, time::SystemTime}; use heed::{ types::{Bytes, Str}, - BoxedError, BytesDecode, BytesEncode, Database, + BoxedError, BytesDecode, BytesEncode, Database, RoTxn, }; use pubky_common::{ @@ -82,32 +82,31 @@ impl DB { Ok(deleted) } + pub fn contains_directory(&self, txn: &RoTxn, path: &str) -> anyhow::Result { + Ok(self.tables.entries.get_greater_than(txn, path)?.is_some()) + } + /// Return a list of pubky urls. /// /// - limit defaults to and capped by [MAX_LIST_LIMIT] pub fn list( &self, - public_key: &PublicKey, - prefix: &str, + txn: &RoTxn, + path: &str, reverse: bool, limit: Option, cursor: Option, + shallow: bool, ) -> anyhow::Result> { - let db = self.tables.entries; - let txn = self.env.read_txn()?; + // Remove directories from the cursor; + let cursor = cursor + .as_deref() + .and_then(|mut cursor| cursor.rsplit('/').next()) + .unwrap_or(if reverse { "~" } else { "" }); + + let cursor = format!("{path}{cursor}"); + let mut cursor = cursor.as_str(); - let prefix = format!("{public_key}/{prefix}"); - // Normalized cursor - let cursor = cursor.map(|mut cursor| { - if cursor.starts_with("pubky://") { - cursor = cursor[8..].into(); - } - if cursor.starts_with(&prefix) { - cursor - } else { - format!("{prefix}{cursor}") - } - }); let limit = limit.unwrap_or(MAX_LIST_LIMIT).min(MAX_LIST_LIMIT); // Vector to store results @@ -115,37 +114,19 @@ impl DB { // Fetch data based on direction if reverse { - if let Some(x) = &cursor { - let mut cursor = cursor.unwrap_or(prefix.to_string()); - let mut cursor = cursor.as_str(); - - for _ in 0..limit { - if let Some((key, _)) = self.tables.entries.get_lower_than(&txn, cursor)? { - if !key.starts_with(&prefix) { - break; - } - cursor = key; - results.push(format!("pubky://{}", key)) - }; - } - } else { - // TODO: find a way to avoid this special case. - - let mut iter = self.tables.entries.rev_prefix_iter(&txn, &prefix)?; - - for _ in 0..limit { - if let Some((key, _)) = iter.next().transpose()? { - results.push(format!("pubky://{}", key)) - }; - } + for _ in 0..limit { + if let Some((key, _)) = self.tables.entries.get_lower_than(txn, cursor)? { + if !key.starts_with(path) { + break; + } + cursor = key; + results.push(format!("pubky://{}", key)) + }; } } else { - let mut cursor = cursor.unwrap_or(prefix.to_string()); - let mut cursor = cursor.as_str(); - for _ in 0..limit { - if let Some((key, _)) = self.tables.entries.get_greater_than(&txn, cursor)? { - if !key.starts_with(&prefix) { + if let Some((key, _)) = self.tables.entries.get_greater_than(txn, cursor)? { + if !key.starts_with(path) { break; } cursor = key; diff --git a/pubky-homeserver/src/routes/public.rs b/pubky-homeserver/src/routes/public.rs index a99f633..33a6da3 100644 --- a/pubky-homeserver/src/routes/public.rs +++ b/pubky-homeserver/src/routes/public.rs @@ -82,14 +82,28 @@ pub async fn get( verify(path.as_str()); let public_key = pubky.public_key(); - if params.contains_key("list") { + let path = path.as_str(); + + if path.ends_with('/') { + let txn = state.db.env.read_txn()?; + + let path = format!("{public_key}/{path}"); + + if !state.db.contains_directory(&txn, &path)? { + return Err(Error::new( + StatusCode::NOT_FOUND, + "Directory Not Found".into(), + )); + } + // Handle listing let vec = state.db.list( - public_key, - path.as_str(), + &txn, + &path, params.contains_key("reverse"), params.get("limit").and_then(|l| l.parse::().ok()), params.get("cursor").map(|cursor| cursor.into()), + params.contains_key("shallow"), )?; return Ok(Response::builder() @@ -101,10 +115,10 @@ pub async fn get( // TODO: Enable streaming - match state.db.get_blob(public_key, path.as_str()) { + match state.db.get_blob(public_key, path) { Err(error) => Err(error)?, Ok(Some(bytes)) => Ok(Response::builder().body(Body::from(bytes)).unwrap()), - Ok(None) => Err(Error::with_status(StatusCode::NOT_FOUND)), + Ok(None) => Err(Error::new(StatusCode::NOT_FOUND, "File Not Found".into())), } } diff --git a/pubky/src/shared/list_builder.rs b/pubky/src/shared/list_builder.rs index 5d0aa32..4d63351 100644 --- a/pubky/src/shared/list_builder.rs +++ b/pubky/src/shared/list_builder.rs @@ -52,8 +52,17 @@ impl<'a> ListBuilder<'a> { pub async fn send(self) -> Result> { let mut url = self.client.pubky_to_http(self.url).await?; + if !url.path().ends_with('/') { + let path = url.path().to_string(); + let mut parts = path.split('/').collect::>(); + parts.pop(); + + let path = format!("{}/", parts.join("/")); + + url.set_path(&path) + } + let mut query = url.query_pairs_mut(); - query.append_key_only("list"); if self.reverse { query.append_key_only("reverse"); diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index f1b5829..a316306 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -236,7 +236,7 @@ mod tests { client.put(url.as_str(), &[0]).await.unwrap(); } - let url = format!("pubky://{}/pub/example.com/", keypair.public_key()); + let url = format!("pubky://{}/pub/example.com/extra", keypair.public_key()); { let list = client.list(url.as_str()).unwrap().send().await.unwrap();