summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--cmd/cmd.go25
-rw-r--r--cmd/init.go4
-rw-r--r--cmd/main.go2
-rw-r--r--files/database.go246
-rw-r--r--files/index.go56
-rw-r--r--files/operations.go143
-rw-r--r--files/types.go6
-rw-r--r--go.mod26
-rw-r--r--go.sum70
10 files changed, 382 insertions, 197 deletions
diff --git a/.gitignore b/.gitignore
index 6f72f89..a80ed40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ go.work.sum
# env file
.env
+.vscode/launch.json
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 82a9cd5..974770d 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -5,33 +5,24 @@ import (
"os"
"os/signal"
- "github.com/justjakka/reencoder/files"
- "github.com/tidwall/buntdb"
+ badger "github.com/dgraph-io/badger/v4"
"github.com/urfave/cli/v2"
+
+ "github.com/justjakka/reencoder/files"
)
func runCmd(cCtx *cli.Context) error {
- ctx, err := initCmd(cCtx)
+ ctx, err := initArgs(cCtx)
if err != nil {
return err
}
- db, err := buntdb.Open(ctx.Value("dbfile").(string))
+ db, err := badger.Open(files.Options(ctx.Value("dbfile").(string)))
if err != nil {
return err
}
defer db.Close()
- if err := db.Shrink(); err != nil {
- return err
- }
-
- if err := db.CreateIndex("process", "*", buntdb.IndexJSON("process")); err != nil {
- if err != buntdb.ErrIndexExists {
- return err
- }
- }
-
ctx = context.WithValue(ctx, "database", db)
ctx, stop := signal.NotifyContext(ctx, os.Interrupt)
@@ -41,6 +32,10 @@ func runCmd(cCtx *cli.Context) error {
return err
}
+ if err = files.ReencodeFlacs(ctx); err != nil {
+ return err
+ }
+
return nil
/*
@@ -63,7 +58,7 @@ func runCmd(cCtx *cli.Context) error {
return cCtx.Err() */
}
-func start() {
+func Start() {
app := &cli.App{
Name: "reencoder",
Usage: "reencodes and stores info",
diff --git a/cmd/init.go b/cmd/init.go
index 18e3b65..2459315 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -34,7 +34,7 @@ func getDb(cCtx *cli.Context) (context.Context, error) {
return context.WithValue(cCtx.Context, "dbfile", ""), errors.New("failed to locate application data folder")
}
- return context.WithValue(cCtx.Context, "dbfile", filepath.Join(localFolder, "reencoder.db")), nil
+ return context.WithValue(cCtx.Context, "dbfile", filepath.Join(localFolder, "reencoder")), nil
}
if _, err := os.Stat(cCtx.Path("dbfile")); err != nil {
return context.WithValue(cCtx.Context, "dbfile", ""), err
@@ -52,7 +52,7 @@ func checkTools() error {
return nil
}
-func initCmd(cCtx *cli.Context) (context.Context, error) {
+func initArgs(cCtx *cli.Context) (context.Context, error) {
if err := checkTools(); err != nil {
return nil, err
}
diff --git a/cmd/main.go b/cmd/main.go
index 6d4c482..52cefc8 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -1,5 +1,5 @@
package main
func main() {
- start()
+ Start()
}
diff --git a/files/database.go b/files/database.go
index 24bd5af..1ad40aa 100644
--- a/files/database.go
+++ b/files/database.go
@@ -2,125 +2,201 @@ package files
import (
"context"
- "crypto/sha256"
"encoding/json"
- "fmt"
- "io"
- "io/fs"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
+ "time"
- "github.com/tidwall/buntdb"
+ badger "github.com/dgraph-io/badger/v4"
+ "github.com/dgraph-io/badger/v4/options"
+ "golang.org/x/sync/errgroup"
)
-func evaluateFile(database *buntdb.DB, hashsum string, filedata FileInfo) error {
- return database.View(func(tx *buntdb.Tx) error {
- data, err := tx.Get(hashsum)
- if err != nil {
+func decodeDbInfo(item *badger.Item) (*FileInfo, error) {
+ var info FileInfo
+ if err := item.Value(func(val []byte) error {
+ if err := json.Unmarshal(val, &info); err != nil {
return err
}
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return &info, nil
+}
- var info FileInfo
- if err := json.Unmarshal([]byte(data), &info); err != nil {
+func getInfoFromDb(database *badger.DB, hashsum []byte) (*FileInfo, error) {
+ var info *FileInfo
+ if err := database.View(func(txn *badger.Txn) error {
+ item, err := txn.Get(hashsum)
+ if err != nil {
return err
}
- if info == filedata {
- return UpToDate
- }
- switch info.Modtime == filedata.Modtime {
- case true:
- if info.AbsPath == filedata.AbsPath {
- if info.Process {
- return NeedsReencode
- }
- return UpToDate
- }
- if info.Process {
- return NeedsReencode
- }
- return MovedFile
- default:
- return nil
+
+ info, err = decodeDbInfo(item)
+ if err != nil {
+ return err
}
- })
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return info, nil
}
-func getEncoderVer(path string) (string, error) {
- out, err := exec.Command("metaflac", "--show-vendor-tag", path).Output()
+func (filedata *FileInfo) evaluateFile(database *badger.DB, hashsum []byte, encoder string) error {
+ info, err := getInfoFromDb(database, hashsum)
if err != nil {
- return "", err
+ return err
}
- r := regexp.MustCompile("libFLAC \\d\\.\\d\\.\\d")
- encoder := r.FindString(string(out))
- switch encoder {
- case "":
- return "", nil
- default:
- return strings.Split(encoder, " ")[1], nil
+ if info.Process {
+ return ReencodeNeeded
+ }
+ if filedata.Encoder != encoder {
+ return ReencodeNeeded
}
+ if info.AbsPath != filedata.AbsPath {
+ return FileMoved
+ }
+ return ReencodeNotNeeded
}
-func updateFile(database *buntdb.DB, hashsum string, filedata FileInfo) error {
- return database.Update(func(tx *buntdb.Tx) error {
- data, err := json.Marshal(filedata)
+func (filedata *FileInfo) updateFile(database *badger.DB, hashsum []byte) error {
+ return database.Update(func(txn *badger.Txn) error {
+ encoded, err := json.Marshal(filedata)
if err != nil {
return err
}
- if _, _, err := tx.Set(hashsum, string(data), nil); err != nil {
- return err
- }
- return nil
+ return txn.Set(hashsum, encoded)
})
}
-func ProcessFile(ctx context.Context, path string, info fs.DirEntry) error {
- tmp, err := info.Info()
- if err != nil {
- return err
- }
+func (filedata *FileInfo) IndexFile(ctx context.Context, hashsum []byte) error {
+ database := ctx.Value("database").(*badger.DB)
+ encoder := ctx.Value("encoder").(string)
- modtime := tmp.ModTime()
-
- database := ctx.Value("database").(*buntdb.DB)
+ err := filedata.evaluateFile(database, hashsum, encoder)
+ switch err {
+ case FileMoved, ReencodeNotNeeded:
+ filedata.Process = false
+ case ReencodeNeeded, badger.ErrKeyNotFound:
- file, err := os.Open(path)
- if err != nil {
+ default:
return err
}
- defer file.Close()
-
- hash := sha256.New()
- if _, err := io.Copy(hash, file); err != nil {
+ if err := filedata.updateFile(database, hashsum); err != nil {
return err
}
- hashsum := fmt.Sprintf("%x", hash.Sum(nil))
+ return nil
+}
- abspath, err := filepath.Abs(path)
- if err != nil {
- return err
- }
+func ReencodeFlacs(ctx context.Context) error {
+ db := ctx.Value("database").(*badger.DB)
+ wg := new(errgroup.Group)
+ wg.SetLimit(4)
+ err := db.View(func(txn *badger.Txn) error {
+ opts := badger.DefaultIteratorOptions
+ opts.PrefetchSize = 10
+ it := txn.NewIterator(opts)
+ defer it.Close()
- encoder, err := getEncoderVer(path)
+ for it.Rewind(); it.Valid(); it.Next() {
+ /* item := it.Item()
+ key := item.Key()
+ info, err := decodeDbInfo(item)
+ if err != nil {
+ return err
+ }
+ if err := info.reencodeFile(ctx); err != nil {
+ return err
+ }
+ if err := info.updateFile(db, key); err != nil {
+ return err
+ } */
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ item := it.Item()
+ key := item.Key()
+ info, err := decodeDbInfo(item)
+ if err != nil {
+ return err
+ }
+ wg.Go(func() error {
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ if err := info.reencodeFile(ctx); err != nil {
+ return err
+ }
+ if err := info.updateFile(db, key); err != nil {
+ return err
+ }
+ return nil
+ }
+ })
+ }
+ }
+ return nil
+ })
+ wg.Wait()
+ return err
+}
- filedata := FileInfo{AbsPath: abspath, Modtime: modtime, Encoder: encoder, Process: true}
+func Options(path string) badger.Options {
+ return badger.Options{
+ Dir: path,
+ ValueDir: path,
- err = evaluateFile(database, hashsum, filedata)
- switch err {
- case MovedFile, UpToDate:
- if filedata.Encoder == ctx.Value("encoder").(string) {
- return nil
- }
- case buntdb.ErrNotFound, NeedsReencode:
+ MemTableSize: 64 << 20,
+ BaseTableSize: 2 << 20,
+ BaseLevelSize: 10 << 20,
+ TableSizeMultiplier: 2,
+ LevelSizeMultiplier: 10,
+ MaxLevels: 7,
+ NumGoroutines: 8,
+ MetricsEnabled: true,
- default:
- return err
- }
- if err := updateFile(database, hashsum, filedata); err != nil {
- return err
+ NumCompactors: 4, // Run at least 2 compactors. Zero-th compactor prioritizes L0.
+ NumLevelZeroTables: 5,
+ NumLevelZeroTablesStall: 15,
+ NumMemtables: 5,
+ BloomFalsePositive: 0.01,
+ BlockSize: 4 * 1024,
+ SyncWrites: false,
+ NumVersionsToKeep: 1,
+ CompactL0OnClose: false,
+ VerifyValueChecksum: false,
+ Compression: options.Snappy,
+ BlockCacheSize: 256 << 20,
+ IndexCacheSize: 0,
+
+ // The following benchmarks were done on a 4 KB block size (default block size). The
+ // compression is ratio supposed to increase with increasing compression level but since the
+ // input for compression algorithm is small (4 KB), we don't get significant benefit at
+ // level 3.
+ // NOTE: The benchmarks are with DataDog ZSTD that requires CGO. Hence, no longer valid.
+ // no_compression-16 10 502848865 ns/op 165.46 MB/s -
+ // zstd_compression/level_1-16 7 739037966 ns/op 112.58 MB/s 2.93
+ // zstd_compression/level_3-16 7 756950250 ns/op 109.91 MB/s 2.72
+ // zstd_compression/level_15-16 1 11135686219 ns/op 7.47 MB/s 4.38
+ // Benchmark code can be found in table/builder_test.go file
+ ZSTDCompressionLevel: 1,
+
+ // (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32.
+ // -1 so 2*ValueLogFileSize won't overflow on 32-bit systems.
+ ValueLogFileSize: 1<<30 - 1,
+
+ ValueLogMaxEntries: 1000000,
+
+ VLogPercentile: 0.0,
+ ValueThreshold: (1 << 20),
+
+ Logger: nil,
+ EncryptionKey: []byte{},
+ EncryptionKeyRotationDuration: 10 * 24 * time.Hour, // Default 10 days.
+ DetectConflicts: true,
+ NamespaceOffset: -1,
}
- return nil
}
diff --git a/files/index.go b/files/index.go
deleted file mode 100644
index ea05dc4..0000000
--- a/files/index.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package files
-
-import (
- "context"
- "io/fs"
- "path/filepath"
- "time"
-
- "github.com/briandowns/spinner"
- "golang.org/x/sync/errgroup"
-)
-
-func IndexFlacs(ctx context.Context) error {
- spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
- spin.Suffix = " Indexing flacs..."
-
- spin.Start()
-
- wg := new(errgroup.Group)
- wg.SetLimit(100)
-
- if err := filepath.WalkDir(ctx.Value("path").(string), func(path string, info fs.DirEntry, err error) error {
- select {
- case <-ctx.Done():
- spin.FinalMSG = "Stopping...\n"
- spin.Stop()
- return nil
- default:
- if err != nil {
- return err
- }
- if !info.IsDir() {
- if filepath.Ext(path) == ".flac" {
- wg.Go(func() error {
- select {
- case <-ctx.Done():
- return nil
- default:
- return ProcessFile(ctx, path, info)
- }
- })
- }
-
- }
- return nil
- }
-
- }); err != nil {
- return err
- }
-
- wg.Wait()
- spin.FinalMSG = "Done indexing flacs"
- spin.Stop()
- return nil
-}
diff --git a/files/operations.go b/files/operations.go
new file mode 100644
index 0000000..3da6586
--- /dev/null
+++ b/files/operations.go
@@ -0,0 +1,143 @@
+package files
+
+import (
+ "context"
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/briandowns/spinner"
+ "golang.org/x/sync/errgroup"
+)
+
+func (file *FileInfo) reencodeFile(ctx context.Context) error {
+ /* cmd := exec.Command("flac", "-8", "-f", file.AbsPath)
+ if err := cmd.Run(); err != nil {
+ log.Printf("%s\n", err.Error())
+ return err
+ }
+ file.Encoder = ctx.Value("encoder").(string) */
+ fmt.Println(file)
+ return nil
+}
+
+func getEncoderVer(path string) (string, error) {
+ out, err := exec.Command("metaflac", "--show-vendor-tag", path).Output()
+ if err != nil {
+ return "", err
+ }
+
+ r := regexp.MustCompile("libFLAC \\d\\.\\d\\.\\d")
+ encoder := r.FindString(string(out))
+ switch encoder {
+ case "":
+ return "", nil
+ default:
+ return strings.Split(encoder, " ")[1], nil
+ }
+}
+
+func getInfoFromFile(path string, info fs.DirEntry) (*FileInfo, error) {
+ var filedata FileInfo
+
+ filedata.Process = true
+
+ tmp, err := info.Info()
+ if err != nil {
+ return nil, err
+ }
+
+ filedata.Modtime = tmp.ModTime()
+
+ abspath, err := filepath.Abs(path)
+ if err != nil {
+ return nil, err
+ }
+
+ filedata.AbsPath = abspath
+
+ encoder, err := getEncoderVer(path)
+ if err != nil {
+ return nil, err
+ }
+
+ filedata.Encoder = encoder
+
+ return &filedata, nil
+}
+
+func getSha256(path string) ([]byte, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ hash := sha256.New()
+ if _, err := io.Copy(hash, file); err != nil {
+ return nil, err
+ }
+ return hash.Sum(nil), nil
+}
+
+func IndexFlacs(ctx context.Context) error {
+ spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+ spin.Suffix = " Indexing flacs..."
+
+ spin.Start()
+
+ wg := new(errgroup.Group)
+ wg.SetLimit(100)
+
+ if err := filepath.WalkDir(ctx.Value("path").(string), func(path string, info fs.DirEntry, err error) error {
+ select {
+ case <-ctx.Done():
+ spin.FinalMSG = "Stopping...\n"
+ spin.Stop()
+ return filepath.SkipAll
+ default:
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ if filepath.Ext(path) == ".flac" {
+ wg.Go(func() error {
+ select {
+ case <-ctx.Done():
+ return nil
+ default:
+ data, err := getInfoFromFile(path, info)
+ if err != nil {
+ return err
+ }
+
+ hashsum, err := getSha256(path)
+ if err != nil {
+ return err
+ }
+
+ return data.IndexFile(ctx, hashsum)
+ }
+ })
+ }
+
+ }
+ return nil
+ }
+
+ }); err != nil {
+ return err
+ }
+
+ wg.Wait()
+ spin.FinalMSG = "Done indexing flacs\n"
+ spin.Stop()
+ return nil
+}
diff --git a/files/types.go b/files/types.go
index 54d8563..87ae6e1 100644
--- a/files/types.go
+++ b/files/types.go
@@ -6,9 +6,9 @@ import (
)
var (
- UpToDate = errors.New("file is up to date")
- MovedFile = errors.New("file was moved")
- NeedsReencode = errors.New("file needs to be reencoded")
+ ReencodeNotNeeded = errors.New("file is up to date")
+ FileMoved = errors.New("file was moved")
+ ReencodeNeeded = errors.New("file needs to be reencoded")
)
type FileInfo struct {
diff --git a/go.mod b/go.mod
index 0c1e6dd..e7345d0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,25 +5,31 @@ go 1.24.2
require github.com/urfave/cli/v2 v2.27.6
require (
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.7.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/google/flatbuffers v25.2.10+incompatible // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/tidwall/btree v1.4.2 // indirect
- github.com/tidwall/gjson v1.14.3 // indirect
- github.com/tidwall/grect v0.1.4 // indirect
- github.com/tidwall/match v1.1.1 // indirect
- github.com/tidwall/pretty v1.2.0 // indirect
- github.com/tidwall/rtred v0.1.2 // indirect
- github.com/tidwall/tinyqueue v0.1.1 // indirect
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/term v0.28.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/otel v1.35.0 // indirect
+ go.opentelemetry.io/otel/metric v1.35.0 // indirect
+ go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/term v0.30.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
)
require (
github.com/briandowns/spinner v1.23.2
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
+ github.com/dgraph-io/badger/v4 v4.7.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/tidwall/buntdb v1.3.2
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/sync v0.13.0
)
diff --git a/go.sum b/go.sum
index c088acc..1723f5b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,46 +1,66 @@
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=
+github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=
+github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
+github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
+github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
-github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
-github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
-github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
-github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
-github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
-github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
-github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
-github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
-github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
-github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
-github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
-github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
-github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
-github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
-github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
-github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
-golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=