refactor podcast schema and generate unique episode paths (#373)
closes #350
This commit is contained in:
@@ -8,7 +8,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -48,7 +47,7 @@ func makeGoldenPath(test string) string {
|
||||
snake := testCamelExpr.ReplaceAllString(test, "${1}_${2}")
|
||||
lower := strings.ToLower(snake)
|
||||
relPath := strings.ReplaceAll(lower, "/", "_")
|
||||
return path.Join("testdata", relPath)
|
||||
return filepath.Join("testdata", relPath)
|
||||
}
|
||||
|
||||
func makeHTTPMock(query url.Values) (*httptest.ResponseRecorder, *http.Request) {
|
||||
|
||||
@@ -184,11 +184,7 @@ func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response {
|
||||
c.DB.
|
||||
Where("id=?", id.Value).
|
||||
Find(&pe)
|
||||
p := db.Podcast{}
|
||||
c.DB.
|
||||
Where("id=?", pe.PodcastID).
|
||||
Find(&p)
|
||||
sub.PlayQueue.List[i] = spec.NewTCPodcastEpisode(&pe, &p)
|
||||
sub.PlayQueue.List[i] = spec.NewTCPodcastEpisode(&pe)
|
||||
sub.PlayQueue.List[i].TranscodeMeta = transcodeMeta
|
||||
}
|
||||
}
|
||||
@@ -300,7 +296,7 @@ func (c *Controller) ServeJukebox(r *http.Request) *spec.Response { // nolint:go
|
||||
trackPaths := func(ids []specid.ID) ([]string, error) {
|
||||
var paths []string
|
||||
for _, id := range ids {
|
||||
r, err := specidpaths.Locate(c.DB, c.PodcastsPath, id)
|
||||
r, err := specidpaths.Locate(c.DB, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find track by id: %w", err)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (c *Controller) ServeCreatePlaylist(r *http.Request) *spec.Response {
|
||||
playlist.Items = nil
|
||||
ids := params.GetOrIDList("songId", nil)
|
||||
for _, id := range ids {
|
||||
r, err := specidpaths.Locate(c.DB, c.PodcastsPath, id)
|
||||
r, err := specidpaths.Locate(c.DB, id)
|
||||
if err != nil {
|
||||
return spec.NewError(0, "lookup id %v: %v", id, err)
|
||||
}
|
||||
@@ -154,7 +154,7 @@ func (c *Controller) ServeUpdatePlaylist(r *http.Request) *spec.Response {
|
||||
// add items
|
||||
if ids, err := params.GetIDList("songIdToAdd"); err == nil {
|
||||
for _, id := range ids {
|
||||
item, err := specidpaths.Locate(c.DB, c.PodcastsPath, id)
|
||||
item, err := specidpaths.Locate(c.DB, id)
|
||||
if err != nil {
|
||||
return spec.NewError(0, "locate id %q: %v", id, err)
|
||||
}
|
||||
@@ -224,14 +224,10 @@ func playlistRender(c *Controller, params params.Params, playlistID string, play
|
||||
resp.Duration += track.Length
|
||||
case specid.PodcastEpisode:
|
||||
var pe db.PodcastEpisode
|
||||
if err := c.DB.Where("id=?", id.Value).Find(&pe).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
if err := c.DB.Preload("Podcast").Where("id=?", id.Value).Find(&pe).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("load podcast episode by id: %w", err)
|
||||
}
|
||||
var p db.Podcast
|
||||
if err := c.DB.Where("id=?", pe.PodcastID).Find(&p).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("load podcast by id: %w", err)
|
||||
}
|
||||
trch = spec.NewTCPodcastEpisode(&pe, &p)
|
||||
trch = spec.NewTCPodcastEpisode(&pe)
|
||||
resp.Duration += pe.Length
|
||||
default:
|
||||
continue
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
@@ -117,26 +117,26 @@ var (
|
||||
)
|
||||
|
||||
// TODO: can we use specidpaths.Locate here?
|
||||
func coverFor(dbc *db.DB, artistInfoCache *artistinfocache.ArtistInfoCache, podcastPath string, id specid.ID) (io.ReadCloser, error) {
|
||||
func coverFor(dbc *db.DB, artistInfoCache *artistinfocache.ArtistInfoCache, id specid.ID) (io.ReadCloser, error) {
|
||||
switch id.Type {
|
||||
case specid.Album:
|
||||
return coverForAlbum(dbc, id.Value)
|
||||
case specid.Artist:
|
||||
return coverForArtist(artistInfoCache, id.Value)
|
||||
case specid.Podcast:
|
||||
return coverForPodcast(dbc, podcastPath, id.Value)
|
||||
return coverForPodcast(dbc, id.Value)
|
||||
case specid.PodcastEpisode:
|
||||
return coverGetPathPodcastEpisode(dbc, podcastPath, id.Value)
|
||||
return coverGetPathPodcastEpisode(dbc, id.Value)
|
||||
default:
|
||||
return nil, errCoverNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func coverForAlbum(dbc *db.DB, id int) (*os.File, error) {
|
||||
folder := &db.Album{}
|
||||
var folder db.Album
|
||||
err := dbc.DB.
|
||||
Select("id, root_dir, left_path, right_path, cover").
|
||||
First(folder, id).
|
||||
First(&folder, id).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("select album: %w", err)
|
||||
@@ -144,7 +144,7 @@ func coverForAlbum(dbc *db.DB, id int) (*os.File, error) {
|
||||
if folder.Cover == "" {
|
||||
return nil, errCoverEmpty
|
||||
}
|
||||
return os.Open(path.Join(folder.RootDir, folder.LeftPath, folder.RightPath, folder.Cover))
|
||||
return os.Open(filepath.Join(folder.RootDir, folder.LeftPath, folder.RightPath, folder.Cover))
|
||||
}
|
||||
|
||||
func coverForArtist(artistInfoCache *artistinfocache.ArtistInfoCache, id int) (io.ReadCloser, error) {
|
||||
@@ -162,39 +162,33 @@ func coverForArtist(artistInfoCache *artistinfocache.ArtistInfoCache, id int) (i
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func coverForPodcast(dbc *db.DB, podcastPath string, id int) (*os.File, error) {
|
||||
podcast := &db.Podcast{}
|
||||
func coverForPodcast(dbc *db.DB, id int) (*os.File, error) {
|
||||
var podcast db.Podcast
|
||||
err := dbc.
|
||||
First(podcast, id).
|
||||
First(&podcast, id).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("select podcast: %w", err)
|
||||
}
|
||||
if podcast.ImagePath == "" {
|
||||
if podcast.Image == "" {
|
||||
return nil, errCoverEmpty
|
||||
}
|
||||
return os.Open(path.Join(podcastPath, podcast.ImagePath))
|
||||
return os.Open(filepath.Join(podcast.RootDir, podcast.Image))
|
||||
}
|
||||
|
||||
func coverGetPathPodcastEpisode(dbc *db.DB, podcastPath string, id int) (*os.File, error) {
|
||||
episode := &db.PodcastEpisode{}
|
||||
func coverGetPathPodcastEpisode(dbc *db.DB, id int) (*os.File, error) {
|
||||
var pe db.PodcastEpisode
|
||||
err := dbc.
|
||||
First(episode, id).
|
||||
Preload("Podcast").
|
||||
First(&pe, id).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("select episode: %w", err)
|
||||
}
|
||||
podcast := &db.Podcast{}
|
||||
err = dbc.
|
||||
First(podcast, episode.PodcastID).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("select podcast: %w", err)
|
||||
}
|
||||
if podcast.ImagePath == "" {
|
||||
if pe.Podcast == nil || pe.Podcast.Image == "" {
|
||||
return nil, errCoverEmpty
|
||||
}
|
||||
return os.Open(path.Join(podcastPath, podcast.ImagePath))
|
||||
return os.Open(filepath.Join(pe.Podcast.RootDir, pe.Podcast.Image))
|
||||
}
|
||||
|
||||
func coverScaleAndSave(reader io.Reader, cachePath string, size int) error {
|
||||
@@ -220,14 +214,14 @@ func (c *Controller) ServeGetCoverArt(w http.ResponseWriter, r *http.Request) *s
|
||||
return spec.NewError(10, "please provide an `id` parameter")
|
||||
}
|
||||
size := params.GetOrInt("size", coverDefaultSize)
|
||||
cachePath := path.Join(
|
||||
cachePath := filepath.Join(
|
||||
c.CacheCoverPath,
|
||||
fmt.Sprintf("%s-%d.%s", id.String(), size, coverCacheFormat),
|
||||
)
|
||||
_, err = os.Stat(cachePath)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
reader, err := coverFor(c.DB, c.ArtistInfoCache, c.PodcastsPath, id)
|
||||
reader, err := coverFor(c.DB, c.ArtistInfoCache, id)
|
||||
if err != nil {
|
||||
return spec.NewError(10, "couldn't find cover `%s`: %v", id, err)
|
||||
}
|
||||
@@ -256,7 +250,7 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
|
||||
return spec.NewError(10, "please provide an `id` parameter")
|
||||
}
|
||||
|
||||
file, err := specidpaths.Locate(c.DB, c.PodcastsPath, id)
|
||||
file, err := specidpaths.Locate(c.DB, id)
|
||||
if err != nil {
|
||||
return spec.NewError(0, "error looking up id %s: %v", id, err)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.senan.xyz/gonic/db"
|
||||
@@ -62,7 +62,7 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
|
||||
Title: t.TagTitle,
|
||||
TrackNumber: t.TagTrackNumber,
|
||||
DiscNumber: t.TagDiscNumber,
|
||||
Path: path.Join(
|
||||
Path: filepath.Join(
|
||||
parent.LeftPath,
|
||||
parent.RightPath,
|
||||
t.Filename,
|
||||
@@ -95,21 +95,24 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
|
||||
return trCh
|
||||
}
|
||||
|
||||
func NewTCPodcastEpisode(pe *db.PodcastEpisode, parent *db.Podcast) *TrackChild {
|
||||
func NewTCPodcastEpisode(pe *db.PodcastEpisode) *TrackChild {
|
||||
trCh := &TrackChild{
|
||||
ID: pe.SID(),
|
||||
ContentType: pe.MIME(),
|
||||
Suffix: pe.Ext(),
|
||||
Size: pe.Size,
|
||||
Title: pe.Title,
|
||||
Path: pe.Path,
|
||||
ParentID: parent.SID(),
|
||||
ParentID: pe.SID(),
|
||||
Duration: pe.Length,
|
||||
Bitrate: pe.Bitrate,
|
||||
IsDir: false,
|
||||
Type: "podcastepisode",
|
||||
CreatedAt: pe.CreatedAt,
|
||||
}
|
||||
if pe.Podcast != nil {
|
||||
trCh.ParentID = pe.Podcast.SID()
|
||||
trCh.Path = pe.AbsPath()
|
||||
}
|
||||
return trCh
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -61,7 +61,7 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
|
||||
Artist: t.TagTrackArtist,
|
||||
TrackNumber: t.TagTrackNumber,
|
||||
DiscNumber: t.TagDiscNumber,
|
||||
Path: path.Join(
|
||||
Path: filepath.Join(
|
||||
album.LeftPath,
|
||||
album.RightPath,
|
||||
t.Filename,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"jaytaylor.com/html2text"
|
||||
"go.senan.xyz/gonic/db"
|
||||
"jaytaylor.com/html2text"
|
||||
)
|
||||
|
||||
func NewPodcastChannel(p *db.Podcast) *PodcastChannel {
|
||||
desc, err := html2text.FromString(p.Description, html2text.Options{TextOnly: true})
|
||||
if (err != nil) {
|
||||
if err != nil {
|
||||
desc = ""
|
||||
}
|
||||
ret := &PodcastChannel{
|
||||
@@ -26,31 +26,34 @@ func NewPodcastChannel(p *db.Podcast) *PodcastChannel {
|
||||
return ret
|
||||
}
|
||||
|
||||
func NewPodcastEpisode(e *db.PodcastEpisode) *PodcastEpisode {
|
||||
if e == nil {
|
||||
func NewPodcastEpisode(pe *db.PodcastEpisode) *PodcastEpisode {
|
||||
if pe == nil {
|
||||
return nil
|
||||
}
|
||||
desc, err := html2text.FromString(e.Description, html2text.Options{TextOnly: true})
|
||||
if (err != nil) {
|
||||
desc, err := html2text.FromString(pe.Description, html2text.Options{TextOnly: true})
|
||||
if err != nil {
|
||||
desc = ""
|
||||
}
|
||||
return &PodcastEpisode{
|
||||
ID: e.SID(),
|
||||
StreamID: e.SID(),
|
||||
ContentType: e.MIME(),
|
||||
ChannelID: e.PodcastSID(),
|
||||
Title: e.Title,
|
||||
r := &PodcastEpisode{
|
||||
ID: pe.SID(),
|
||||
StreamID: pe.SID(),
|
||||
ContentType: pe.MIME(),
|
||||
ChannelID: pe.PodcastSID(),
|
||||
Title: pe.Title,
|
||||
Description: desc,
|
||||
Status: string(e.Status),
|
||||
CoverArt: e.PodcastSID(),
|
||||
PublishDate: *e.PublishDate,
|
||||
Status: string(pe.Status),
|
||||
CoverArt: pe.PodcastSID(),
|
||||
PublishDate: *pe.PublishDate,
|
||||
Genre: "Podcast",
|
||||
Duration: e.Length,
|
||||
Year: e.PublishDate.Year(),
|
||||
Suffix: formatExt(e.Ext()),
|
||||
BitRate: e.Bitrate,
|
||||
Duration: pe.Length,
|
||||
Year: pe.PublishDate.Year(),
|
||||
Suffix: formatExt(pe.Ext()),
|
||||
BitRate: pe.Bitrate,
|
||||
IsDir: false,
|
||||
Path: e.Path,
|
||||
Size: e.Size,
|
||||
Size: pe.Size,
|
||||
}
|
||||
if pe.Podcast != nil {
|
||||
r.Path = pe.AbsPath()
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -18,21 +18,17 @@ type Result interface {
|
||||
}
|
||||
|
||||
// Locate maps a specid to its location on the filesystem
|
||||
func Locate(dbc *db.DB, podcastsPath string, id specid.ID) (Result, error) {
|
||||
func Locate(dbc *db.DB, id specid.ID) (Result, error) {
|
||||
switch id.Type {
|
||||
case specid.Track:
|
||||
var track db.Track
|
||||
if err := dbc.Preload("Album").Where("id=?", id.Value).Find(&track).Error; err == nil {
|
||||
return &track, nil
|
||||
}
|
||||
return &track, dbc.Preload("Album").Where("id=?", id.Value).Find(&track).Error
|
||||
case specid.PodcastEpisode:
|
||||
var pe db.PodcastEpisode
|
||||
if err := dbc.Where("id=? AND status=?", id.Value, db.PodcastEpisodeStatusCompleted).Find(&pe).Error; err == nil {
|
||||
pe.AbsP = filepath.Join(podcastsPath, pe.Path)
|
||||
return &pe, err
|
||||
}
|
||||
return &pe, dbc.Preload("Podcast").Where("id=? AND status=?", id.Value, db.PodcastEpisodeStatusCompleted).Find(&pe).Error
|
||||
default:
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// Locate maps a location on the filesystem to a specid
|
||||
@@ -42,9 +38,13 @@ func Lookup(dbc *db.DB, musicPaths []string, podcastsPath string, path string) (
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, podcastsPath) {
|
||||
path, _ = filepath.Rel(podcastsPath, path)
|
||||
podcastPath, episodeFilename := filepath.Split(path)
|
||||
q := dbc.
|
||||
Joins(`JOIN podcasts ON podcasts.id=podcast_episodes.podcast_id`).
|
||||
Where(`podcasts.root_dir=? AND podcast_episodes.filename=?`, filepath.Clean(podcastPath), filepath.Clean(episodeFilename))
|
||||
|
||||
var pe db.PodcastEpisode
|
||||
if err := dbc.Where(`path=?`, path).First(&pe).Error; err == nil {
|
||||
if err := q.First(&pe).Error; err == nil {
|
||||
return &pe, nil
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
|
||||
Reference in New Issue
Block a user