feat(homeserver): list when path ends with '/' and some refactoring

This commit is contained in:
nazeh
2024-08-07 18:01:02 +03:00
parent 59360d9cd6
commit dd2c9ea93c
4 changed files with 56 additions and 52 deletions

View File

@@ -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<bool> {
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<u16>,
cursor: Option<String>,
shallow: bool,
) -> anyhow::Result<Vec<String>> {
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;

View File

@@ -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::<u16>().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())),
}
}

View File

@@ -52,8 +52,17 @@ impl<'a> ListBuilder<'a> {
pub async fn send(self) -> Result<Vec<String>> {
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::<Vec<&str>>();
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");

View File

@@ -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();