package dto
import (
"bytes"
"encoding/hex"
"strconv"
"strings"
"github.com/navidrome/navidrome/model/id"
)
const guidLen = 32
const (
kindIdx = 12
maxPayload = 1<<24 - 1
)
const (
kindLibrary byte = iota + 1
kindPlaylistsFolder
kindPlaylistEntry
)
var zeroPrefix [kindIdx]byte
const PlaylistsFolderID = "playlists"
var PlaylistsFolderGUID = encodeReserved(kindPlaylistsFolder, 0)
func EncodeID(ndID string) string {
b, err := id.Decode(ndID)
if err != nil {
return ""
}
return hex.EncodeToString(b)
}
func EncodeLibraryID(libID int) string {
return encodeReserved(kindLibrary, libID)
}
func EncodePlaylistEntryID(entryID string) string {
n, err := strconv.Atoi(entryID)
if err != nil {
return ""
}
return encodeReserved(kindPlaylistEntry, n)
}
func encodeReserved(kind byte, payload int) string {
if payload < 0 || payload > maxPayload {
return ""
}
var b [16]byte
b[kindIdx] = kind
b[13], b[14], b[15] = byte(payload>>16), byte(payload>>8), byte(payload)
return hex.EncodeToString(b[:])
}
func DecodeID(guid string) (string, bool) {
b, ok := decodeGUID(guid)
if !ok {
return "", false
}
kind, payload, reserved := reservedFields(b)
if !reserved {
return id.Encode(b), true
}
switch kind {
case kindLibrary:
return strconv.Itoa(payload), true
case kindPlaylistsFolder:
return PlaylistsFolderID, true
}
return "", false
}
func DecodeIDs(guids []string) ([]string, bool) {
out := make([]string, len(guids))
for i, guid := range guids {
decoded, ok := DecodeID(guid)
if !ok {
return nil, false
}
out[i] = decoded
}
return out, true
}
func DecodePlaylistEntryID(guid string) (string, bool) {
b, ok := decodeGUID(guid)
if !ok {
return "", false
}
if kind, payload, reserved := reservedFields(b); reserved && kind == kindPlaylistEntry {
return strconv.Itoa(payload), true
}
return "", false
}
func decodeGUID(guid string) ([16]byte, bool) {
guid = strings.ToLower(strings.ReplaceAll(guid, "-", ""))
if len(guid) != guidLen {
return [16]byte{}, false
}
bs, err := hex.DecodeString(guid)
if err != nil {
return [16]byte{}, false
}
return [16]byte(bs), true
}
func reservedFields(b [16]byte) (kind byte, payload int, reserved bool) {
if !bytes.Equal(b[:kindIdx], zeroPrefix[:]) {
return 0, 0, false
}
return b[kindIdx], int(b[13])<<16 | int(b[14])<<8 | int(b[15]), true
}