1
0
mirror of https://github.com/mxpv/podsync.git synced 2024-05-11 05:55:04 +00:00
Files
mxpv-podsync/cmd/podsync/updater.go

244 lines
6.2 KiB
Go
Raw Normal View History

2019-10-29 14:38:29 -07:00
package main
import (
"bytes"
2019-10-29 14:38:29 -07:00
"context"
"fmt"
2020-03-07 14:51:38 -08:00
"io"
2019-11-13 18:16:35 -08:00
"os"
2020-02-17 22:31:40 +00:00
"regexp"
2019-10-29 14:38:29 -07:00
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
2019-10-29 14:38:29 -07:00
"github.com/mxpv/podsync/pkg/config"
2020-02-02 16:32:11 -08:00
"github.com/mxpv/podsync/pkg/db"
2019-12-21 14:15:07 -08:00
"github.com/mxpv/podsync/pkg/feed"
"github.com/mxpv/podsync/pkg/fs"
2019-10-29 14:38:29 -07:00
"github.com/mxpv/podsync/pkg/model"
2020-03-07 14:51:38 -08:00
"github.com/mxpv/podsync/pkg/ytdl"
2019-10-29 14:38:29 -07:00
)
2019-11-13 18:16:35 -08:00
type Downloader interface {
2020-03-07 14:51:38 -08:00
Download(ctx context.Context, feedConfig *config.Feed, episode *model.Episode) (io.ReadCloser, error)
2019-11-13 18:16:35 -08:00
}
2019-10-29 14:38:29 -07:00
type Updater struct {
2019-11-13 18:16:35 -08:00
config *config.Config
downloader Downloader
2020-02-02 16:32:11 -08:00
db db.Storage
fs fs.Storage
2019-10-29 14:38:29 -07:00
}
func NewUpdater(config *config.Config, downloader Downloader, db db.Storage, fs fs.Storage) (*Updater, error) {
2019-12-01 15:19:08 -08:00
return &Updater{
config: config,
downloader: downloader,
db: db,
fs: fs,
2019-12-01 15:19:08 -08:00
}, nil
2019-10-29 14:38:29 -07:00
}
2019-11-13 18:16:35 -08:00
func (u *Updater) Update(ctx context.Context, feedConfig *config.Feed) error {
log.WithFields(log.Fields{
2019-11-13 18:16:35 -08:00
"feed_id": feedConfig.ID,
"format": feedConfig.Format,
"quality": feedConfig.Quality,
}).Infof("-> updating %s", feedConfig.URL)
2020-03-07 17:25:55 -08:00
started := time.Now()
2019-12-01 15:19:08 -08:00
if err := u.updateFeed(ctx, feedConfig); err != nil {
return err
}
if err := u.downloadEpisodes(ctx, feedConfig); err != nil {
2019-12-01 15:19:08 -08:00
return err
}
if err := u.buildXML(ctx, feedConfig); err != nil {
return err
}
elapsed := time.Since(started)
nextUpdate := time.Now().Add(feedConfig.UpdatePeriod.Duration)
log.Infof("successfully updated feed in %s, next update at %s", elapsed, nextUpdate.Format(time.Kitchen))
return nil
}
// updateFeed pulls API for new episodes and saves them to database
func (u *Updater) updateFeed(ctx context.Context, feedConfig *config.Feed) error {
2019-10-29 14:38:29 -07:00
// Create an updater for this feed type
2020-03-07 17:25:55 -08:00
provider, err := feed.New(ctx, feedConfig, u.config.Tokens)
2019-10-29 14:38:29 -07:00
if err != nil {
return err
}
// Query API to get episodes
log.Debug("building feed")
2019-11-13 18:16:35 -08:00
result, err := provider.Build(ctx, feedConfig)
2019-10-29 14:38:29 -07:00
if err != nil {
return err
}
log.Debugf("received %d episode(s) for %q", len(result.Episodes), result.Title)
2019-12-01 16:01:36 -08:00
if err := u.db.AddFeed(ctx, feedConfig.ID, result); err != nil {
2019-12-01 15:19:08 -08:00
return err
}
log.Debug("successfully saved updates to storage")
return nil
}
func (u *Updater) downloadEpisodes(ctx context.Context, feedConfig *config.Feed) error {
2019-12-01 15:19:08 -08:00
var (
feedID = feedConfig.ID
downloadList []*model.Episode
2019-12-01 15:19:08 -08:00
)
// Build the list of files to download
if err := u.db.WalkEpisodes(ctx, feedID, func(episode *model.Episode) error {
if episode.Status != model.EpisodeNew && episode.Status != model.EpisodeError {
2019-12-01 15:19:08 -08:00
// File already downloaded
return nil
}
2020-02-17 22:31:40 +00:00
if feedConfig.Filters.Title != "" {
matched, err := regexp.MatchString(feedConfig.Filters.Title, episode.Title)
if err != nil {
log.Warnf("Pattern '%s' is not a valid filter for %s Title", feedConfig.Filters.Title, feedConfig.ID)
} else {
if !matched {
log.Infof("Skipping '%s' due to lack of match with '%s'", episode.Title, feedConfig.Filters.Title)
return nil
}
}
}
downloadList = append(downloadList, episode)
2019-12-01 15:19:08 -08:00
return nil
}); err != nil {
return errors.Wrapf(err, "failed to build update list")
}
2019-11-13 18:16:35 -08:00
2019-12-01 16:01:36 -08:00
var (
downloadCount = len(downloadList)
2019-12-01 16:01:36 -08:00
downloaded = 0
)
if downloadCount > 0 {
log.Infof("download count: %d", downloadCount)
2019-12-01 16:01:36 -08:00
} else {
log.Info("no episodes to download")
return nil
}
2019-11-13 18:16:35 -08:00
2019-12-01 15:19:08 -08:00
// Download pending episodes
for idx, episode := range downloadList {
var (
logger = log.WithFields(log.Fields{"index": idx, "episode_id": episode.ID})
2020-03-07 17:25:55 -08:00
episodeName = feed.EpisodeName(feedConfig, episode)
)
// Check whether episode already exists
size, err := u.fs.Size(ctx, feedID, episodeName)
2019-12-01 15:19:08 -08:00
if err == nil {
logger.Infof("episode %q already exists on disk", episode.ID)
2019-11-13 18:16:35 -08:00
2019-12-01 15:19:08 -08:00
// File already exists, update file status and disk size
if err := u.db.UpdateEpisode(feedID, episode.ID, func(episode *model.Episode) error {
episode.Size = size
episode.Status = model.EpisodeDownloaded
2019-12-01 15:19:08 -08:00
return nil
}); err != nil {
logger.WithError(err).Error("failed to update file info")
return err
}
2019-12-01 15:19:08 -08:00
2020-01-25 16:18:07 -08:00
continue
2019-12-01 15:19:08 -08:00
} else if os.IsNotExist(err) {
// Will download, do nothing here
2019-11-13 18:16:35 -08:00
} else {
2019-12-01 15:19:08 -08:00
logger.WithError(err).Error("failed to stat file")
return err
2019-11-13 18:16:35 -08:00
}
2019-12-01 15:19:08 -08:00
// Download episode to disk
2020-03-07 14:51:38 -08:00
// We download the episode to a temp directory first to avoid downloading this file by clients
// while still being processed by youtube-dl (e.g. a file is being downloaded from YT or encoding in progress)
2019-12-01 15:19:08 -08:00
logger.Infof("! downloading episode %s", episode.VideoURL)
2020-03-07 14:51:38 -08:00
tempFile, err := u.downloader.Download(ctx, feedConfig, episode)
2019-12-01 15:19:08 -08:00
if err != nil {
// YouTube might block host with HTTP Error 429: Too Many Requests
// We still need to generate XML, so just stop sending download requests and
// retry next time
2020-03-07 14:51:38 -08:00
if err == ytdl.ErrTooManyRequests {
2019-12-01 15:19:08 -08:00
break
}
if err := u.db.UpdateEpisode(feedID, episode.ID, func(episode *model.Episode) error {
episode.Status = model.EpisodeError
2019-12-01 15:19:08 -08:00
return nil
}); err != nil {
return err
}
continue
}
logger.Debug("copying file")
fileSize, err := u.fs.Create(ctx, feedID, episodeName, tempFile)
2020-03-07 14:51:38 -08:00
tempFile.Close()
if err != nil {
logger.WithError(err).Error("failed to copy file")
return err
}
logger.Debugf("copied %d bytes", fileSize)
2020-03-07 14:51:38 -08:00
2019-12-01 15:19:08 -08:00
// Update file status in database
2019-12-01 16:01:36 -08:00
logger.Infof("successfully downloaded file %q", episode.ID)
if err := u.db.UpdateEpisode(feedID, episode.ID, func(episode *model.Episode) error {
2020-03-07 14:51:38 -08:00
episode.Size = fileSize
episode.Status = model.EpisodeDownloaded
2019-12-01 15:19:08 -08:00
return nil
}); err != nil {
return err
2019-11-13 18:16:35 -08:00
}
2019-12-01 16:01:36 -08:00
downloaded++
2019-11-13 18:16:35 -08:00
}
2019-12-01 16:01:36 -08:00
log.Infof("downloaded %d episode(s)", downloaded)
2019-12-01 15:19:08 -08:00
return nil
}
func (u *Updater) buildXML(ctx context.Context, feedConfig *config.Feed) error {
2020-03-07 17:25:55 -08:00
f, err := u.db.GetFeed(ctx, feedConfig.ID)
2019-12-01 15:19:08 -08:00
if err != nil {
return err
}
2019-10-29 14:38:29 -07:00
// Build iTunes XML feed with data received from builder
log.Debug("building iTunes podcast feed")
2020-03-07 17:25:55 -08:00
podcast, err := feed.Build(ctx, f, feedConfig, u.fs)
2019-10-29 14:38:29 -07:00
if err != nil {
return err
}
var (
reader = bytes.NewReader([]byte(podcast.String()))
xmlName = fmt.Sprintf("%s.xml", feedConfig.ID)
)
if _, err := u.fs.Create(ctx, "", xmlName, reader); err != nil {
return errors.Wrap(err, "failed to upload new XML feed")
2019-10-29 14:38:29 -07:00
}
return nil
}