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

226 lines
4.8 KiB
Go
Raw Normal View History

2017-08-13 14:50:59 -07:00
package feeds
import (
"fmt"
2017-11-03 21:09:21 -07:00
"log"
2017-08-13 14:50:59 -07:00
"time"
2017-11-03 19:16:15 -07:00
"github.com/go-pg/pg"
2017-08-13 14:50:59 -07:00
itunes "github.com/mxpv/podcast"
2017-08-19 16:58:23 -07:00
"github.com/mxpv/podsync/pkg/api"
2017-11-03 16:04:33 -07:00
"github.com/mxpv/podsync/pkg/model"
2017-08-13 14:50:59 -07:00
"github.com/pkg/errors"
2017-11-03 17:19:44 -07:00
"github.com/ventu-io/go-shortid"
2017-08-13 14:50:59 -07:00
)
2017-08-20 18:35:47 -07:00
const (
maxPageSize = 150
)
2017-11-04 17:27:01 -07:00
const (
MetricQueries = "queries"
MetricDownloads = "downloads"
)
type stats interface {
Inc(metric, hashID string) (int64, error)
Get(metric, hashID string) (int64, error)
}
2017-11-02 18:03:44 -07:00
type builder interface {
2017-11-03 16:04:33 -07:00
Build(feed *model.Feed) (podcast *itunes.Podcast, err error)
2017-11-02 18:03:44 -07:00
}
2017-11-03 17:19:44 -07:00
type Service struct {
sid *shortid.Shortid
2017-11-04 17:27:01 -07:00
stats stats
2017-11-03 19:16:15 -07:00
db *pg.DB
2017-08-13 14:50:59 -07:00
builders map[api.Provider]builder
}
2017-11-03 17:19:44 -07:00
func (s Service) CreateFeed(req *api.CreateFeedRequest, identity *api.Identity) (string, error) {
2017-08-13 17:12:35 -07:00
feed, err := parseURL(req.URL)
2017-08-13 14:50:59 -07:00
if err != nil {
return "", errors.Wrapf(err, "failed to create feed for URL: %s", req.URL)
}
// Make sure builder exists for this provider
_, ok := s.builders[feed.Provider]
if !ok {
return "", fmt.Errorf("failed to get builder for URL: %s", req.URL)
}
2017-11-03 19:16:15 -07:00
now := time.Now().UTC()
2017-08-13 14:50:59 -07:00
// Set default fields
feed.PageSize = api.DefaultPageSize
2017-11-03 15:04:33 -07:00
feed.Format = api.FormatVideo
feed.Quality = api.QualityHigh
2017-08-13 14:50:59 -07:00
feed.FeatureLevel = api.DefaultFeatures
2017-11-03 19:16:15 -07:00
feed.CreatedAt = now
feed.LastAccess = now
2017-08-13 14:50:59 -07:00
2017-08-20 18:35:47 -07:00
if identity.FeatureLevel > 0 {
2017-11-03 16:04:33 -07:00
feed.UserID = identity.UserId
2017-08-20 18:35:47 -07:00
feed.Quality = req.Quality
feed.Format = req.Format
feed.FeatureLevel = identity.FeatureLevel
feed.PageSize = req.PageSize
if feed.PageSize > maxPageSize {
feed.PageSize = maxPageSize
}
}
2017-08-13 14:50:59 -07:00
// Generate short id
2017-11-03 17:19:44 -07:00
hashId, err := s.sid.Generate()
2017-08-13 14:50:59 -07:00
if err != nil {
return "", errors.Wrap(err, "failed to generate id for feed")
}
2017-11-03 16:04:33 -07:00
feed.HashID = hashId
2017-08-13 14:50:59 -07:00
// Save to database
2017-11-03 19:16:15 -07:00
_, err = s.db.Model(feed).Insert()
if err != nil {
2017-08-13 14:50:59 -07:00
return "", errors.Wrap(err, "failed to save feed to database")
}
return hashId, nil
}
2017-11-03 19:16:15 -07:00
func (s Service) QueryFeed(hashID string) (*model.Feed, error) {
lastAccess := time.Now().UTC()
feed := &model.Feed{}
res, err := s.db.Model(feed).
Set("last_access = ?", lastAccess).
Where("hash_id = ?", hashID).
Returning("*").
Update()
if err != nil {
return nil, errors.Wrapf(err, "failed to query feed: %s", hashID)
}
if res.RowsAffected() != 1 {
return nil, api.ErrNotFound
}
return feed, nil
}
func (s Service) BuildFeed(hashID string) (*itunes.Podcast, error) {
feed, err := s.QueryFeed(hashID)
2017-08-13 14:50:59 -07:00
if err != nil {
return nil, err
}
builder, ok := s.builders[feed.Provider]
if !ok {
2017-11-03 19:16:15 -07:00
return nil, errors.Wrapf(err, "failed to get builder for feed: %s", hashID)
2017-08-13 14:50:59 -07:00
}
2017-11-04 17:27:01 -07:00
podcast, err := builder.Build(feed)
if err != nil {
return nil, err
}
_, err = s.stats.Inc(MetricQueries, feed.HashID)
if err != nil {
return nil, errors.Wrapf(err, "failed to update metrics for feed: %s", hashID)
}
return podcast, nil
2017-08-13 14:50:59 -07:00
}
2017-11-03 19:16:15 -07:00
func (s Service) GetMetadata(hashID string) (*api.Metadata, error) {
feed := &model.Feed{}
err := s.db.
Model(feed).
Where("hash_id = ?", hashID).
2017-11-04 17:27:01 -07:00
Column("provider", "format", "quality", "user_id").
2017-11-03 19:16:15 -07:00
Select()
2017-11-03 16:04:33 -07:00
if err != nil {
return nil, err
}
2017-11-04 17:27:01 -07:00
downloads, err := s.stats.Inc(MetricDownloads, hashID)
if err != nil {
return nil, err
}
2017-11-03 16:04:33 -07:00
return &api.Metadata{
2017-11-04 17:27:01 -07:00
Provider: feed.Provider,
Format: feed.Format,
Quality: feed.Quality,
Downloads: downloads,
2017-11-03 16:04:33 -07:00
}, nil
2017-08-13 14:50:59 -07:00
}
2017-11-03 20:55:58 -07:00
func (s Service) Downgrade(patronID string, featureLevel int) error {
log.Printf("Downgrading patron '%s' to feature level %d", patronID, featureLevel)
if featureLevel == api.DefaultFeatures {
res, err := s.db.
Model(&model.Feed{}).
Set("page_size = ?", 50).
Set("feature_level = ?", 0).
Set("format = ?", api.FormatVideo).
2017-11-03 21:09:21 -07:00
Set("quality = ?", api.QualityHigh).
Where("user_id = ?", patronID).
2017-11-03 20:55:58 -07:00
Update()
if err != nil {
log.Printf("failed to downgrade patron '%s' to feature level %d: %v", patronID, featureLevel, err)
return err
}
log.Printf("Updated %d feed(s) of user '%s' to feature level %d", res.RowsAffected(), patronID, featureLevel)
return nil
}
return errors.New("unsupported downgrade type")
}
2017-11-03 17:19:44 -07:00
type feedOption func(*Service)
2017-11-03 16:04:33 -07:00
//noinspection GoExportedFuncWithUnexportedType
2017-11-03 19:16:15 -07:00
func WithPostgres(db *pg.DB) feedOption {
2017-11-03 17:19:44 -07:00
return func(service *Service) {
2017-11-03 19:16:15 -07:00
service.db = db
}
}
2017-11-03 16:04:33 -07:00
//noinspection GoExportedFuncWithUnexportedType
func WithBuilder(provider api.Provider, builder builder) feedOption {
2017-11-03 17:19:44 -07:00
return func(service *Service) {
service.builders[provider] = builder
}
}
2017-11-04 17:27:01 -07:00
//noinspection GoExportedFuncWithUnexportedType
func WithStats(m stats) feedOption {
return func(service *Service) {
service.stats = m
}
}
2017-11-03 17:19:44 -07:00
func NewFeedService(opts ...feedOption) (*Service, error) {
sid, err := shortid.New(1, shortid.DefaultABC, uint64(time.Now().UnixNano()))
if err != nil {
return nil, err
}
svc := &Service{
sid: sid,
builders: make(map[api.Provider]builder),
}
for _, fn := range opts {
fn(svc)
}
2017-11-03 17:19:44 -07:00
return svc, nil
}