From f5fb294efa1c960609742bbe30cec80920e3ee3c Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 18 Nov 2025 15:36:35 -0300 Subject: [PATCH] blossom: add an alternative BlobIndex implementation in memory. --- khatru/blossom/blob.go | 5 +- khatru/blossom/memoryindex.go | 89 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 khatru/blossom/memoryindex.go diff --git a/khatru/blossom/blob.go b/khatru/blossom/blob.go index 24dfd10..5985598 100644 --- a/khatru/blossom/blob.go +++ b/khatru/blossom/blob.go @@ -24,4 +24,7 @@ type BlobIndex interface { Delete(ctx context.Context, sha256 string, pubkey nostr.PubKey) error } -var _ BlobIndex = (*EventStoreBlobIndexWrapper)(nil) +var ( + _ BlobIndex = (*EventStoreBlobIndexWrapper)(nil) + _ BlobIndex = (*MemoryBlobIndex)(nil) +) diff --git a/khatru/blossom/memoryindex.go b/khatru/blossom/memoryindex.go new file mode 100644 index 0000000..82f09f1 --- /dev/null +++ b/khatru/blossom/memoryindex.go @@ -0,0 +1,89 @@ +package blossom + +import ( + "context" + "errors" + "iter" + "slices" + + "fiatjaf.com/nostr" + "github.com/puzpuzpuz/xsync/v3" +) + +type ownedBlob struct { + blob BlobDescriptor + owners []nostr.PubKey +} + +type MemoryBlobIndex struct { + m *xsync.MapOf[string, ownedBlob] +} + +func NewMemoryBlobIndex() MemoryBlobIndex { + return MemoryBlobIndex{ + m: xsync.NewMapOf[string, ownedBlob](), + } +} + +func (x MemoryBlobIndex) Keep(ctx context.Context, blob BlobDescriptor, pubkey nostr.PubKey) error { + x.m.Compute(blob.SHA256, func(oldValue ownedBlob, loaded bool) (newValue ownedBlob, delete bool) { + if loaded { + newValue = oldValue + if !slices.Contains(newValue.owners, pubkey) { + newValue.owners = append(newValue.owners, pubkey) + } + } else { + newValue = ownedBlob{ + blob: blob, + owners: []nostr.PubKey{pubkey}, + } + } + + return newValue, false + }) + + return nil +} + +func (x MemoryBlobIndex) List(ctx context.Context, pubkey nostr.PubKey) iter.Seq[BlobDescriptor] { + return func(yield func(BlobDescriptor) bool) { + x.m.Range(func(key string, value ownedBlob) bool { + if value.blob.Owner == value.owners[0] { + if !yield(value.blob) { + return false + } + } + return true + }) + } +} + +func (x MemoryBlobIndex) Get(ctx context.Context, sha256 string) (*BlobDescriptor, error) { + if val, ok := x.m.Load(sha256); ok { + val.blob.Owner = val.owners[0] + return &val.blob, nil + } + return nil, errors.New("not found") +} + +func (x MemoryBlobIndex) Delete(ctx context.Context, sha256 string, pubkey nostr.PubKey) error { + x.m.Compute(sha256, func(oldValue ownedBlob, loaded bool) (newValue ownedBlob, delete bool) { + if loaded { + if idx := slices.Index(oldValue.owners, pubkey); idx != -1 { + if len(oldValue.owners) == 1 { + // this is the only owner, remove the blob + return oldValue, true + } else { + // remove this owner + oldValue.owners[idx] = oldValue.owners[len(oldValue.owners)-1] + oldValue.owners = oldValue.owners[0 : len(oldValue.owners)-1] + return oldValue, false + } + } + } + + return oldValue, true + }) + + return nil +}