Files
gonic/db/model.go

460 lines
13 KiB
Go

//nolint:lll // struct tags get very long and can't be split
package db
import (
"fmt"
"mime"
"path/filepath"
"sort"
"strings"
"time"
// TODO: remove this dep
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
)
type Artist struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null; unique_index"`
NameUDec string `sql:"default: null"`
Albums []*Album `gorm:"many2many:album_artists"`
AlbumCount int `sql:"-"`
ArtistStar *ArtistStar
ArtistRating *ArtistRating
AverageRating float64 `sql:"default: null"`
Info *ArtistInfo `gorm:"foreignkey:id"`
}
func (a *Artist) SID() *specid.ID {
return &specid.ID{Type: specid.Artist, Value: a.ID}
}
func (a *Artist) IndexName() string {
if len(a.NameUDec) > 0 {
return a.NameUDec
}
return a.Name
}
type Genre struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null; unique_index"`
AlbumCount int `sql:"-"`
TrackCount int `sql:"-"`
}
// AudioFile is used to avoid some duplication in handlers_raw.go
// between Track and Podcast
type AudioFile interface {
Ext() string
MIME() string
AudioFilename() string
AudioBitrate() int
AudioLength() int
}
type Track struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"`
FilenameUDec string `sql:"default: null"`
Album *Album
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Genres []*Genre `gorm:"many2many:track_genres"`
Size int `sql:"default: null"`
Length int `sql:"default: null"`
Bitrate int `sql:"default: null"`
TagTitle string `sql:"default: null"`
TagTitleUDec string `sql:"default: null"`
TagTrackArtist string `sql:"default: null"`
TagTrackNumber int `sql:"default: null"`
TagDiscNumber int `sql:"default: null"`
TagBrainzID string `sql:"default: null"`
TrackStar *TrackStar
TrackRating *TrackRating
AverageRating float64 `sql:"default: null"`
}
func (t *Track) AudioLength() int { return t.Length }
func (t *Track) AudioBitrate() int { return t.Bitrate }
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) Ext() string {
return filepath.Ext(t.Filename)
}
func (t *Track) AudioFilename() string {
return t.Filename
}
func (t *Track) MIME() string {
return mime.TypeByExtension(filepath.Ext(t.Filename))
}
func (t *Track) AbsPath() string {
if t.Album == nil {
return ""
}
return filepath.Join(
t.Album.RootDir,
t.Album.LeftPath,
t.Album.RightPath,
t.Filename,
)
}
func (t *Track) RelPath() string {
if t.Album == nil {
return ""
}
return filepath.Join(
t.Album.LeftPath,
t.Album.RightPath,
t.Filename,
)
}
func (t *Track) GenreStrings() []string {
strs := make([]string, 0, len(t.Genres))
for _, genre := range t.Genres {
strs = append(strs, genre.Name)
}
return strs
}
type User struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
Name string `gorm:"not null; unique_index" sql:"default: null"`
Password string `gorm:"not null" sql:"default: null"`
LastFMSession string `sql:"default: null"`
ListenBrainzURL string `sql:"default: null"`
ListenBrainzToken string `sql:"default: null"`
IsAdmin bool `sql:"default: null"`
Avatar []byte `sql:"default: null"`
}
type Setting struct {
Key SettingKey `gorm:"not null; primary_key; auto_increment:false" sql:"default: null"`
Value string `sql:"default: null"`
}
type Play struct {
ID int `gorm:"primary_key"`
User *User
UserID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
Album *Album
AlbumID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Time time.Time `sql:"default: null"`
Count int
Length int
}
type Album struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
ModifiedAt time.Time
LeftPath string `gorm:"unique_index:idx_album_abs_path"`
RightPath string `gorm:"not null; unique_index:idx_album_abs_path" sql:"default: null"`
RightPathUDec string `sql:"default: null"`
Parent *Album
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
Genres []*Genre `gorm:"many2many:album_genres"`
Cover string `sql:"default: null"`
Artists []*Artist `gorm:"many2many:album_artists"`
TagTitle string `sql:"default: null"`
TagTitleUDec string `sql:"default: null"`
TagBrainzID string `sql:"default: null"`
TagYear int `sql:"default: null"`
Tracks []*Track
ChildCount int `sql:"-"`
Duration int `sql:"-"`
AlbumStar *AlbumStar
AlbumRating *AlbumRating
AverageRating float64 `sql:"default: null"`
}
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 {
if len(a.RightPathUDec) > 0 {
return a.RightPathUDec
}
return a.RightPath
}
func (a *Album) GenreStrings() []string {
strs := make([]string, 0, len(a.Genres))
for _, genre := range a.Genres {
strs = append(strs, genre.Name)
}
return strs
}
func (a *Album) ArtistsStrings() []string {
artists := append([]*Artist(nil), a.Artists...)
sort.Slice(artists, func(i, j int) bool {
return artists[i].ID < artists[j].ID
})
strs := make([]string, 0, len(artists))
for _, artist := range artists {
strs = append(strs, artist.Name)
}
return strs
}
type PlayQueue struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
User *User
UserID int `sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
Current string
Position int
ChangedBy string
Items string
}
func (p *PlayQueue) CurrentSID() *specid.ID {
id, _ := specid.New(p.Current)
return &id
}
func (p *PlayQueue) GetItems() []specid.ID {
return splitIDs(p.Items, ",")
}
func (p *PlayQueue) SetItems(items []specid.ID) {
p.Items = join(items, ",")
}
type TranscodePreference struct {
User *User
UserID int `gorm:"not null; unique_index:idx_user_id_client" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
Client string `gorm:"not null; unique_index:idx_user_id_client" sql:"default: null"`
Profile string `gorm:"not null" sql:"default: null"`
}
type AlbumArtist struct {
Album *Album
AlbumID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Artist *Artist
ArtistID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
}
type TrackGenre struct {
Track *Track
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
Genre *Genre
GenreID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
}
type AlbumGenre struct {
Album *Album
AlbumID int `gorm:"not null; unique_index:idx_album_id_genre_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Genre *Genre
GenreID int `gorm:"not null; unique_index:idx_album_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
}
type AlbumStar struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
AlbumID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
StarDate time.Time
}
type AlbumRating struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
AlbumID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Rating int `gorm:"not null; check:(rating >= 1 AND rating <= 5)"`
}
type ArtistStar struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
ArtistID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
StarDate time.Time
}
type ArtistRating struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
ArtistID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
Rating int `gorm:"not null; check:(rating >= 1 AND rating <= 5)"`
}
type TrackStar struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
TrackID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
StarDate time.Time
}
type TrackRating struct {
UserID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
TrackID int `gorm:"primary_key; not null" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
Rating int `gorm:"not null; check:(rating >= 1 AND rating <= 5)"`
}
type PodcastAutoDownload string
const (
PodcastAutoDownloadLatest PodcastAutoDownload = "latest"
PodcastAutoDownloadNone PodcastAutoDownload = "none"
)
type Podcast struct {
ID int `gorm:"primary_key"`
UpdatedAt time.Time
ModifiedAt time.Time
URL string
Title string
Description string
ImageURL string
Image string
Error string
Episodes []*PodcastEpisode
AutoDownload PodcastAutoDownload
RootDir string
}
func (p *Podcast) SID() *specid.ID {
return &specid.ID{Type: specid.Podcast, Value: p.ID}
}
type PodcastEpisodeStatus string
const (
PodcastEpisodeStatusDownloading PodcastEpisodeStatus = "downloading"
PodcastEpisodeStatusSkipped PodcastEpisodeStatus = "skipped"
PodcastEpisodeStatusDeleted PodcastEpisodeStatus = "deleted"
PodcastEpisodeStatusCompleted PodcastEpisodeStatus = "completed"
PodcastEpisodeStatusError PodcastEpisodeStatus = "error"
)
type PodcastEpisode struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
ModifiedAt time.Time
PodcastID int `gorm:"not null" sql:"default: null; type:int REFERENCES podcasts(id) ON DELETE CASCADE"`
Title string
Description string
PublishDate *time.Time
AudioURL string
Bitrate int
Length int
Size int
Filename string
Status PodcastEpisodeStatus
Error string
Podcast *Podcast
}
func (pe *PodcastEpisode) AudioLength() int { return pe.Length }
func (pe *PodcastEpisode) AudioBitrate() int { return pe.Bitrate }
func (pe *PodcastEpisode) SID() *specid.ID {
return &specid.ID{Type: specid.PodcastEpisode, Value: pe.ID}
}
func (pe *PodcastEpisode) PodcastSID() *specid.ID {
return &specid.ID{Type: specid.Podcast, Value: pe.PodcastID}
}
func (pe *PodcastEpisode) AudioFilename() string {
return pe.Filename
}
func (pe *PodcastEpisode) Ext() string {
return filepath.Ext(pe.Filename)
}
func (pe *PodcastEpisode) MIME() string {
return mime.TypeByExtension(filepath.Ext(pe.Filename))
}
func (pe *PodcastEpisode) AbsPath() string {
if pe.Podcast == nil {
return ""
}
return filepath.Join(pe.Podcast.RootDir, pe.Filename)
}
type Bookmark struct {
ID int `gorm:"primary_key"`
User *User
UserID int `sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
Position int
Comment string
EntryIDType string
EntryID int
CreatedAt time.Time
UpdatedAt time.Time
}
type InternetRadioStation struct {
ID int `gorm:"primary_key"`
StreamURL string
Name string
HomepageURL string
}
func (ir *InternetRadioStation) SID() *specid.ID {
return &specid.ID{Type: specid.InternetRadioStation, Value: ir.ID}
}
type ArtistInfo struct {
ID int `gorm:"primary_key" sql:"type:int REFERENCES artists(id) ON DELETE CASCADE"`
CreatedAt time.Time
UpdatedAt time.Time `gorm:"index"`
Biography string
MusicBrainzID string
LastFMURL string
ImageURL string
SimilarArtists string
TopTracks string
}
func (p *ArtistInfo) GetSimilarArtists() []string { return strings.Split(p.SimilarArtists, ";") }
func (p *ArtistInfo) SetSimilarArtists(items []string) { p.SimilarArtists = strings.Join(items, ";") }
func (p *ArtistInfo) GetTopTracks() []string { return strings.Split(p.TopTracks, ";") }
func (p *ArtistInfo) SetTopTracks(items []string) { p.TopTracks = strings.Join(items, ";") }
func splitIDs(in, sep string) []specid.ID {
if in == "" {
return []specid.ID{}
}
parts := strings.Split(in, sep)
ret := make([]specid.ID, 0, len(parts))
for _, p := range parts {
id, _ := specid.New(p)
ret = append(ret, id)
}
return ret
}
func join[T fmt.Stringer](in []T, sep string) string {
if in == nil {
return ""
}
strs := make([]string, 0, len(in))
for _, id := range in {
strs = append(strs, id.String())
}
return strings.Join(strs, sep)
}