refactor podcast schema and generate unique episode paths (#373)

closes #350
This commit is contained in:
Senan Kelly
2023-09-21 00:01:16 +01:00
committed by GitHub
parent c13d17262f
commit 33f1f2e0cf
15 changed files with 401 additions and 193 deletions

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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