193 lines
6.8 KiB
Go
193 lines
6.8 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"gitea.kazu634.com/kazu634/tver/internal/store"
|
|
)
|
|
|
|
type subscriptionActionView struct {
|
|
ID, Genre, ErrorText string
|
|
Subscribed, Choosing, DownloadExisting bool
|
|
}
|
|
|
|
type subscriptionView struct {
|
|
ID, Title, CreatedAtText, LastCheckedText string
|
|
CheckErrorAtText, CheckErrorText string
|
|
Enabled bool
|
|
Jobs int64
|
|
}
|
|
|
|
type subscriptionsView struct {
|
|
Items []subscriptionView
|
|
OutOfBand bool
|
|
}
|
|
|
|
func (s *server) subscriptionChoice(w http.ResponseWriter, r *http.Request) {
|
|
genre, id, ok := episodeForm(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
ep, found := s.cachedEpisode(genre, id)
|
|
if !found || ep.SeriesID == "" {
|
|
http.Error(w, "新着キャッシュに番組がありません。新着を再表示してください", http.StatusNotFound)
|
|
return
|
|
}
|
|
registered, err := s.isSubscribed(r.Context(), ep.SeriesID)
|
|
if err != nil {
|
|
s.logger.Printf("番組の登録状態取得に失敗: %v", err)
|
|
http.Error(w, "登録状態を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if registered {
|
|
s.subscriptionRegisteredResponse(w, r, id, genre)
|
|
return
|
|
}
|
|
s.render(w, "subscription_action", subscriptionActionView{ID: id, Genre: genre, Choosing: true})
|
|
}
|
|
|
|
func (s *server) registerSubscription(w http.ResponseWriter, r *http.Request) {
|
|
genre, id, ok := episodeForm(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
choice := r.Form.Get("download_existing")
|
|
if choice != "" && choice != "1" {
|
|
http.Error(w, "公開中の回の選択が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ep, found := s.cachedEpisode(genre, id)
|
|
if !found || ep.SeriesID == "" || ep.SeriesTitle == "" {
|
|
http.Error(w, "新着キャッシュに番組がありません。新着を再表示してください", http.StatusNotFound)
|
|
return
|
|
}
|
|
registered, err := s.isSubscribed(r.Context(), ep.SeriesID)
|
|
if err != nil {
|
|
s.logger.Printf("番組の登録状態取得に失敗: %v", err)
|
|
http.Error(w, "登録状態を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if registered {
|
|
s.subscriptionRegisteredResponse(w, r, id, genre)
|
|
return
|
|
}
|
|
episodes, err := s.fetcher.FetchSeriesEpisodes(r.Context(), ep.SeriesID)
|
|
if err != nil {
|
|
s.logger.Printf("番組の公開中エピソード取得に失敗: %s", platformTokenPattern.ReplaceAllString(err.Error(), "${1}[REDACTED]"))
|
|
s.render(w, "subscription_action", subscriptionActionView{ID: id, Genre: genre, Choosing: true, DownloadExisting: choice == "1", ErrorText: "公開中の回を取得できませんでした。再試行してください"})
|
|
return
|
|
}
|
|
items := make([]store.Episode, 0, len(episodes))
|
|
for _, episode := range episodes {
|
|
items = append(items, storeEpisode(episode))
|
|
}
|
|
_, err = s.jobs.RegisterSubscription(r.Context(), ep.SeriesID, ep.SeriesTitle, items, choice == "1")
|
|
if err != nil {
|
|
s.logger.Printf("番組登録に失敗: %v", err)
|
|
s.render(w, "subscription_action", subscriptionActionView{ID: id, Genre: genre, Choosing: true, DownloadExisting: choice == "1", ErrorText: "番組を登録できませんでした。再試行してください"})
|
|
return
|
|
}
|
|
s.subscriptionRegisteredResponse(w, r, id, genre)
|
|
}
|
|
|
|
func (s *server) subscriptionRegisteredResponse(w http.ResponseWriter, r *http.Request, id, genre string) {
|
|
if r.Header.Get("HX-Request") != "true" && r.Method == http.MethodPost {
|
|
http.Redirect(w, r, "/?genre="+genre, http.StatusSeeOther)
|
|
return
|
|
}
|
|
s.render(w, "subscription_action", subscriptionActionView{ID: id, Genre: genre, Subscribed: true})
|
|
}
|
|
|
|
func (s *server) isSubscribed(ctx context.Context, seriesID string) (bool, error) {
|
|
result, err := s.jobs.SubscribedSeries(ctx, []string{seriesID})
|
|
return result[seriesID], err
|
|
}
|
|
|
|
func (s *server) subscriptionsPage(w http.ResponseWriter, r *http.Request) {
|
|
view, err := s.loadSubscriptions(r.Context())
|
|
if err != nil {
|
|
s.logger.Printf("登録番組の取得に失敗: %v", err)
|
|
http.Error(w, "登録番組を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
s.render(w, "layout", page{Title: "登録番組", Subscriptions: &view})
|
|
}
|
|
|
|
func (s *server) subscriptionsFragment(w http.ResponseWriter, r *http.Request) {
|
|
view, err := s.loadSubscriptions(r.Context())
|
|
if err != nil {
|
|
s.logger.Printf("登録番組の取得に失敗: %v", err)
|
|
http.Error(w, "登録番組を取得できませんでした", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
s.render(w, "subscriptions_list", view)
|
|
}
|
|
|
|
func (s *server) loadSubscriptions(ctx context.Context) (subscriptionsView, error) {
|
|
subs, err := s.jobs.ListSubscriptions(ctx)
|
|
if err != nil {
|
|
return subscriptionsView{}, err
|
|
}
|
|
view := subscriptionsView{Items: make([]subscriptionView, 0, len(subs))}
|
|
for _, sub := range subs {
|
|
item := subscriptionView{ID: sub.SeriesID, Title: sub.SeriesTitle,
|
|
CreatedAtText: sub.CreatedAt.In(s.zone).Format("2006-01-02 15:04 MST"),
|
|
LastCheckedText: "未確認", Enabled: sub.Enabled, Jobs: sub.JobCount}
|
|
if sub.LastCheckedAt != nil {
|
|
item.LastCheckedText = sub.LastCheckedAt.In(s.zone).Format("2006-01-02 15:04 MST")
|
|
}
|
|
if sub.LastCheckErrorAt != nil && sub.LastCheckError != nil {
|
|
item.CheckErrorAtText = sub.LastCheckErrorAt.In(s.zone).Format("2006-01-02 15:04 MST")
|
|
item.CheckErrorText = *sub.LastCheckError
|
|
}
|
|
view.Items = append(view.Items, item)
|
|
}
|
|
return view, nil
|
|
}
|
|
|
|
func (s *server) pauseSubscription(w http.ResponseWriter, r *http.Request) {
|
|
s.subscriptionMutation(w, r, func(ctx context.Context, id string) error {
|
|
return s.jobs.SetSubscriptionEnabled(ctx, id, false)
|
|
})
|
|
}
|
|
|
|
func (s *server) resumeSubscription(w http.ResponseWriter, r *http.Request) {
|
|
s.subscriptionMutation(w, r, func(ctx context.Context, id string) error {
|
|
return s.jobs.SetSubscriptionEnabled(ctx, id, true)
|
|
})
|
|
}
|
|
|
|
func (s *server) deleteSubscription(w http.ResponseWriter, r *http.Request) {
|
|
s.subscriptionMutation(w, r, func(ctx context.Context, id string) error {
|
|
deleted, err := s.jobs.DeleteSubscription(ctx, id)
|
|
if err == nil && !deleted {
|
|
return store.ErrInvalidTransition
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (s *server) subscriptionMutation(w http.ResponseWriter, r *http.Request, change func(context.Context, string) error) {
|
|
id := r.PathValue("id")
|
|
if id == "" {
|
|
http.Error(w, "番組 ID が不正です", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := change(r.Context(), id); err != nil {
|
|
if errors.Is(err, store.ErrInvalidTransition) {
|
|
http.Error(w, "番組が見つかりません", http.StatusNotFound)
|
|
} else {
|
|
s.logger.Printf("登録番組の操作に失敗: %v", err)
|
|
http.Error(w, "登録番組を変更できませんでした", http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
if r.Header.Get("HX-Request") != "true" {
|
|
http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
|
|
return
|
|
}
|
|
s.subscriptionsFragment(w, r)
|
|
}
|