diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index 67d626be..af26828d 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -98,39 +98,82 @@ impl DB { cursor: Option, shallow: bool, ) -> anyhow::Result> { + // Vector to store results + let mut results = Vec::new(); + + let limit = limit.unwrap_or(MAX_LIST_LIMIT).min(MAX_LIST_LIMIT); + // 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 limit = limit.unwrap_or(MAX_LIST_LIMIT).min(MAX_LIST_LIMIT); - - // Vector to store results - let mut results = Vec::new(); + let mut cursor = format!("{path}{cursor}"); // Fetch data based on direction if reverse { for _ in 0..limit { - if let Some((key, _)) = self.tables.entries.get_lower_than(txn, cursor)? { + 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)) + + if shallow { + let mut split = key[path.len()..].split('/'); + let item = split.next().expect("should not be reachable"); + + let is_directory = split.next().is_some(); + + cursor = format!( + "{}{}", + &key[..(path.len() + item.len())], + // `.` is immediately lower than `/` + if is_directory { "." } else { "" } + ); + + let url = format!( + "pubky://{path}{item}{}", + if is_directory { "/" } else { "" } + ); + + results.push(url); + } else { + cursor = key.to_string(); + results.push(format!("pubky://{}", key)) + } }; } } else { for _ in 0..limit { - if let Some((key, _)) = self.tables.entries.get_greater_than(txn, cursor)? { + if let Some((key, _)) = self.tables.entries.get_greater_than(txn, &cursor)? { if !key.starts_with(path) { break; } - cursor = key; - results.push(format!("pubky://{}", key)) + + if shallow { + let mut split = key[path.len()..].split('/'); + let item = split.next().expect("should not be reachable"); + + let is_directory = split.next().is_some(); + + cursor = format!( + "{}{}", + &key[..(path.len() + item.len())], + // `0` is immediately higher than `/` + if is_directory { "0" } else { "" } + ); + + let url = format!( + "pubky://{path}{item}{}", + if is_directory { "/" } else { "" } + ); + + results.push(url); + } else { + cursor = key.to_string(); + results.push(format!("pubky://{}", key)) + } }; } }; diff --git a/pubky/src/shared/list_builder.rs b/pubky/src/shared/list_builder.rs index 4d63351e..5eccc7ee 100644 --- a/pubky/src/shared/list_builder.rs +++ b/pubky/src/shared/list_builder.rs @@ -10,6 +10,7 @@ pub struct ListBuilder<'a> { limit: Option, cursor: Option<&'a str>, client: &'a PubkyClient, + shallow: bool, } impl<'a> ListBuilder<'a> { @@ -21,12 +22,13 @@ impl<'a> ListBuilder<'a> { limit: None, cursor: None, reverse: false, + shallow: false, } } /// Set the `reverse` option. - pub fn reverse(mut self, reverse: bool) -> Self { - self.reverse = reverse; + pub fn reverse(mut self) -> Self { + self.reverse = true; self } @@ -44,6 +46,11 @@ impl<'a> ListBuilder<'a> { self } + pub fn shallow(mut self) -> Self { + self.shallow = true; + self + } + /// Send the list request. /// /// Returns a list of Pubky URLs of the files in the path of the `url` @@ -68,6 +75,10 @@ impl<'a> ListBuilder<'a> { query.append_key_only("reverse"); } + if self.shallow { + query.append_key_only("shallow"); + } + if let Some(limit) = self.limit { query.append_pair("limit", &limit.to_string()); } diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index a316306c..0f748746 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -319,7 +319,7 @@ mod tests { let list = client .list(url.as_str()) .unwrap() - .reverse(true) + .reverse() .send() .await .unwrap(); @@ -340,7 +340,7 @@ mod tests { let list = client .list(url.as_str()) .unwrap() - .reverse(true) + .reverse() .limit(2) .send() .await @@ -360,7 +360,7 @@ mod tests { let list = client .list(url.as_str()) .unwrap() - .reverse(true) + .reverse() .limit(2) .cursor("d.txt") .send() @@ -377,4 +377,84 @@ mod tests { ); } } + + #[tokio::test] + async fn list_shallow() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let urls = vec![ + format!("pubky://{}/pub/a.com/a.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("pubky://{}/pub/example.con/d.txt", keypair.public_key()), + format!("pubky://{}/pub/example.con", keypair.public_key()), + format!("pubky://{}/pub/file", keypair.public_key()), + format!("pubky://{}/pub/file2", keypair.public_key()), + format!("pubky://{}/pub/z.com/a.txt", keypair.public_key()), + ]; + + for url in urls { + client.put(url.as_str(), &[0]).await.unwrap(); + } + + let url = format!("pubky://{}/pub/", keypair.public_key()); + + { + let list = client + .list(url.as_str()) + .unwrap() + .shallow() + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{}/pub/a.com/", keypair.public_key()), + format!("pubky://{}/pub/example.com/", keypair.public_key()), + format!("pubky://{}/pub/example.con", keypair.public_key()), + format!("pubky://{}/pub/example.con/", keypair.public_key()), + format!("pubky://{}/pub/file", keypair.public_key()), + format!("pubky://{}/pub/file2", keypair.public_key()), + format!("pubky://{}/pub/z.com/", keypair.public_key()), + ], + "normal list shallow" + ); + } + + { + let list = client + .list(url.as_str()) + .unwrap() + .shallow() + .reverse() + .send() + .await + .unwrap(); + + assert_eq!( + list, + vec![ + format!("pubky://{}/pub/z.com/", keypair.public_key()), + format!("pubky://{}/pub/file2", keypair.public_key()), + format!("pubky://{}/pub/file", keypair.public_key()), + format!("pubky://{}/pub/example.con/", keypair.public_key()), + format!("pubky://{}/pub/example.con", keypair.public_key()), + format!("pubky://{}/pub/example.com/", keypair.public_key()), + format!("pubky://{}/pub/a.com/", keypair.public_key()), + ], + "reverse list shallow" + ); + } + } }