diff --git a/.gitignore b/.gitignore index f44405b..aaf9402 100644 --- 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 new file mode 100644 index 0000000..9ccbe14 --- /dev/null +++ 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 { + 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 index 70c6722..3c8b210 100644 --- 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 index 7ec81cf..a61bebe 100644 --- 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 { + 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> { + 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 index 79f55be..7532672 100644 --- a/src/note.rs +++ b/src/note.rs @@ -53,6 +53,13 @@ impl<'a> Note<'a> { } } + pub fn key(&self) -> Option { + 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 index 6f11e39..16c9909 100644 --- 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 } }