// // tank, Import and Playlist Daemon for Aura project // Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // package store import ( "errors" "fmt" "sync" "time" "github.com/jinzhu/gorm" ) const ( FileURIScheme = "file" // well-known table names migrationsTn = "__migrations__" ) //******* Errors var ( ErrNotImplemented = errors.New("not implemented") ErrNotFound = gorm.ErrRecordNotFound ErrShowAlreadyExists = errors.New("show already exists") ErrFileNotNew = errors.New("file does not exist or is not new") ErrFileImportNotDone = errors.New("file import is not done") ErrPlaylistHasMultipleNullDurationEntries = errors.New("playlists may only have one entry without a duration") ) type ErrFileInUse struct { Playlists []Playlist `json:"playlists"` } func (e *ErrFileInUse) Error() string { return fmt.Sprintf("the file is still in use by %d playlist(s)", len(e.Playlists)) } type ErrInvalidMetadataField string func (e ErrInvalidMetadataField) Error() string { return "invalid metadata field: " + string(e) } type ErrInvalidPlaylistEntry struct { idx int message string } func (e ErrInvalidPlaylistEntry) Error() string { return fmt.Sprintf("playlist entry #%d is invalid: %s", e.idx, e.message) } //******* Logs type LogLine struct { Stream string `json:"stream"` Timestamp time.Time `json:"timestamp"` Line string `json:"line"` } // This implements json.Marshaler and therefore doesn't need `lines` to be public. type Log struct { m sync.RWMutex lines []LogLine } //******* Shows type Show struct { ID uint64 `json:"id" gorm:"primary_key"` CreatedAt time.Time `json:"created"` UpdatedAt time.Time `json:"updated"` } func (g Show) String() uint64 { return g.ID } //******* Files type ImportState int const ( ImportNew ImportState = iota ImportInitializing ImportPending ImportRunning ImportDone ImportAborted ) func (s ImportState) String() string { switch s { case ImportNew: return "new" case ImportInitializing: return "initializing" case ImportPending: return "pending" case ImportRunning: return "running" case ImportDone: return "done" case ImportAborted: return "aborted" } return "unknown" } func (s *ImportState) fromString(str string) error { switch str { case "new": *s = ImportNew case "initializing": *s = ImportInitializing case "pending": *s = ImportPending case "running": *s = ImportRunning case "done": *s = ImportDone case "aborted": *s = ImportAborted default: return errors.New("invalid import state: '" + str + "'") } return nil } func (s ImportState) MarshalText() (data []byte, err error) { data = []byte(s.String()) return } func (s *ImportState) UnmarshalText(data []byte) (err error) { return s.fromString(string(data)) } type Import struct { State ImportState `json:"state" swaggertype:"string"` Error string `json:"error,omitempty"` } type FileSource struct { URI string `json:"uri" gorm:"size:1024"` Hash string `json:"hash"` Import Import `json:"import" gorm:"embedded;embedded_prefix:import__"` } type FileMetadata struct { // actually a full-text index would be nice here... Artist string `json:"artist,omitempty" gorm:"index"` Title string `json:"title,omitempty" gorm:"index"` Album string `json:"album,omitempty" gorm:"index"` Organization string `json:"organization,omitempty" gorm:"index"` ISRC string `json:"isrc,omitempty" gorm:"index"` // TODO: add more fields... } type File struct { ID uint64 `json:"id" gorm:"primary_key"` CreatedAt time.Time `json:"created"` UpdatedAt time.Time `json:"updated"` ShowID uint64 `json:"showId" gorm:"not null;index"` Show Show `json:"-" gorm:"association_foreignkey:ID"` Source FileSource `json:"source" gorm:"embedded;embedded_prefix:source__"` Metadata FileMetadata `json:"metadata" gorm:"embedded;embedded_prefix:metadata__"` Size uint64 `json:"size"` Duration float64 `json:"duration"` } //******* ImportLogs type ImportLog struct { ID uint64 `gorm:"primary_key"` File File `gorm:"association_autoupdate:false;association_autocreate:false"` FileID uint64 `gorm:"not null;index;unique_index:unique_import_log_step"` ImportStep string `gorm:"not null;index;unique_index:unique_import_log_step"` Encoded []byte `gorm:"size:-1"` } // Mind that ImportLogs is not []ImportLog but rather a map containing objects of type LOG type ImportLogs map[string]*Log //******* Playlists type PlaylistEntry struct { ID uint64 `json:"-" gorm:"primary_key"` PlaylistID uint64 `json:"-" gorm:"not null;index;unique_index:unique_playlist_line_numbers"` LineNum uint `json:"-" gorm:"not null;unique_index:unique_playlist_line_numbers"` URI string `json:"uri" gorm:"size:1024"` Duration *float64 `json:"duration,omitempty"` File *File `json:"file,omitempty" gorm:"association_autoupdate:false;association_autocreate:false"` FileID *uint64 `json:"-" gorm:"index"` } type Playlist struct { ID uint64 `json:"id" gorm:"primary_key"` CreatedAt time.Time `json:"created"` UpdatedAt time.Time `json:"updated"` Description string `json:"description"` PlayoutMode string `json:"playoutMode" gorm:"not null;default:'linear'"` ShowID uint64 `json:"showId" gorm:"not null;index"` Show Show `json:"-" gorm:"association_foreignkey:ID"` Entries []PlaylistEntry `json:"entries"` } //****************