mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
refactor: replace md5 with xxh3 for faster and more efficient hashing
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
2dab4b4048
commit
6d8a3e48ee
@ -27,6 +27,9 @@ linters:
|
||||
disable:
|
||||
- staticcheck
|
||||
settings:
|
||||
errcheck:
|
||||
exclude-functions:
|
||||
- (*github.com/zeebo/xxh3.Hasher).Write
|
||||
gocritic:
|
||||
disable-all: true
|
||||
enabled-checks:
|
||||
|
||||
@ -2,8 +2,6 @@ package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
@ -16,6 +14,7 @@ import (
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
// StaleAbsentAge is how long an absent state is trusted before a recheck retries it.
|
||||
@ -66,8 +65,7 @@ func FingerprintInputs() []FingerprintInput {
|
||||
func ConfigFingerprint() string {
|
||||
values := slice.Map(FingerprintInputs(), func(i FingerprintInput) string { return i.Value })
|
||||
raw := fmt.Sprintf("%s|%d", strings.Join(values, "|"), artworkEpoch)
|
||||
sum := md5.Sum([]byte(raw)) //nolint:gosec // fingerprint, not security-sensitive
|
||||
return hex.EncodeToString(sum[:])
|
||||
return fmt.Sprintf("%016x", xxh3.Hash([]byte(raw)))
|
||||
}
|
||||
|
||||
// backfill enqueues artwork resolution for every entity when the config fingerprint changed.
|
||||
|
||||
@ -135,7 +135,7 @@ var _ = Describe("Housekeeping", func() {
|
||||
conf.Server.EnableExternalServices = true
|
||||
conf.Server.EnableM3UExternalAlbumArt = false
|
||||
|
||||
Expect(ConfigFingerprint()).To(Equal("7e537a22febc07d3d5ca40546e88da54"))
|
||||
Expect(ConfigFingerprint()).To(Equal("7b538a83a870c16d"))
|
||||
})
|
||||
|
||||
It("reports the config inputs it hashes, so a change can be traced to a setting", func() {
|
||||
|
||||
@ -2,7 +2,6 @@ package model
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
@ -20,6 +19,7 @@ import (
|
||||
"github.com/navidrome/navidrome/utils/gg"
|
||||
"github.com/navidrome/navidrome/utils/number"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
type MediaFile struct {
|
||||
@ -232,7 +232,7 @@ func (mf MediaFile) Hash() string {
|
||||
ZeroNil: true,
|
||||
}
|
||||
hash, _ := hashstructure.Hash(mf, opts)
|
||||
sum := md5.New()
|
||||
sum := xxh3.New()
|
||||
sum.Write(fmt.Appendf(nil, "%d", hash))
|
||||
sum.Write(mf.Tags.Hash())
|
||||
sum.Write(mf.Participants.Hash())
|
||||
|
||||
@ -715,14 +715,10 @@ var _ = Describe("MediaFile.Movements", func() {
|
||||
})
|
||||
|
||||
var _ = Describe("MediaFile.Hash", func() {
|
||||
// Guards the upgrade guarantee: converting BPM/BitDepth from int to *int must not change hashes,
|
||||
// or every file would be spuriously re-imported on the next scan.
|
||||
// Golden hashes were captured at 46221d516 when those fields were plain ints.
|
||||
It("keeps hashes identical to the pre-pointer-conversion values", func() {
|
||||
// Golden hashes computed at 46221d516, when BPM/BitDepth were plain ints — pinning
|
||||
// them guarantees the pointer conversion cannot trigger a full-library re-import.
|
||||
Expect(MediaFile{Title: "Song"}.Hash()).To(Equal("1d856ced42cb96db39e354a4bac9a622"))
|
||||
Expect(MediaFile{Title: "Song", BPM: new(120), BitDepth: new(16)}.Hash()).To(Equal("b2b0b1d1dd7fd767093588e4af3a0689"))
|
||||
// Pins the hash formula: an accidental change spuriously re-imports every file on the next scan.
|
||||
It("hashes to a stable value", func() {
|
||||
Expect(MediaFile{Title: "Song"}.Hash()).To(Equal("05fdf70bb0cbe090"))
|
||||
Expect(MediaFile{Title: "Song", BPM: new(120), BitDepth: new(16)}.Hash()).To(Equal("b5daf6ac1009a538"))
|
||||
})
|
||||
It("changes the hash when a pointer field has a value", func() {
|
||||
base := MediaFile{Title: "Song"}
|
||||
|
||||
@ -2,12 +2,12 @@ package model
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -193,7 +193,7 @@ func (p Participants) Hash() []byte {
|
||||
flattened = append(flattened, role.String()+":"+strings.Join(ids, "/"))
|
||||
}
|
||||
slices.Sort(flattened)
|
||||
sum := md5.New()
|
||||
sum := xxh3.New()
|
||||
sum.Write([]byte(strings.Join(flattened, "|")))
|
||||
return sum.Sum(nil)
|
||||
}
|
||||
|
||||
@ -2,13 +2,13 @@ package model
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/model/id"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
@ -117,7 +117,7 @@ func (t Tags) Hash() []byte {
|
||||
}
|
||||
ids := t.IDs()
|
||||
slices.Sort(ids)
|
||||
sum := md5.New()
|
||||
sum := xxh3.New()
|
||||
sum.Write([]byte(strings.Join(ids, "|")))
|
||||
return sum.Sum(nil)
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -23,6 +22,7 @@ import (
|
||||
"github.com/navidrome/navidrome/utils/hasher"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
// sqlRepository is the base repository for all SQL repositories. It provides common functions to interact with the DB.
|
||||
@ -298,8 +298,8 @@ func (r sqlRepository) visibleLibraryIDs() ([]int, error) {
|
||||
func (r sqlRepository) seedKey() string {
|
||||
// Seed keys must be all lowercase, or else SQLite3 will encode it, making it not match the seed
|
||||
// used in the query. Hashing the user ID and converting it to a hex string will do the trick
|
||||
userIDHash := md5.Sum([]byte(loggedUser(r.ctx).ID))
|
||||
return fmt.Sprintf("%s|%x", r.tableName, userIDHash)
|
||||
userIDHash := xxh3.Hash([]byte(loggedUser(r.ctx).ID))
|
||||
return fmt.Sprintf("%s|%016x", r.tableName, userIDHash)
|
||||
}
|
||||
|
||||
func (r sqlRepository) resetSeededRandom(options []model.QueryOptions) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user