add strfry backend.
Some checks failed
build cli / make-release (push) Has been cancelled
build cli / build-linux (push) Has been cancelled

This commit is contained in:
fiatjaf
2024-07-19 22:16:50 -03:00
parent 5baa1c304b
commit 92588ac842
2 changed files with 165 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/fiatjaf/eventstore/mysql" "github.com/fiatjaf/eventstore/mysql"
"github.com/fiatjaf/eventstore/postgresql" "github.com/fiatjaf/eventstore/postgresql"
"github.com/fiatjaf/eventstore/sqlite3" "github.com/fiatjaf/eventstore/sqlite3"
"github.com/fiatjaf/eventstore/strfry"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
@@ -52,6 +53,8 @@ var app = &cli.Command{
case strings.HasPrefix(path, "https://"): case strings.HasPrefix(path, "https://"):
// if we ever add something else that uses URLs we'll have to modify this // if we ever add something else that uses URLs we'll have to modify this
typ = "elasticsearch" typ = "elasticsearch"
case strings.HasSuffix(path, ".conf"):
typ = "strfry"
default: default:
// try to detect based on the form and names of disk files // try to detect based on the form and names of disk files
dbname, err := detect(path) dbname, err := detect(path)
@@ -102,6 +105,8 @@ var app = &cli.Command{
} }
case "elasticsearch": case "elasticsearch":
db = &elasticsearch.ElasticsearchStorage{URL: path} db = &elasticsearch.ElasticsearchStorage{URL: path}
case "strfry":
db = &strfry.StrfryBackend{ConfigPath: path}
case "": case "":
return fmt.Errorf("couldn't determine store type, you can use --type to specify it manually") return fmt.Errorf("couldn't determine store type, you can use --type to specify it manually")
default: default:

160
strfry/lib.go Normal file
View File

@@ -0,0 +1,160 @@
package strfry
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/fiatjaf/eventstore"
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr"
)
var _ eventstore.Store = (*StrfryBackend)(nil)
type StrfryBackend struct {
ConfigPath string
ExecutablePath string
}
func (s *StrfryBackend) Init() error {
if s.ExecutablePath == "" {
configPath := filepath.Dir(s.ConfigPath)
os.Setenv("PATH", configPath+":"+os.Getenv("PATH"))
exe, err := exec.LookPath("strfry")
if err != nil {
return fmt.Errorf("failed to find strfry executable: %w (better provide it manually)", err)
}
s.ExecutablePath = exe
}
return nil
}
func (_ StrfryBackend) Close() {}
func (s StrfryBackend) QueryEvents(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
stdout, err := s.baseStrfryScan(ctx, filter)
if err != nil {
return nil, err
}
ch := make(chan *nostr.Event)
go func() {
defer close(ch)
for {
line, err := stdout.ReadBytes('\n')
if err != nil {
break
}
evt := &nostr.Event{}
easyjson.Unmarshal(line, evt)
if evt.ID == "" {
continue
}
ch <- evt
}
}()
return ch, nil
}
func (s StrfryBackend) SaveEvent(ctx context.Context, evt *nostr.Event) error {
args := make([]string, 0, 4)
if s.ConfigPath != "" {
args = append(args, "--config="+s.ConfigPath)
}
args = append(args, "import")
args = append(args, "--show-rejected")
args = append(args, "--no-verify")
cmd := exec.CommandContext(ctx, s.ExecutablePath, args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
// event is sent on stdin
j, _ := easyjson.Marshal(evt)
cmd.Stdin = bytes.NewBuffer(j)
err := cmd.Run()
if err != nil {
return fmt.Errorf(
"%s %s failed: %w, (%s)",
s.ExecutablePath, strings.Join(args, " "), err, stderr.String(),
)
}
return nil
}
func (s StrfryBackend) DeleteEvent(ctx context.Context, evt *nostr.Event) error {
args := make([]string, 0, 3)
if s.ConfigPath != "" {
args = append(args, "--config="+s.ConfigPath)
}
args = append(args, "delete")
args = append(args, "--filter={\"ids\":[\""+evt.ID+"\"]}")
cmd := exec.CommandContext(ctx, s.ExecutablePath, args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf(
"%s %s failed: %w, (%s)",
s.ExecutablePath, strings.Join(args, " "), err, stderr.String(),
)
}
return nil
}
func (s StrfryBackend) CountEvents(ctx context.Context, filter nostr.Filter) (int64, error) {
stdout, err := s.baseStrfryScan(ctx, filter)
if err != nil {
return 0, err
}
var count int64
for {
_, err := stdout.ReadBytes('\n')
if err != nil {
break
}
count++
}
return count, nil
}
func (s StrfryBackend) baseStrfryScan(ctx context.Context, filter nostr.Filter) (*bytes.Buffer, error) {
args := make([]string, 0, 3)
if s.ConfigPath != "" {
args = append(args, "--config="+s.ConfigPath)
}
args = append(args, "scan")
args = append(args, filter.String())
cmd := exec.CommandContext(ctx, s.ExecutablePath, args...)
var stdout bytes.Buffer
cmd.Stdout = &stdout
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return nil, fmt.Errorf(
"%s %s failed: %w, (%s)",
s.ExecutablePath, strings.Join(args, " "), err, stderr.String(),
)
}
return &stdout, nil
}