Add download all button and include options to automatically download new episodes
This commit is contained in:
committed by
Senan Kelly
parent
2a11017d54
commit
10fca91785
@@ -179,9 +179,22 @@
|
|||||||
<table id="podcast-preferences">
|
<table id="podcast-preferences">
|
||||||
{{ range $pref := .Podcasts }}
|
{{ range $pref := .Podcasts }}
|
||||||
<tr>
|
<tr>
|
||||||
<form id="podcast-{{ $pref.ID }}" action="{{ printf "/admin/delete_podcast_do?id=%d" $pref.ID | path }}" method="post"></form>
|
<form id="podcast-{{ $pref.ID }}-delete" action="{{ printf "/admin/delete_podcast_do?id=%d" $pref.ID | path }}" method="post"></form>
|
||||||
|
<form id="podcast-{{ $pref.ID }}-download" action="{{ printf "/admin/download_podcast_do?id=%d" $pref.ID | path }}" method="post"></form>
|
||||||
|
<form id="podcast-{{ $pref.ID }}-autodl" action="{{ printf "/admin/update_podcast_do?id=%d" $pref.ID | path }}" method="post"></form>
|
||||||
<td>{{ $pref.Title }}</td>
|
<td>{{ $pref.Title }}</td>
|
||||||
<td><input form="podcast-{{ $pref.ID }}" type="submit" value="delete"></td>
|
<td><select form="podcast-{{ $pref.ID }}-autodl" name="auto_dl">
|
||||||
|
{{ if eq $pref.AutoDownload "dl_latest" }}
|
||||||
|
<option value="dl_latest" selected="selected">download latest</option>
|
||||||
|
<option value="dl_none">no auto download</option>
|
||||||
|
{{ else }}
|
||||||
|
<option value="dl_none" selected="selected" >no auto download</option>
|
||||||
|
<option value="dl_latest">download latest</option>
|
||||||
|
{{ end }}
|
||||||
|
</select></td>
|
||||||
|
<td><input form="podcast-{{ $pref.ID }}-download" type="submit" value="download all"></td>
|
||||||
|
<td><input form="podcast-{{ $pref.ID }}-autodl" type="submit" value="save"></td>
|
||||||
|
<td><input form="podcast-{{ $pref.ID }}-delete" type="submit" value="delete"></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ type templateData struct {
|
|||||||
DefaultListenBrainzURL string
|
DefaultListenBrainzURL string
|
||||||
SelectedUser *db.User
|
SelectedUser *db.User
|
||||||
//
|
//
|
||||||
Podcasts []*db.Podcast
|
Podcasts []*db.Podcast
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
|||||||
@@ -442,6 +442,47 @@ func (c *Controller) ServePodcastAddDo(r *http.Request) *Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServePodcastDownloadDo(r *http.Request) *Response {
|
||||||
|
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return &Response{
|
||||||
|
err: "please provide a valid podcast id",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.Podcasts.DownloadPodcastAll(id); err != nil {
|
||||||
|
return &Response{
|
||||||
|
err: "please provide a valid podcast id",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
flashN: []string{"started downloading podcast episodes"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServePodcastUpdateDo(r *http.Request) *Response {
|
||||||
|
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return &Response{
|
||||||
|
err: "please provide a valid podcast id",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
autoDlSetting := r.FormValue("auto_dl")
|
||||||
|
if err := c.Podcasts.SetAutoDownload(id, autoDlSetting); err != nil {
|
||||||
|
return &Response{
|
||||||
|
err: "please provide a valid podcast id and dl setting",
|
||||||
|
code: 400,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Response{
|
||||||
|
redirect: "/admin/home",
|
||||||
|
flashN: []string{"future podcast episodes will be automatically downloaded"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) ServePodcastDeleteDo(r *http.Request) *Response {
|
func (c *Controller) ServePodcastDeleteDo(r *http.Request) *Response {
|
||||||
user := r.Context().Value(CtxUser).(*db.User)
|
user := r.Context().Value(CtxUser).(*db.User)
|
||||||
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
id, err := strconv.Atoi(r.URL.Query().Get("id"))
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func New(path string) (*DB, error) {
|
|||||||
migrateListenBrainz(),
|
migrateListenBrainz(),
|
||||||
migratePodcast(),
|
migratePodcast(),
|
||||||
migrateBookmarks(),
|
migrateBookmarks(),
|
||||||
|
migratePodcastAutoDownload(),
|
||||||
))
|
))
|
||||||
if err = migr.Migrate(); err != nil {
|
if err = migr.Migrate(); err != nil {
|
||||||
return nil, fmt.Errorf("migrating to latest version: %w", err)
|
return nil, fmt.Errorf("migrating to latest version: %w", err)
|
||||||
|
|||||||
@@ -250,3 +250,15 @@ func migrateBookmarks() gormigrate.Migration {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migratePodcastAutoDownload() gormigrate.Migration {
|
||||||
|
return gormigrate.Migration{
|
||||||
|
ID: "202102191448",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
return tx.AutoMigrate(
|
||||||
|
Podcast{},
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -290,17 +290,18 @@ type AlbumGenre struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Podcast struct {
|
type Podcast struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
ModifiedAt time.Time
|
ModifiedAt time.Time
|
||||||
UserID int `sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
|
UserID int `sql:"default: null; type:int REFERENCES users(id) ON DELETE CASCADE"`
|
||||||
URL string
|
URL string
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Description string
|
||||||
ImageURL string
|
ImageURL string
|
||||||
ImagePath string
|
ImagePath string
|
||||||
Error string
|
Error string
|
||||||
Episodes []*PodcastEpisode
|
Episodes []*PodcastEpisode
|
||||||
|
AutoDownload string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcast) Fullpath(podcastPath string) string {
|
func (p *Podcast) Fullpath(podcastPath string) string {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ const (
|
|||||||
episodeDownloading = "downloading"
|
episodeDownloading = "downloading"
|
||||||
episodeSkipped = "skipped"
|
episodeSkipped = "skipped"
|
||||||
episodeDeleted = "deleted"
|
episodeDeleted = "deleted"
|
||||||
|
episodeCompleted = "completed"
|
||||||
|
|
||||||
|
autoDlLatest = "dl_latest"
|
||||||
|
autoDlNone = "dl_none"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Podcasts) GetPodcastOrAll(userID int, id int, includeEpisodes bool) ([]*db.Podcast, error) {
|
func (p *Podcasts) GetPodcastOrAll(userID int, id int, includeEpisodes bool) ([]*db.Podcast, error) {
|
||||||
@@ -87,7 +91,7 @@ func (p *Podcasts) AddNewPodcast(rssURL string, feed *gofeed.Feed,
|
|||||||
if err := p.DB.Save(&podcast).Error; err != nil {
|
if err := p.DB.Save(&podcast).Error; err != nil {
|
||||||
return &podcast, err
|
return &podcast, err
|
||||||
}
|
}
|
||||||
if err := p.AddNewEpisodes(podcast.ID, feed.Items); err != nil {
|
if err := p.AddNewEpisodes(&podcast, feed.Items); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
@@ -98,6 +102,28 @@ func (p *Podcasts) AddNewPodcast(rssURL string, feed *gofeed.Feed,
|
|||||||
return &podcast, nil
|
return &podcast, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errNoSuchAutoDlOption = errors.New("invalid autodownload setting")
|
||||||
|
|
||||||
|
func (p *Podcasts) SetAutoDownload(podcastID int, setting string) error {
|
||||||
|
podcast := db.Podcast{}
|
||||||
|
err := p.DB.
|
||||||
|
Where("id=?", podcastID).
|
||||||
|
First(&podcast).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch setting {
|
||||||
|
case autoDlLatest:
|
||||||
|
podcast.AutoDownload = autoDlLatest
|
||||||
|
return p.DB.Save(&podcast).Error
|
||||||
|
case autoDlNone:
|
||||||
|
podcast.AutoDownload = autoDlNone
|
||||||
|
return p.DB.Save(&podcast).Error
|
||||||
|
}
|
||||||
|
return errNoSuchAutoDlOption
|
||||||
|
}
|
||||||
|
|
||||||
func getEntriesAfterDate(feed []*gofeed.Item, after time.Time) []*gofeed.Item {
|
func getEntriesAfterDate(feed []*gofeed.Item, after time.Time) []*gofeed.Item {
|
||||||
items := []*gofeed.Item{}
|
items := []*gofeed.Item{}
|
||||||
for _, item := range feed {
|
for _, item := range feed {
|
||||||
@@ -109,10 +135,10 @@ func getEntriesAfterDate(feed []*gofeed.Item, after time.Time) []*gofeed.Item {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) AddNewEpisodes(podcastID int, items []*gofeed.Item) error {
|
func (p *Podcasts) AddNewEpisodes(podcast *db.Podcast, items []*gofeed.Item) error {
|
||||||
podcastEpisode := db.PodcastEpisode{}
|
podcastEpisode := db.PodcastEpisode{}
|
||||||
err := p.DB.
|
err := p.DB.
|
||||||
Where("podcast_id=?", podcastID).
|
Where("podcast_id=?", podcast.ID).
|
||||||
Order("publish_date DESC").
|
Order("publish_date DESC").
|
||||||
First(&podcastEpisode).Error
|
First(&podcastEpisode).Error
|
||||||
itemFound := true
|
itemFound := true
|
||||||
@@ -123,16 +149,23 @@ func (p *Podcasts) AddNewEpisodes(podcastID int, items []*gofeed.Item) error {
|
|||||||
}
|
}
|
||||||
if !itemFound {
|
if !itemFound {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if err := p.AddEpisode(podcastID, item); err != nil {
|
if _, err := p.AddEpisode(podcast.ID, item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, item := range getEntriesAfterDate(items, *podcastEpisode.PublishDate) {
|
for _, item := range getEntriesAfterDate(items, *podcastEpisode.PublishDate) {
|
||||||
if err := p.AddEpisode(podcastID, item); err != nil {
|
episode, err := p.AddEpisode(podcast.ID, item)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if podcast.AutoDownload == autoDlLatest &&
|
||||||
|
(episode.Status != episodeCompleted && episode.Status != episodeDownloading) {
|
||||||
|
if err := p.DownloadEpisode(episode.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -157,7 +190,7 @@ func getSecondsFromString(time string) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Podcasts) AddEpisode(podcastID int, item *gofeed.Item) error {
|
func (p *Podcasts) AddEpisode(podcastID int, item *gofeed.Item) (*db.PodcastEpisode, error) {
|
||||||
duration := 0
|
duration := 0
|
||||||
// if it has the media extension use it
|
// if it has the media extension use it
|
||||||
for _, content := range item.Extensions["media"]["content"] {
|
for _, content := range item.Extensions["media"]["content"] {
|
||||||
@@ -174,19 +207,19 @@ func (p *Podcasts) AddEpisode(podcastID int, item *gofeed.Item) error {
|
|||||||
|
|
||||||
if episode, ok := p.findEnclosureAudio(podcastID, duration, item); ok {
|
if episode, ok := p.findEnclosureAudio(podcastID, duration, item); ok {
|
||||||
if err := p.DB.Save(episode).Error; err != nil {
|
if err := p.DB.Save(episode).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
return episode, nil
|
||||||
}
|
}
|
||||||
if episode, ok := p.findMediaAudio(podcastID, duration, item); ok {
|
if episode, ok := p.findMediaAudio(podcastID, duration, item); ok {
|
||||||
if err := p.DB.Save(episode).Error; err != nil {
|
if err := p.DB.Save(episode).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
return episode, nil
|
||||||
}
|
}
|
||||||
// hopefully shouldnt reach here
|
// hopefully shouldnt reach here
|
||||||
log.Println("failed to find audio in feed item, skipping")
|
log.Println("failed to find audio in feed item, skipping")
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAudio(mediaType, url string) bool {
|
func isAudio(mediaType, url string) bool {
|
||||||
@@ -275,7 +308,7 @@ func (p *Podcasts) refreshPodcasts(podcasts []*db.Podcast) error {
|
|||||||
errs.Add(fmt.Errorf("refreshing podcast with url %q: %w", podcast.URL, err))
|
errs.Add(fmt.Errorf("refreshing podcast with url %q: %w", podcast.URL, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = p.AddNewEpisodes(podcast.ID, feed.Items); err != nil {
|
if err = p.AddNewEpisodes(podcast, feed.Items); err != nil {
|
||||||
errs.Add(fmt.Errorf("adding episodes: %w", err))
|
errs.Add(fmt.Errorf("adding episodes: %w", err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -283,6 +316,30 @@ func (p *Podcasts) refreshPodcasts(podcasts []*db.Podcast) error {
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Podcasts) DownloadPodcastAll(podcastID int) error {
|
||||||
|
podcastEpisodes := []db.PodcastEpisode{}
|
||||||
|
err := p.DB.
|
||||||
|
Where("podcast_id=?", podcastID).
|
||||||
|
Find(&podcastEpisodes).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get episodes by podcast id: %w", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for _, episode := range podcastEpisodes {
|
||||||
|
if episode.Status == episodeDownloading || episode.Status == episodeCompleted {
|
||||||
|
log.Println("skipping episode is in progress or already downloaded")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.DownloadEpisode(episode.ID); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
log.Printf("Finished downloading episode: \"%s\"", episode.Title)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Podcasts) DownloadEpisode(episodeID int) error {
|
func (p *Podcasts) DownloadEpisode(episodeID int) error {
|
||||||
podcastEpisode := db.PodcastEpisode{}
|
podcastEpisode := db.PodcastEpisode{}
|
||||||
podcast := db.Podcast{}
|
podcast := db.Podcast{}
|
||||||
@@ -412,13 +469,13 @@ func (p *Podcasts) doPodcastDownload(podcastEpisode *db.PodcastEpisode, file *os
|
|||||||
podcastPath := path.Join(p.PodcastBasePath, podcastEpisode.Path)
|
podcastPath := path.Join(p.PodcastBasePath, podcastEpisode.Path)
|
||||||
podcastTags, err := tags.New(podcastPath)
|
podcastTags, err := tags.New(podcastPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error parsing podcast: %e", err)
|
log.Printf("error parsing podcast audio: %e", err)
|
||||||
podcastEpisode.Status = "error"
|
podcastEpisode.Status = "error"
|
||||||
p.DB.Save(podcastEpisode)
|
p.DB.Save(podcastEpisode)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
podcastEpisode.Bitrate = podcastTags.Bitrate()
|
podcastEpisode.Bitrate = podcastTags.Bitrate()
|
||||||
podcastEpisode.Status = "completed"
|
podcastEpisode.Status = episodeCompleted
|
||||||
podcastEpisode.Length = podcastTags.Length()
|
podcastEpisode.Length = podcastTags.Length()
|
||||||
podcastEpisode.Size = int(stat.Size())
|
podcastEpisode.Size = int(stat.Size())
|
||||||
return p.DB.Save(podcastEpisode).Error
|
return p.DB.Save(podcastEpisode).Error
|
||||||
|
|||||||
@@ -145,10 +145,10 @@ func setupAdmin(r *mux.Router, ctrl *ctrladmin.Controller) {
|
|||||||
routUser.Handle("/delete_playlist_do", ctrl.H(ctrl.ServeDeletePlaylistDo))
|
routUser.Handle("/delete_playlist_do", ctrl.H(ctrl.ServeDeletePlaylistDo))
|
||||||
routUser.Handle("/create_transcode_pref_do", ctrl.H(ctrl.ServeCreateTranscodePrefDo))
|
routUser.Handle("/create_transcode_pref_do", ctrl.H(ctrl.ServeCreateTranscodePrefDo))
|
||||||
routUser.Handle("/delete_transcode_pref_do", ctrl.H(ctrl.ServeDeleteTranscodePrefDo))
|
routUser.Handle("/delete_transcode_pref_do", ctrl.H(ctrl.ServeDeleteTranscodePrefDo))
|
||||||
if ctrl.Podcasts.PodcastBasePath != "" {
|
routUser.Handle("/add_podcast_do", ctrl.H(ctrl.ServePodcastAddDo))
|
||||||
routUser.Handle("/add_podcast_do", ctrl.H(ctrl.ServePodcastAddDo))
|
routUser.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
||||||
routUser.Handle("/delete_podcast_do", ctrl.H(ctrl.ServePodcastDeleteDo))
|
routUser.Handle("/download_podcast_do", ctrl.H(ctrl.ServePodcastDownloadDo))
|
||||||
}
|
routUser.Handle("/update_podcast_do", ctrl.H(ctrl.ServePodcastUpdateDo))
|
||||||
// ** begin admin routes (if session is valid, and is admin)
|
// ** begin admin routes (if session is valid, and is admin)
|
||||||
routAdmin := routUser.NewRoute().Subrouter()
|
routAdmin := routUser.NewRoute().Subrouter()
|
||||||
routAdmin.Use(ctrl.WithAdminSession)
|
routAdmin.Use(ctrl.WithAdminSession)
|
||||||
|
|||||||
Reference in New Issue
Block a user