番組登録を追加(登録時の過去回の選択、一時停止・再開・解除、既知の回の記録)
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
var ErrSubscriptionPaused = errors.New("subscription is paused")
|
||||
|
||||
// RegisterSubscription は登録と公開中の回の記録・任意の投入を一つの取引で行う。
|
||||
func (s *Store) RegisterSubscription(ctx context.Context, seriesID, title string, episodes []Episode, downloadExisting bool) (bool, error) {
|
||||
if seriesID == "" || title == "" {
|
||||
return false, errors.New("series id and title are required")
|
||||
}
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var inserted string
|
||||
err = tx.QueryRow(ctx, `INSERT INTO subscriptions (series_id, series_title, last_checked_at)
|
||||
VALUES ($1,$2,now()) ON CONFLICT (series_id) DO NOTHING RETURNING series_id`, seriesID, title).Scan(&inserted)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if _, err := addKnownAndMaybeQueue(ctx, tx, seriesID, episodes, downloadExisting); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// QueueUnseenEpisodes は未確認の回だけを既知に記録し、同じ取引で待ち行列へ入れる。
|
||||
func (s *Store) QueueUnseenEpisodes(ctx context.Context, seriesID string, episodes []Episode) (int, error) {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
var enabled bool
|
||||
err = tx.QueryRow(ctx, `SELECT enabled FROM subscriptions WHERE series_id=$1 FOR UPDATE`, seriesID).Scan(&enabled)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, ErrInvalidTransition
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !enabled {
|
||||
return 0, ErrSubscriptionPaused
|
||||
}
|
||||
count, err := addKnownAndMaybeQueue(ctx, tx, seriesID, episodes, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func addKnownAndMaybeQueue(ctx context.Context, tx pgx.Tx, seriesID string, episodes []Episode, queue bool) (int, error) {
|
||||
count := 0
|
||||
for _, ep := range episodes {
|
||||
if ep.ID == "" || ep.SeriesID != seriesID {
|
||||
return 0, fmt.Errorf("invalid episode identity for series %s", seriesID)
|
||||
}
|
||||
var inserted string
|
||||
err := tx.QueryRow(ctx, `INSERT INTO known_episodes (series_id, episode_id)
|
||||
VALUES ($1,$2) ON CONFLICT DO NOTHING RETURNING episode_id`, seriesID, ep.ID).Scan(&inserted)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count++
|
||||
if queue {
|
||||
_, err := tx.Exec(ctx, `INSERT INTO download_jobs
|
||||
(episode_id, series_id, series_title, title, broadcaster_name, duration_seconds, end_at, thumbnail_path, source)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'subscription')
|
||||
ON CONFLICT (episode_id) DO NOTHING`, ep.ID, seriesID, ep.SeriesTitle, ep.Title,
|
||||
ep.BroadcasterName, ep.DurationSeconds, ep.EndAt, ep.ThumbnailPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// UnknownEpisodeIDs は指定シリーズの未確認のエピソード ID を入力順で返す。
|
||||
func (s *Store) UnknownEpisodeIDs(ctx context.Context, seriesID string, ids []string) ([]string, error) {
|
||||
if len(ids) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `SELECT episode_id FROM known_episodes WHERE series_id=$1 AND episode_id=ANY($2)`, seriesID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
known := make(map[string]bool)
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
known[id] = true
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var unknown []string
|
||||
emitted := make(map[string]bool)
|
||||
for _, id := range ids {
|
||||
if !known[id] && !emitted[id] {
|
||||
unknown = append(unknown, id)
|
||||
emitted[id] = true
|
||||
}
|
||||
}
|
||||
return unknown, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetSubscriptionEnabled(ctx context.Context, seriesID string, enabled bool) error {
|
||||
tag, err := s.pool.Exec(ctx, `UPDATE subscriptions SET enabled=$2 WHERE series_id=$1`, seriesID, enabled)
|
||||
return transitionResult(tag.RowsAffected(), err)
|
||||
}
|
||||
|
||||
func (s *Store) SubscribedSeries(ctx context.Context, ids []string) (map[string]bool, error) {
|
||||
result := make(map[string]bool)
|
||||
if len(ids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
rows, err := s.pool.Query(ctx, `SELECT series_id FROM subscriptions WHERE series_id=ANY($1)`, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[id] = true
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user