Skip to content
Snippets Groups Projects
types.go 6.17 KiB
//
//  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"`
}

//****************