11
22#include "nostrdb.h"
33#include "binmoji.h"
4-
5- // these must be byte-aligned, they are directly accessing the serialized data
6- // representation
7- #pragma pack(push, 1)
8-
9- // 16 bytes
10- struct ndb_note_meta_entry {
11- // 4 byte entry header
12- uint16_t type ;
13- uint16_t flags ;
14-
15- // additional 4 bytes of aux storage for payloads that are >8 bytes
16- //
17- // for reactions types, this is used for counts
18- // normally this would have been padding but we make use of it
19- // in our manually packed structure
20- uint32_t aux ;
21-
22- // 8 byte metadata payload
23- union {
24- uint64_t value ;
25-
26- struct {
27- uint32_t offset ;
28- uint32_t padding ;
29- } offset ;
30-
31- // the reaction binmoji[1] for reaction, count is stored in aux
32- union ndb_reaction_str reaction_str ;
33- } payload ;
34- };
35- STATIC_ASSERT (sizeof (struct ndb_note_meta_entry ) == 16 , note_meta_entry_should_be_16_bytes );
36-
37- /* newtype wrapper around the header entry */
38- struct ndb_note_meta {
39- // 4 bytes
40- uint8_t version ;
41- uint8_t padding ;
42- uint16_t count ;
43-
44- // 4 bytes
45- uint32_t data_table_size ;
46-
47- // 8 bytes
48- uint64_t flags ;
49- };
50- STATIC_ASSERT (sizeof (struct ndb_note_meta ) == 16 , note_meta_entry_should_be_16_bytes );
51-
52- #pragma pack(pop)
4+ #include "metadata.h"
535
546int ndb_reaction_str_is_emoji (union ndb_reaction_str str )
557{
@@ -96,6 +48,19 @@ static int ndb_reaction_set_str(union ndb_reaction_str *reaction, const char *st
9648 return 0 ;
9749}
9850
51+ const char * ndb_reaction_to_str (union ndb_reaction_str * str , char buf [128 ])
52+ {
53+ struct binmoji binmoji ;
54+
55+ if (ndb_reaction_str_is_emoji (* str )) {
56+ binmoji_decode (str -> binmoji , & binmoji );
57+ binmoji_to_string (& binmoji , buf , 128 );
58+ return (const char * )buf ;
59+ } else {
60+ return (const char * )str -> packed .str ;
61+ }
62+ }
63+
9964/* set the value of an ndb_reaction_str to an emoji or small string */
10065int ndb_reaction_set (union ndb_reaction_str * reaction , const char * str )
10166{
@@ -215,9 +180,16 @@ static int compare_entries(const void *a, const void *b)
215180struct ndb_note_meta_entry * ndb_note_meta_entries (struct ndb_note_meta * meta )
216181{
217182 /* entries start at the end of the header record */
218- return (struct ndb_note_meta_entry * )((uint8_t * )meta + sizeof (* meta ));
183+ return (struct ndb_note_meta_entry * )((unsigned char * )meta + sizeof (* meta ));
219184}
220185
186+ struct ndb_note_meta_entry * ndb_note_meta_entry_at (struct ndb_note_meta * meta , int i )
187+ {
188+ if (i >= ndb_note_meta_entries_count (meta ))
189+ return NULL ;
190+
191+ return & ndb_note_meta_entries (meta )[i ];
192+ }
221193void ndb_note_meta_build (struct ndb_note_meta_builder * builder , struct ndb_note_meta * * meta )
222194{
223195 /* sort entries */
@@ -227,18 +199,27 @@ void ndb_note_meta_build(struct ndb_note_meta_builder *builder, struct ndb_note_
227199 /* not initialized */
228200 assert (builder -> cursor .start != builder -> cursor .p );
229201
230- if (header -> count > 0 ) {
202+ if (header -> count > 1 ) {
231203 entries = ndb_note_meta_entries (header );
204+ /*assert(entries);*/
232205
233206 /* ensure entries are always sorted so bsearch is possible for large metadata
234207 * entries. probably won't need that for awhile though */
208+
209+ /* this also ensures our counts entry is near the front, which will be a very
210+ * hot and common entry to hit */
235211 qsort (entries , header -> count , sizeof (struct ndb_note_meta_entry ), compare_entries );
236212 }
237213
238214 * meta = header ;
239215 return ;
240216}
241217
218+ uint16_t * ndb_note_meta_entry_type (struct ndb_note_meta_entry * entry )
219+ {
220+ return & entry -> type ;
221+ }
222+
242223/* find a metadata entry, optionally matching a payload */
243224struct ndb_note_meta_entry * ndb_note_meta_find_entry (struct ndb_note_meta * meta , uint16_t type , uint64_t * payload )
244225{
@@ -249,12 +230,19 @@ struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta,
249230 return NULL ;
250231
251232 entries = ndb_note_meta_entries (meta );
233+ assert (((intptr_t )entries - (intptr_t )meta ) == 16 );
252234
253235 for (i = 0 ; i < meta -> count ; i ++ ) {
254236 entry = & entries [i ];
237+ assert (((uintptr_t )entry % 8 ) == 0 );
238+ /*
239+ assert(entry->type < 100);
240+ printf("finding %d/%d q:%d q:%"PRIx64" entry_type:%d entry:%"PRIx64"\n",
241+ i+1, (int)meta->count, type, payload ? *payload : 0, entry->type, entry->payload.value);
242+ */
255243 if (entry -> type != type )
256244 continue ;
257- if (payload && * payload != entry -> payload .value )
245+ if (payload && ( * payload != entry -> payload .value ) )
258246 continue ;
259247 return entry ;
260248 }
@@ -266,28 +254,135 @@ void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t coun
266254{
267255 entry -> type = NDB_NOTE_META_REACTION ;
268256 entry -> flags = 0 ;
269- entry -> aux = count ;
257+ entry -> aux . value = count ;
270258 entry -> payload .reaction_str = str ;
271259}
272260
273261/* sets the quote repost count for this note */
274- void ndb_note_meta_quotes_set (struct ndb_note_meta_entry * entry , uint32_t count )
262+ void ndb_note_meta_counts_set (struct ndb_note_meta_entry * entry ,
263+ uint32_t total_reactions ,
264+ uint16_t quotes ,
265+ uint16_t direct_replies ,
266+ uint32_t thread_replies )
275267{
276- entry -> type = NDB_NOTE_META_QUOTES ;
277- entry -> flags = 0 ;
278- entry -> aux = count ;
279- /* unused */
280- entry -> payload .value = 0 ;
268+ entry -> type = NDB_NOTE_META_COUNTS ;
269+ entry -> aux .total_reactions = total_reactions ;
270+ entry -> payload .counts .quotes = quotes ;
271+ entry -> payload .counts .direct_replies = direct_replies ;
272+ entry -> payload .counts .thread_replies = thread_replies ;
273+ }
274+
275+ /* clones a metadata, either adding a new entry of a specific type, or returing
276+ * a reference to it
277+ *
278+ * [in/out] meta: pointer to an existing meta entry, can but overwritten to
279+ * [out] entry: pointer to the added entry
280+ *
281+ * */
282+ enum ndb_meta_clone_result ndb_note_meta_clone_with_entry (
283+ struct ndb_note_meta * * meta ,
284+ struct ndb_note_meta_entry * * entry ,
285+ uint16_t type ,
286+ uint64_t * payload ,
287+ unsigned char * buf ,
288+ size_t bufsize )
289+ {
290+ size_t size , offset ;
291+ struct ndb_note_meta_builder builder ;
292+
293+ if (* meta == NULL ) {
294+ ndb_note_meta_builder_init (& builder , buf , bufsize );
295+ * entry = ndb_note_meta_add_entry (& builder );
296+ * meta = (struct ndb_note_meta * )buf ;
297+
298+ assert (* entry );
299+
300+ ndb_note_meta_build (& builder , meta );
301+ return NDB_META_CLONE_NEW_ENTRY ;
302+ } else if ((size = ndb_note_meta_total_size (* meta )) > bufsize ) {
303+ ndb_debug ("buf size too small (%d < %d) for metadata entry\n" , bufsize , size );
304+ goto fail ;
305+ } else if ((* entry = ndb_note_meta_find_entry (* meta , type , payload ))) {
306+ offset = (unsigned char * )(* entry ) - (unsigned char * )(* meta );
307+
308+ /* we have an existing entry. simply memcpy and return the new entry position */
309+ assert (offset < size );
310+ assert ((offset % 16 ) == 0 );
311+ assert (((uintptr_t )buf % 8 ) == 0 );
312+
313+ memcpy (buf , * meta , size );
314+ * meta = (struct ndb_note_meta * )buf ;
315+ * entry = (struct ndb_note_meta_entry * )(((unsigned char * )(* meta )) + offset );
316+ return NDB_META_CLONE_EXISTING_ENTRY ;
317+ } else if (size + sizeof (* entry ) > bufsize ) {
318+ /* if we don't have an existing entry, make sure we have room to add one */
319+
320+ ndb_debug ("note metadata is too big (%d > %d) to clone with entry\n" ,
321+ (int )(len + sizeof (* entry )), (int )scratch_size );
322+ /* no room. this is bad, if this happens we should fix it */
323+ goto fail ;
324+ } else {
325+ /* we need to add a new entry */
326+ ndb_note_meta_builder_init (& builder , buf , bufsize );
327+
328+ memcpy (buf , * meta , size );
329+ builder .cursor .p = buf + size ;
330+
331+ * entry = ndb_note_meta_add_entry (& builder );
332+ assert (* entry );
333+ (* entry )-> type = type ;
334+ (* entry )-> payload .value = payload ? * payload : 0 ;
335+
336+ * meta = (struct ndb_note_meta * )buf ;
337+
338+ assert (* entry );
339+ assert (* meta );
340+
341+ ndb_note_meta_build (& builder , meta );
342+
343+ /* we re-find here since it could have been sorted */
344+ * entry = ndb_note_meta_find_entry (* meta , type , payload );
345+ assert (* entry );
346+ assert (* ndb_note_meta_entry_type (* entry ) == type );
347+
348+ return NDB_META_CLONE_NEW_ENTRY ;
349+ }
350+
351+ assert (!"should be impossible to get here" );
352+ fail :
353+ * entry = NULL ;
354+ * meta = NULL ;
355+ return 0 ;
356+ }
357+
358+ uint32_t * ndb_note_meta_reaction_count (struct ndb_note_meta_entry * entry )
359+ {
360+ return & entry -> aux .value ;
361+ }
362+
363+ uint16_t * ndb_note_meta_counts_direct_replies (struct ndb_note_meta_entry * entry )
364+ {
365+ return & entry -> payload .counts .direct_replies ;
366+ }
367+
368+ uint32_t * ndb_note_meta_counts_total_reactions (struct ndb_note_meta_entry * entry )
369+ {
370+ return & entry -> aux .total_reactions ;
371+ }
372+
373+ uint32_t * ndb_note_meta_counts_thread_replies (struct ndb_note_meta_entry * entry )
374+ {
375+ return & entry -> payload .counts .thread_replies ;
281376}
282377
283- uint32_t ndb_note_meta_reaction_count (struct ndb_note_meta_entry * entry )
378+ uint16_t * ndb_note_meta_counts_quotes (struct ndb_note_meta_entry * entry )
284379{
285- return entry -> aux ;
380+ return & entry -> payload . counts . quotes ;
286381}
287382
288383void ndb_note_meta_reaction_set_count (struct ndb_note_meta_entry * entry , uint32_t count )
289384{
290- entry -> aux = count ;
385+ entry -> aux . value = count ;
291386}
292387
293388union ndb_reaction_str ndb_note_meta_reaction_str (struct ndb_note_meta_entry * entry )
0 commit comments