move the id type into spec

This commit is contained in:
sentriz
2020-05-03 00:59:19 +01:00
committed by Senan Kelly
parent 26457aae6c
commit 1ef2d43d39
9 changed files with 167 additions and 136 deletions

View File

@@ -9,6 +9,7 @@ import (
"go.senan.xyz/gonic/server/ctrlsubsonic/params" "go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec" "go.senan.xyz/gonic/server/ctrlsubsonic/spec"
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
"go.senan.xyz/gonic/server/db" "go.senan.xyz/gonic/server/db"
"go.senan.xyz/gonic/server/lastfm" "go.senan.xyz/gonic/server/lastfm"
) )
@@ -263,9 +264,11 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
if gorm.IsRecordNotFoundError(err) && !inclNotPresent { if gorm.IsRecordNotFoundError(err) && !inclNotPresent {
continue continue
} }
similar := &spec.SimilarArtist{ID: -1} similar := &spec.SimilarArtist{
ID: specid.ID{Type: specid.Artist, Value: -1},
}
if artist.ID != 0 { if artist.ID != 0 {
similar.ID = artist.ID similar.ID = artist.SID()
} }
similar.Name = similarInfo.Name similar.Name = similarInfo.Name
similar.AlbumCount = artist.AlbumCount similar.AlbumCount = artist.AlbumCount

View File

@@ -30,15 +30,15 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"go.senan.xyz/gonic/server/ids" "go.senan.xyz/gonic/server/ctrlsubsonic/specid"
) )
// some thin wrappers // some thin wrappers
// may be needed when cleaning up parse() below // may be needed when cleaning up parse() below
func parseStr(in string) (string, error) { return in, nil } func parseStr(in string) (string, error) { return in, nil }
func parseInt(in string) (int, error) { return strconv.Atoi(in) } func parseInt(in string) (int, error) { return strconv.Atoi(in) }
func parseID(in string) (ids.IDV, error) { return ids.Parse(in) } func parseID(in string) (specid.ID, error) { return specid.New(in) }
func parseBool(in string) (bool, error) { return strconv.ParseBool(in) } func parseBool(in string) (bool, error) { return strconv.ParseBool(in) }
func parse(values []string, i interface{}) error { func parse(values []string, i interface{}) error {
if len(values) == 0 { if len(values) == 0 {
@@ -50,7 +50,7 @@ func parse(values []string, i interface{}) error {
*v, err = parseStr(values[0]) *v, err = parseStr(values[0])
case *int: case *int:
*v, err = parseInt(values[0]) *v, err = parseInt(values[0])
case *ids.IDV: case *specid.ID:
*v, err = parseID(values[0]) *v, err = parseID(values[0])
case *bool: case *bool:
*v, err = parseBool(values[0]) *v, err = parseBool(values[0])
@@ -70,7 +70,7 @@ func parse(values []string, i interface{}) error {
} }
*v = append(*v, parsed) *v = append(*v, parsed)
} }
case *[]ids.IDV: case *[]specid.ID:
for _, value := range values { for _, value := range values {
parsed, err := parseID(value) parsed, err := parseID(value)
if err != nil { if err != nil {
@@ -229,56 +229,56 @@ func (p Params) GetFirstOrIntList(or []int, keys ...string) []int {
return or return or
} }
// ** begin ids.IDV {get, get first, get or, get first or} // ** begin specid.ID {get, get first, get or, get first or}
func (p Params) GetID(key string) (ids.IDV, error) { func (p Params) GetID(key string) (specid.ID, error) {
var ret ids.IDV var ret specid.ID
return ret, parse(p.get(key), &ret) return ret, parse(p.get(key), &ret)
} }
func (p Params) GetFirstID(keys ...string) (ids.IDV, error) { func (p Params) GetFirstID(keys ...string) (specid.ID, error) {
var ret ids.IDV var ret specid.ID
return ret, parse(p.getFirst(keys), &ret) return ret, parse(p.getFirst(keys), &ret)
} }
func (p Params) GetOrID(key string, or ids.IDV) ids.IDV { func (p Params) GetOrID(key string, or specid.ID) specid.ID {
var ret ids.IDV var ret specid.ID
if err := parse(p.get(key), &ret); err == nil { if err := parse(p.get(key), &ret); err == nil {
return ret return ret
} }
return or return or
} }
func (p Params) GetFirstOrID(or ids.IDV, keys ...string) ids.IDV { func (p Params) GetFirstOrID(or specid.ID, keys ...string) specid.ID {
var ret ids.IDV var ret specid.ID
if err := parse(p.getFirst(keys), &ret); err == nil { if err := parse(p.getFirst(keys), &ret); err == nil {
return ret return ret
} }
return or return or
} }
// ** begin []ids.IDV {get, get first, get or, get first or} // ** begin []specid.ID {get, get first, get or, get first or}
func (p Params) GetIDList(key string) ([]ids.IDV, error) { func (p Params) GetIDList(key string) ([]specid.ID, error) {
var ret []ids.IDV var ret []specid.ID
return ret, parse(p.get(key), &ret) return ret, parse(p.get(key), &ret)
} }
func (p Params) GetFirstIDList(keys ...string) ([]ids.IDV, error) { func (p Params) GetFirstIDList(keys ...string) ([]specid.ID, error) {
var ret []ids.IDV var ret []specid.ID
return ret, parse(p.getFirst(keys), &ret) return ret, parse(p.getFirst(keys), &ret)
} }
func (p Params) GetOrIDList(key string, or []ids.IDV) []ids.IDV { func (p Params) GetOrIDList(key string, or []specid.ID) []specid.ID {
var ret []ids.IDV var ret []specid.ID
if err := parse(p.get(key), &ret); err == nil { if err := parse(p.get(key), &ret); err == nil {
return ret return ret
} }
return or return or
} }
func (p Params) GetFirstOrIDList(or []ids.IDV, keys ...string) []ids.IDV { func (p Params) GetFirstOrIDList(or []specid.ID, keys ...string) []specid.ID {
var ret []ids.IDV var ret []specid.ID
if err := parse(p.getFirst(keys), &ret); err == nil { if err := parse(p.getFirst(keys), &ret); err == nil {
return ret return ret
} }

View File

@@ -3,42 +3,41 @@ package spec
import ( import (
"path" "path"
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/db" "go.senan.xyz/gonic/server/db"
) )
func NewAlbumByFolder(f *db.Album) *Album { func NewAlbumByFolder(f *db.Album) *Album {
a := &Album{ a := &Album{
Artist: f.Parent.RightPath, Artist: f.Parent.RightPath,
ID: params.IDAlbum(f.ID), ID: f.SID(),
IsDir: true, IsDir: true,
ParentID: params.IDAlbum(f.ParentID), ParentID: f.ParentSID(),
Title: f.RightPath, Title: f.RightPath,
TrackCount: f.ChildCount, TrackCount: f.ChildCount,
} }
if f.Cover != "" { if f.Cover != "" {
a.CoverID = f.ID a.CoverID = f.SID()
} }
return a return a
} }
func NewTCAlbumByFolder(f *db.Album) *TrackChild { func NewTCAlbumByFolder(f *db.Album) *TrackChild {
trCh := &TrackChild{ trCh := &TrackChild{
ID: params.IDAlbum(f.ID), ID: f.SID(),
IsDir: true, IsDir: true,
Title: f.RightPath, Title: f.RightPath,
ParentID: params.IDAlbum(f.ParentID), ParentID: f.ParentSID(),
CreatedAt: f.UpdatedAt, CreatedAt: f.UpdatedAt,
} }
if f.Cover != "" { if f.Cover != "" {
trCh.CoverID = f.ID trCh.CoverID = f.SID()
} }
return trCh return trCh
} }
func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild { func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
trCh := &TrackChild{ trCh := &TrackChild{
ID: params.IDTrack(t.ID), ID: t.SID(),
ContentType: t.MIME(), ContentType: t.MIME(),
Suffix: t.Ext(), Suffix: t.Ext(),
Size: t.Size, Size: t.Size,
@@ -51,7 +50,7 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
parent.RightPath, parent.RightPath,
t.Filename, t.Filename,
), ),
ParentID: params.IDAlbum(parent.ID), ParentID: parent.SID(),
Duration: t.Length, Duration: t.Length,
Bitrate: t.Bitrate, Bitrate: t.Bitrate,
IsDir: false, IsDir: false,
@@ -59,7 +58,7 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
CreatedAt: t.CreatedAt, CreatedAt: t.CreatedAt,
} }
if parent.Cover != "" { if parent.Cover != "" {
trCh.CoverID = parent.ID trCh.CoverID = parent.SID()
} }
if t.Album != nil { if t.Album != nil {
trCh.Album = t.Album.RightPath trCh.Album = t.Album.RightPath
@@ -73,7 +72,7 @@ func NewArtistByFolder(f *db.Album) *Artist {
// from an "album" where // from an "album" where
// maybe TODO: rename the Album model to Folder // maybe TODO: rename the Album model to Folder
return &Artist{ return &Artist{
ID: params.IDAlbum(f.ID), ID: f.SID(),
Name: f.RightPath, Name: f.RightPath,
AlbumCount: f.ChildCount, AlbumCount: f.ChildCount,
} }
@@ -81,13 +80,13 @@ func NewArtistByFolder(f *db.Album) *Artist {
func NewDirectoryByFolder(f *db.Album, children []*TrackChild) *Directory { func NewDirectoryByFolder(f *db.Album, children []*TrackChild) *Directory {
dir := &Directory{ dir := &Directory{
ID: f.ID, ID: f.SID(),
Name: f.RightPath, Name: f.RightPath,
Children: children, Children: children,
} }
// don't show the root dir as a parent // don't show the root dir as a parent
if f.ParentID != 1 { if f.ParentID != 1 {
dir.ParentID = f.ParentID dir.ParentID = f.ParentSID()
} }
return dir return dir
} }

View File

@@ -9,7 +9,7 @@ import (
func NewAlbumByTags(a *db.Album, artist *db.Artist) *Album { func NewAlbumByTags(a *db.Album, artist *db.Artist) *Album {
ret := &Album{ ret := &Album{
Created: a.ModifiedAt, Created: a.ModifiedAt,
ID: a.ID, ID: a.SID(),
Name: a.TagTitle, Name: a.TagTitle,
Year: a.TagYear, Year: a.TagYear,
TrackCount: a.ChildCount, TrackCount: a.ChildCount,
@@ -18,21 +18,21 @@ func NewAlbumByTags(a *db.Album, artist *db.Artist) *Album {
ret.Genre = a.TagGenre.Name ret.Genre = a.TagGenre.Name
} }
if a.Cover != "" { if a.Cover != "" {
ret.CoverID = a.ID ret.CoverID = a.SID()
} }
if artist != nil { if artist != nil {
ret.Artist = artist.Name ret.Artist = artist.Name
ret.ArtistID = artist.ID ret.ArtistID = artist.SID()
} }
return ret return ret
} }
func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild { func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
ret := &TrackChild{ ret := &TrackChild{
ID: t.ID, ID: t.SID(),
ContentType: t.MIME(), ContentType: t.MIME(),
Suffix: t.Ext(), Suffix: t.Ext(),
ParentID: t.AlbumID, ParentID: t.AlbumSID(),
CreatedAt: t.CreatedAt, CreatedAt: t.CreatedAt,
Size: t.Size, Size: t.Size,
Title: t.TagTitle, Title: t.TagTitle,
@@ -45,16 +45,16 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
t.Filename, t.Filename,
), ),
Album: album.TagTitle, Album: album.TagTitle,
AlbumID: album.ID, AlbumID: album.SID(),
Duration: t.Length, Duration: t.Length,
Bitrate: t.Bitrate, Bitrate: t.Bitrate,
Type: "music", Type: "music",
} }
if album.Cover != "" { if album.Cover != "" {
ret.CoverID = album.ID ret.CoverID = album.SID()
} }
if album.TagArtist != nil { if album.TagArtist != nil {
ret.ArtistID = album.TagArtist.ID ret.ArtistID = album.TagArtist.SID()
} }
// replace tags that we're present // replace tags that we're present
if ret.Title == "" { if ret.Title == "" {
@@ -71,7 +71,7 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
func NewArtistByTags(a *db.Artist) *Artist { func NewArtistByTags(a *db.Artist) *Artist {
return &Artist{ return &Artist{
ID: a.ID, ID: a.SID(),
Name: a.Name, Name: a.Name,
AlbumCount: a.AlbumCount, AlbumCount: a.AlbumCount,
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
"go.senan.xyz/gonic/version" "go.senan.xyz/gonic/version"
) )
@@ -90,15 +91,15 @@ type Albums struct {
type Album struct { type Album struct {
// common // common
ID string `xml:"id,attr,omitempty" json:"id"` ID specid.ID `xml:"id,attr,omitempty" json:"id"`
CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty,string"` CoverID specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"`
ArtistID string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` ArtistID specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
// browsing by folder (eg. getAlbumList) // browsing by folder (eg. getAlbumList)
Title string `xml:"title,attr" json:"title"` Title string `xml:"title,attr" json:"title"`
Album string `xml:"album,attr" json:"album"` Album string `xml:"album,attr" json:"album"`
ParentID string `xml:"parent,attr,omitempty" json:"parent,omitempty"` ParentID specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"`
IsDir bool `xml:"isDir,attr,omitempty" json:"isDir,omitempty"` IsDir bool `xml:"isDir,attr,omitempty" json:"isDir,omitempty"`
// browsing by tags (eg. getAlbumList2) // browsing by tags (eg. getAlbumList2)
Name string `xml:"name,attr" json:"name"` Name string `xml:"name,attr" json:"name"`
TrackCount int `xml:"songCount,attr" json:"songCount"` TrackCount int `xml:"songCount,attr" json:"songCount"`
@@ -119,19 +120,19 @@ type TracksByGenre struct {
type TrackChild struct { type TrackChild struct {
Album string `xml:"album,attr,omitempty" json:"album,omitempty"` Album string `xml:"album,attr,omitempty" json:"album,omitempty"`
AlbumID string `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` AlbumID specid.ID `xml:"albumId,attr,omitempty" json:"albumId,omitempty"`
Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"`
ArtistID string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` ArtistID specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"`
Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"`
ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"`
CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty,string"` CoverID specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"`
CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"`
Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"`
Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"`
ID string `xml:"id,attr,omitempty" json:"id,omitempty"` ID specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"`
IsDir bool `xml:"isDir,attr" json:"isDir"` IsDir bool `xml:"isDir,attr" json:"isDir"`
IsVideo bool `xml:"isVideo,attr" json:"isVideo"` IsVideo bool `xml:"isVideo,attr" json:"isVideo"`
ParentID string `xml:"parent,attr,omitempty" json:"parent,omitempty"` ParentID specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"`
Path string `xml:"path,attr,omitempty" json:"path,omitempty"` Path string `xml:"path,attr,omitempty" json:"path,omitempty"`
Size int `xml:"size,attr,omitempty" json:"size,omitempty"` Size int `xml:"size,attr,omitempty" json:"size,omitempty"`
Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"`
@@ -147,11 +148,11 @@ type Artists struct {
} }
type Artist struct { type Artist struct {
ID string `xml:"id,attr,omitempty" json:"id"` ID specid.ID `xml:"id,attr,omitempty" json:"id"`
Name string `xml:"name,attr" json:"name"` Name string `xml:"name,attr" json:"name"`
CoverID int `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty,string"` CoverID specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"`
AlbumCount int `xml:"albumCount,attr" json:"albumCount"` AlbumCount int `xml:"albumCount,attr" json:"albumCount"`
Albums []*Album `xml:"album,omitempty" json:"album,omitempty"` Albums []*Album `xml:"album,omitempty" json:"album,omitempty"`
} }
type Indexes struct { type Indexes struct {
@@ -166,8 +167,8 @@ type Index struct {
} }
type Directory struct { type Directory struct {
ID string `xml:"id,attr,omitempty" json:"id"` ID specid.ID `xml:"id,attr,omitempty" json:"id"`
ParentID string `xml:"parent,attr,omitempty" json:"parent,omitempty"` ParentID specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"`
Name string `xml:"name,attr,omitempty" json:"name"` Name string `xml:"name,attr,omitempty" json:"name"`
Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"` Starred string `xml:"starred,attr,omitempty" json:"starred,omitempty"`
Children []*TrackChild `xml:"child,omitempty" json:"child,omitempty"` Children []*TrackChild `xml:"child,omitempty" json:"child,omitempty"`
@@ -178,7 +179,7 @@ type MusicFolders struct {
} }
type MusicFolder struct { type MusicFolder struct {
ID string `xml:"id,attr,omitempty" json:"id,omitempty"` ID int `xml:"id,attr,omitempty" json:"id,omitempty"`
Name string `xml:"name,attr,omitempty" json:"name,omitempty"` Name string `xml:"name,attr,omitempty" json:"name,omitempty"`
} }
@@ -238,9 +239,9 @@ type Playlist struct {
} }
type SimilarArtist struct { type SimilarArtist struct {
ID string `xml:"id,attr" json:"id"` ID specid.ID `xml:"id,attr" json:"id"`
Name string `xml:"name,attr" json:"name"` Name string `xml:"name,attr" json:"name"`
AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"` AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"`
} }
type ArtistInfo struct { type ArtistInfo struct {

View File

@@ -0,0 +1,59 @@
package specid
// this package is at such a high level in the hierarchy because
// it's used by both `server/db` (for now) and `server/ctrlsubsonic`
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
var (
ErrBadSeparator = errors.New("bad separator")
ErrNotAnInt = errors.New("not an int")
ErrBadPrefix = errors.New("bad prefix")
)
type IDT string
const (
Artist IDT = "ar"
Album IDT = "al"
Track IDT = "tr"
separator = "-"
)
type ID struct {
Type IDT
Value int
}
func New(in string) (ID, error) {
parts := strings.Split(in, separator)
if len(parts) != 2 {
return ID{}, ErrBadSeparator
}
partType := parts[0]
partValue := parts[1]
val, err := strconv.Atoi(partValue)
if err != nil {
return ID{}, fmt.Errorf("%q: %w", partValue, ErrNotAnInt)
}
for _, acc := range []IDT{Artist, Album, Track} {
if partType == string(acc) {
return ID{Type: acc, Value: val}, nil
}
}
return ID{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
}
func (i ID) String() string {
return fmt.Sprintf("%s%s%d", i.Type, separator, i.Value)
}
func (i ID) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}

View File

@@ -1,14 +1,14 @@
package ids package specid
import ( import (
"errors" "errors"
"testing" "testing"
) )
func TestParse(t *testing.T) { func TestParseID(t *testing.T) {
tcases := []struct { tcases := []struct {
param string param string
expType ID expType IDT
expValue int expValue int
expErr error expErr error
}{ }{
@@ -19,8 +19,9 @@ func TestParse(t *testing.T) {
{param: "al-howdy", expErr: ErrNotAnInt}, {param: "al-howdy", expErr: ErrNotAnInt},
} }
for _, tcase := range tcases { for _, tcase := range tcases {
tcase := tcase // pin
t.Run(tcase.param, func(t *testing.T) { t.Run(tcase.param, func(t *testing.T) {
act, err := Parse(tcase.param) act, err := New(tcase.param)
if !errors.Is(err, tcase.expErr) { if !errors.Is(err, tcase.expErr) {
t.Fatalf("expected err %q, got %q", tcase.expErr, err) t.Fatalf("expected err %q, got %q", tcase.expErr, err)
} }

View File

@@ -11,6 +11,9 @@ import (
"strings" "strings"
"time" "time"
// TODO: remove this dep
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
"go.senan.xyz/gonic/server/mime" "go.senan.xyz/gonic/server/mime"
) )
@@ -46,6 +49,10 @@ type Artist struct {
AlbumCount int `sql:"-"` AlbumCount int `sql:"-"`
} }
func (a *Artist) SID() specid.ID {
return specid.ID{Type: specid.Artist, Value: a.ID}
}
func (a *Artist) IndexName() string { func (a *Artist) IndexName() string {
if len(a.NameUDec) > 0 { if len(a.NameUDec) > 0 {
return a.NameUDec return a.NameUDec
@@ -85,6 +92,18 @@ type Track struct {
TagBrainzID string `sql:"default: null"` TagBrainzID string `sql:"default: null"`
} }
func (t *Track) SID() specid.ID {
return specid.ID{Type: specid.Track, Value: t.ID}
}
func (t *Track) AlbumSID() specid.ID {
return specid.ID{Type: specid.Album, Value: t.AlbumID}
}
func (t *Track) ArtistSID() specid.ID {
return specid.ID{Type: specid.Artist, Value: t.ArtistID}
}
func (t *Track) Ext() string { func (t *Track) Ext() string {
longExt := path.Ext(t.Filename) longExt := path.Ext(t.Filename)
if len(longExt) < 1 { if len(longExt) < 1 {
@@ -157,6 +176,14 @@ type Album struct {
ReceivedTags bool `gorm:"-"` ReceivedTags bool `gorm:"-"`
} }
func (a *Album) SID() specid.ID {
return specid.ID{Type: specid.Album, Value: a.ID}
}
func (a *Album) ParentSID() specid.ID {
return specid.ID{Type: specid.Album, Value: a.ParentID}
}
func (a *Album) IndexRightPath() string { func (a *Album) IndexRightPath() string {
if len(a.RightPathUDec) > 0 { if len(a.RightPathUDec) > 0 {
return a.RightPathUDec return a.RightPathUDec

View File

@@ -1,59 +0,0 @@
package ids
// this package is at such a high level in the hierarchy because
// it's used by both `server/db` (for now) and `server/ctrlsubsonic`
import (
"errors"
"fmt"
"strconv"
"strings"
)
var (
ErrBadSeparator = errors.New("bad separator")
ErrNotAnInt = errors.New("not an int")
ErrBadPrefix = errors.New("bad prefix")
)
type ID string
const (
// type values copied from subsonic
Artist ID = "ar"
Album ID = "al"
Track ID = "tr"
)
var accepted = []ID{Artist,
Album,
Track,
}
type IDV struct {
Type ID
Value int
}
func (i IDV) String() string {
return fmt.Sprintf("%s-%d", i.Type, i.Value)
}
func Parse(in string) (IDV, error) {
parts := strings.Split(in, "-")
if len(parts) != 2 {
return IDV{}, ErrBadSeparator
}
partType := parts[0]
partValue := parts[1]
val, err := strconv.Atoi(partValue)
if err != nil {
return IDV{}, fmt.Errorf("%q: %w", partValue, ErrNotAnInt)
}
for _, acc := range accepted {
if partType == string(acc) {
return IDV{Type: acc, Value: val}, nil
}
}
return IDV{}, fmt.Errorf("%q: %w", partType, ErrBadPrefix)
}