commit eebb544dcc4bc6f065f9045469c61b49577ea6d5
parent 05ae967da62992b0b99a797589be0875de23b301
Author: William Casarin <jb55@jb55.com>
Date: Sat, 30 Dec 2023 20:37:14 -0800
Add note blocks iterator
Diffstat:
6 files changed, 299 insertions(+), 5 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,6 +1,7 @@
.direnv/
/nostrdb
Cargo.lock
+tags
target/
.build-result
.buildcmd
diff --git a/src/block.rs b/src/block.rs
@@ -0,0 +1,254 @@
+use crate::{bindings, Note, Transaction};
+
+#[derive(Debug)]
+pub struct Blocks<'a> {
+ ptr: *mut bindings::ndb_blocks,
+ txn: Option<&'a Transaction>,
+}
+
+#[derive(Debug)]
+pub struct Block<'a> {
+ ptr: *mut bindings::ndb_block,
+ txn: Option<&'a Transaction>,
+}
+
+pub struct BlockIter<'a> {
+ iter: bindings::ndb_block_iterator,
+ txn: Option<&'a Transaction>,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum BlockType {
+ Hashtag,
+ Text,
+ MentionIndex,
+ MentionBech32,
+ Url,
+ Invoice,
+}
+
+impl<'a> Block<'a> {
+ pub(crate) fn new_transactional(
+ ptr: *mut bindings::ndb_block,
+ txn: &'a Transaction,
+ ) -> Block<'a> {
+ Block {
+ ptr,
+ txn: Some(txn),
+ }
+ }
+
+ pub(crate) fn new_owned(ptr: *mut bindings::ndb_block) -> Block<'static> {
+ Block { ptr, txn: None }
+ }
+
+ pub(crate) fn new(ptr: *mut bindings::ndb_block, txn: Option<&'a Transaction>) -> Block<'a> {
+ Block { ptr, txn }
+ }
+
+ pub fn as_ptr(&self) -> *mut bindings::ndb_block {
+ self.ptr
+ }
+
+ pub fn as_str(&self) -> &'a str {
+ unsafe {
+ let str_block = bindings::ndb_block_str(self.as_ptr());
+ if str_block.is_null() {
+ return "";
+ }
+ let ptr = bindings::ndb_str_block_ptr(str_block) as *const u8;
+ let len = bindings::ndb_str_block_len(str_block);
+ let byte_slice = std::slice::from_raw_parts(ptr, len.try_into().unwrap());
+ std::str::from_utf8_unchecked(byte_slice)
+ }
+ }
+
+ pub fn blocktype(&self) -> BlockType {
+ let typ = unsafe { bindings::ndb_get_block_type(self.as_ptr()) };
+ println!("type {}", typ);
+ let r = match typ {
+ 1 => BlockType::Hashtag,
+ 2 => BlockType::Text,
+ 3 => BlockType::MentionIndex,
+ 4 => BlockType::MentionBech32,
+ 5 => BlockType::Url,
+ 6 => BlockType::Invoice,
+ _ => panic!("Invalid blocktype {}", typ),
+ };
+ println!("typer {:?}", r);
+ r
+ }
+}
+
+impl<'a> Blocks<'a> {
+ pub(crate) fn new_transactional(
+ ptr: *mut bindings::ndb_blocks,
+ txn: &'a Transaction,
+ ) -> Blocks<'a> {
+ Blocks {
+ ptr,
+ txn: Some(txn),
+ }
+ }
+
+ pub(crate) fn new_owned(ptr: *mut bindings::ndb_blocks) -> Blocks<'static> {
+ Blocks { ptr, txn: None }
+ }
+
+ pub fn iter(&self, note: &Note<'a>) -> BlockIter<'a> {
+ let content = note.content_ptr();
+ match self.txn {
+ Some(txn) => BlockIter::new_transactional(content, self.as_ptr(), txn),
+ None => BlockIter::new_owned(content, self.as_ptr()),
+ }
+ }
+
+ pub fn as_ptr(&self) -> *mut bindings::ndb_blocks {
+ self.ptr
+ }
+}
+
+impl<'a> BlockIter<'a> {
+ pub(crate) fn new_transactional(
+ content: *const ::std::os::raw::c_char,
+ blocks: *mut bindings::ndb_blocks,
+ txn: &'a Transaction,
+ ) -> BlockIter<'a> {
+ let type_ = bindings::ndb_block_type_BLOCK_TEXT;
+ let mention_index: u32 = 1;
+ let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
+ let block = bindings::ndb_block { type_, block };
+ let p = blocks as *mut ::std::os::raw::c_uchar;
+ let iter = bindings::ndb_block_iterator {
+ content,
+ blocks,
+ p,
+ block,
+ };
+ let mut block_iter = BlockIter {
+ iter,
+ txn: Some(txn),
+ };
+ unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut block_iter.iter) };
+ block_iter
+ }
+
+ pub(crate) fn new_owned(
+ content: *const ::std::os::raw::c_char,
+ blocks: *mut bindings::ndb_blocks,
+ ) -> BlockIter<'static> {
+ let type_ = bindings::ndb_block_type_BLOCK_TEXT;
+ let mention_index: u32 = 1;
+ let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
+ let block = bindings::ndb_block { type_, block };
+ let p = blocks as *mut ::std::os::raw::c_uchar;
+ let mut iter = bindings::ndb_block_iterator {
+ content,
+ blocks,
+ p,
+ block,
+ };
+ unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut iter) };
+ BlockIter { iter, txn: None }
+ }
+
+ pub fn as_ptr(&self) -> *const bindings::ndb_block_iterator {
+ &self.iter
+ }
+
+ pub fn as_mut_ptr(&self) -> *mut bindings::ndb_block_iterator {
+ self.as_ptr() as *mut bindings::ndb_block_iterator
+ }
+}
+
+impl<'a> Iterator for BlockIter<'a> {
+ type Item = Block<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let block = unsafe { bindings::ndb_blocks_iterate_next(self.as_mut_ptr()) };
+ if block.is_null() {
+ return None;
+ }
+
+ Some(Block::new(block, self.txn))
+ }
+}
+
+/*
+impl<'a> IntoIterator for Blocks<'a> {
+ type Item = Block<'a>;
+ type IntoIter = BlockIter<'a>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ match self.txn {
+ Some(txn) => BlockIter::new_transactional(self.as_ptr(), txn),
+ None => BlockIter::new_owned(self.as_ptr()),
+ }
+ }
+}
+*/
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_util;
+ use crate::{Config, Ndb};
+
+ #[test]
+ fn note_blocks_work() {
+ let db = "target/testdbs/note_blocks";
+
+ {
+ let mut ndb = Ndb::new(db, &Config::new()).expect("ndb");
+ ndb.process_event("[\"EVENT\",\"s\",{\"id\":\"d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304\",\"pubkey\":\"b5b1b5d2914daa2eda99af22ae828effe98730bf69dcca000fa37bfb9e395e32\",\"created_at\": 1703989205,\"kind\": 1,\"tags\": [],\"content\": \"#hashtags, are neat nostr:nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw https://github.com/damus-io\",\"sig\": \"07af3062616a17ef392769cadb170ac855c817c103e007c72374499bbadb2fe8917a0cc5b3fdc5aa5d56de086e128b3aeaa8868f6fe42a409767241b6a29cc94\"}]").expect("process ok");
+ }
+
+ {
+ let ndb = Ndb::new(db, &Config::new()).expect("ndb");
+ let id =
+ hex::decode("d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304")
+ .expect("hex id");
+ let txn = Transaction::new(&ndb).expect("txn");
+ let id_bytes: [u8; 32] = id.try_into().expect("id bytes");
+ let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap();
+ let blocks = ndb
+ .get_blocks_by_key(&txn, note.key().unwrap())
+ .expect("note");
+ let mut c = 0;
+ for block in blocks.iter(¬e) {
+ match c {
+ 0 => {
+ assert_eq!(block.blocktype(), BlockType::Hashtag);
+ assert_eq!(block.as_str(), "hashtags");
+ }
+
+ 1 => {
+ assert_eq!(block.blocktype(), BlockType::Text);
+ assert_eq!(block.as_str(), ", are neat ");
+ }
+
+ 2 => {
+ assert_eq!(block.blocktype(), BlockType::MentionBech32);
+ assert_eq!(block.as_str(), "nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw");
+ }
+
+ 3 => {
+ assert_eq!(block.blocktype(), BlockType::Text);
+ assert_eq!(block.as_str(), " ");
+ }
+
+ 4 => {
+ assert_eq!(block.blocktype(), BlockType::Url);
+ assert_eq!(block.as_str(), "https://github.com/damus-io");
+ }
+
+ _ => assert!(false),
+ }
+
+ c += 1;
+ }
+ }
+
+ test_util::cleanup_db(&db);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -8,6 +8,7 @@ mod bindings;
#[allow(non_snake_case)]
mod ndb_profile;
+mod block;
mod config;
mod error;
mod ndb;
@@ -16,6 +17,7 @@ mod profile;
mod result;
mod transaction;
+pub use block::Blocks;
pub use config::Config;
pub use error::Error;
pub use ndb::Ndb;
diff --git a/src/ndb.rs b/src/ndb.rs
@@ -3,7 +3,7 @@ use std::ffi::CString;
use std::ptr;
use crate::bindings;
-use crate::{Config, Error, Note, ProfileRecord, Result, Transaction};
+use crate::{Blocks, Config, Error, Note, ProfileRecord, Result, Transaction};
use std::fs;
use std::path::Path;
use std::sync::Arc;
@@ -88,7 +88,7 @@ impl Ndb {
let profile_record_ptr = unsafe {
bindings::ndb_get_profile_by_pubkey(
- transaction.as_ptr() as *mut bindings::ndb_txn,
+ transaction.as_mut_ptr(),
id.as_ptr(),
&mut len,
&mut primkey,
@@ -109,6 +109,32 @@ impl Ndb {
))
}
+ pub fn get_notekey_by_id(&self, txn: &Transaction, id: &[u8; 32]) -> Result<u64> {
+ let res = unsafe {
+ bindings::ndb_get_notekey_by_id(
+ txn.as_mut_ptr(),
+ id.as_ptr() as *const ::std::os::raw::c_uchar,
+ )
+ };
+
+ if res == 0 {
+ return Err(Error::NotFound);
+ }
+
+ Ok(res)
+ }
+
+ pub fn get_blocks_by_key<'a>(&self, txn: &'a Transaction, note_key: u64) -> Result<Blocks<'a>> {
+ let blocks_ptr =
+ unsafe { bindings::ndb_get_blocks_by_key(self.as_ptr(), txn.as_mut_ptr(), note_key) };
+
+ if blocks_ptr.is_null() {
+ return Err(Error::NotFound);
+ }
+
+ Ok(Blocks::new_transactional(blocks_ptr, txn))
+ }
+
/// Get a note from the database. Takes a [Transaction] and a 32-byte [Note] Id
pub fn get_note_by_id<'a>(
&self,
diff --git a/src/note.rs b/src/note.rs
@@ -53,6 +53,13 @@ impl<'a> Note<'a> {
}
}
+ pub fn key(&self) -> Option<u64> {
+ match self {
+ Note::Transactional { key, .. } => Some(*key),
+ _ => None,
+ }
+ }
+
pub fn size(&self) -> usize {
match self {
Note::Owned { size, .. } => *size,
@@ -71,10 +78,14 @@ impl<'a> Note<'a> {
unsafe { bindings::ndb_note_content_length(self.as_ptr()) as usize }
}
+ pub fn content_ptr(&self) -> *const ::std::os::raw::c_char {
+ unsafe { bindings::ndb_note_content(self.as_ptr()) }
+ }
+
/// Get the [`Note`] contents.
pub fn content(&self) -> &'a str {
unsafe {
- let content = bindings::ndb_note_content(self.as_ptr());
+ let content = self.content_ptr();
let byte_slice = std::slice::from_raw_parts(content as *const u8, self.content_size());
std::str::from_utf8_unchecked(byte_slice)
}
diff --git a/src/transaction.rs b/src/transaction.rs
@@ -27,8 +27,8 @@ impl Transaction {
&self.txn
}
- pub fn as_mut_ptr(&mut self) -> *mut bindings::ndb_txn {
- &mut self.txn
+ pub fn as_mut_ptr(&self) -> *mut bindings::ndb_txn {
+ self.as_ptr() as *mut bindings::ndb_txn
}
}