mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 22:36:37 +08:00
fix subscription management loading
This commit is contained in:
@@ -84,7 +84,7 @@ func createSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
enriched := []model.Subscription{*s}
|
enriched := []model.Subscription{*s}
|
||||||
svc.Subscription.EnrichProgress(c.Request.Context(), enriched)
|
svc.Subscription.EnrichManagementProgress(c.Request.Context(), enriched)
|
||||||
*s = enriched[0]
|
*s = enriched[0]
|
||||||
c.JSON(http.StatusCreated, s)
|
c.JSON(http.StatusCreated, s)
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ func listSubscriptionsHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
svc.Subscription.EnrichProgress(c.Request.Context(), items)
|
svc.Subscription.EnrichManagementProgress(c.Request.Context(), items)
|
||||||
go enrichAndPersistSubscriptions(context.Background(), svc, append([]model.Subscription(nil), items...))
|
go enrichAndPersistSubscriptions(context.Background(), svc, append([]model.Subscription(nil), items...))
|
||||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ func listSubscriptionHistoryHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
svc.Subscription.EnrichProgress(c.Request.Context(), items)
|
svc.Subscription.EnrichManagementProgress(c.Request.Context(), items)
|
||||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ func restoreSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
enriched := []model.Subscription{*sub}
|
enriched := []model.Subscription{*sub}
|
||||||
svc.Subscription.EnrichProgress(c.Request.Context(), enriched)
|
svc.Subscription.EnrichManagementProgress(c.Request.Context(), enriched)
|
||||||
c.JSON(http.StatusOK, enriched[0])
|
c.JSON(http.StatusOK, enriched[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,22 +45,68 @@ func (s *SubscriptionService) EnrichProgress(ctx context.Context, items []model.
|
|||||||
SubscriptionLocalAvailability(ctx, s.repo, &items[i]),
|
SubscriptionLocalAvailability(ctx, s.repo, &items[i]),
|
||||||
s.pendingDownloadAvailability(ctx, &items[i]),
|
s.pendingDownloadAvailability(ctx, &items[i]),
|
||||||
)
|
)
|
||||||
items[i].DownloadedEpisodes = availability.DownloadedEpisodes
|
applySubscriptionAvailability(&items[i], availability)
|
||||||
items[i].LocalMediaCount = availability.LocalMediaCount
|
|
||||||
items[i].MissingEpisodes = availability.MissingEpisodes
|
|
||||||
items[i].InLibrary = availability.InLibrary
|
|
||||||
if items[i].TotalEpisodes == 0 {
|
|
||||||
items[i].TotalEpisodes = availability.TotalEpisodes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubscriptionService) addDownloadTaskAvailability(ctx context.Context, sub *model.Subscription, queries []string, out *LocalAvailability) {
|
func (s *SubscriptionService) EnrichManagementProgress(ctx context.Context, items []model.Subscription) {
|
||||||
if s == nil || s.repo == nil || s.repo.Download == nil || out == nil {
|
rows := s.downloadTaskRowsForAvailability(ctx)
|
||||||
|
for i := range items {
|
||||||
|
availability := mergeLocalAvailability(
|
||||||
|
SubscriptionLocalAvailability(ctx, s.repo, &items[i]),
|
||||||
|
s.pendingDownloadTaskAvailability(ctx, &items[i], rows, false),
|
||||||
|
)
|
||||||
|
applySubscriptionAvailability(&items[i], availability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applySubscriptionAvailability(sub *model.Subscription, availability LocalAvailability) {
|
||||||
|
if sub == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sub.DownloadedEpisodes = availability.DownloadedEpisodes
|
||||||
|
sub.LocalMediaCount = availability.LocalMediaCount
|
||||||
|
sub.MissingEpisodes = availability.MissingEpisodes
|
||||||
|
sub.InLibrary = availability.InLibrary
|
||||||
|
if sub.TotalEpisodes == 0 {
|
||||||
|
sub.TotalEpisodes = availability.TotalEpisodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) downloadTaskRowsForAvailability(ctx context.Context) []model.DownloadTask {
|
||||||
|
if s == nil || s.repo == nil || s.repo.Download == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
rows, err := s.repo.Download.List(ctx)
|
rows, err := s.repo.Download.List(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) addDownloadTaskAvailability(ctx context.Context, sub *model.Subscription, queries []string, out *LocalAvailability) {
|
||||||
|
rows := s.downloadTaskRowsForAvailability(ctx)
|
||||||
|
s.addDownloadTaskRowsAvailability(ctx, sub, queries, rows, true, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) pendingDownloadTaskAvailability(ctx context.Context, sub *model.Subscription, rows []model.DownloadTask, verifyLive bool) LocalAvailability {
|
||||||
|
out := LocalAvailability{
|
||||||
|
ExistingEpisodeKeys: map[string]struct{}{},
|
||||||
|
MissingEpisodeKeys: map[string]struct{}{},
|
||||||
|
}
|
||||||
|
if sub != nil {
|
||||||
|
out.TotalEpisodes = sub.TotalEpisodes
|
||||||
|
}
|
||||||
|
queries := subscriptionAvailabilityQueries(sub)
|
||||||
|
if len(queries) == 0 {
|
||||||
|
return s.finalizePendingAvailability(sub, out)
|
||||||
|
}
|
||||||
|
s.addDownloadTaskRowsAvailability(ctx, sub, queries, rows, verifyLive, &out)
|
||||||
|
return s.finalizePendingAvailability(sub, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) addDownloadTaskRowsAvailability(ctx context.Context, sub *model.Subscription, queries []string, rows []model.DownloadTask, verifyLive bool, out *LocalAvailability) {
|
||||||
|
if out == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
baseSavePath := s.subscriptionBaseSavePath(ctx, sub)
|
baseSavePath := s.subscriptionBaseSavePath(ctx, sub)
|
||||||
@@ -68,7 +114,7 @@ func (s *SubscriptionService) addDownloadTaskAvailability(ctx context.Context, s
|
|||||||
if !downloadTaskBlocksReadd(row.Status) {
|
if !downloadTaskBlocksReadd(row.Status) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !s.downloadTaskCountsAsPending(ctx, row) {
|
if verifyLive && !s.downloadTaskCountsAsPending(ctx, row) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
linkedToSubscription := sub != nil && strings.TrimSpace(row.SubscriptionID) != "" && row.SubscriptionID == sub.ID
|
linkedToSubscription := sub != nil && strings.TrimSpace(row.SubscriptionID) != "" && row.SubscriptionID == sub.ID
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,6 +44,49 @@ func TestSubscriptionEnrichProgressIncludesPendingDownloads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubscriptionEnrichManagementProgressSkipsLiveQB(t *testing.T) {
|
||||||
|
var qbCalls int32
|
||||||
|
qb := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
atomic.AddInt32(&qbCalls, 1)
|
||||||
|
http.Error(w, "management enrichment should not call qb", http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
defer qb.Close()
|
||||||
|
|
||||||
|
db := newServiceTestDB(t, &model.DownloadTask{}, &model.Media{})
|
||||||
|
repos := repository.New(db)
|
||||||
|
sub := model.Subscription{
|
||||||
|
Base: model.Base{ID: "sub-spy-family"},
|
||||||
|
Name: "间谍过家家 自动订阅",
|
||||||
|
Filter: "间谍过家家",
|
||||||
|
MediaType: "tv",
|
||||||
|
SavePath: "/downloads/tv",
|
||||||
|
TotalEpisodes: 2,
|
||||||
|
}
|
||||||
|
if err := repos.Download.Create(t.Context(), &model.DownloadTask{
|
||||||
|
SubscriptionID: sub.ID,
|
||||||
|
Source: "qbittorrent",
|
||||||
|
URL: "magnet:?xt=urn:btih:5555555555555555555555555555555555555555",
|
||||||
|
Title: "间谍过家家 S01E01 1080p",
|
||||||
|
SavePath: "/downloads/tv",
|
||||||
|
Status: "downloading",
|
||||||
|
Progress: 0.4,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
downloads := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), nil)
|
||||||
|
downloads.qb.Configure(QBitConfig{BaseURL: qb.URL, Username: "admin", Password: "admin"})
|
||||||
|
svc := NewSubscriptionService(nil, nil, repos, downloads, nil, nil)
|
||||||
|
items := []model.Subscription{sub}
|
||||||
|
|
||||||
|
svc.EnrichManagementProgress(t.Context(), items)
|
||||||
|
if got := atomic.LoadInt32(&qbCalls); got != 0 {
|
||||||
|
t.Fatalf("management progress called qb %d times, want 0", got)
|
||||||
|
}
|
||||||
|
if items[0].DownloadedEpisodes != 1 || items[0].LocalMediaCount != 1 {
|
||||||
|
t.Fatalf("unexpected management progress: %+v", items[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubscriptionPollIntervalDefaultsAndClampsMinimum(t *testing.T) {
|
func TestSubscriptionPollIntervalDefaultsAndClampsMinimum(t *testing.T) {
|
||||||
if subscriptionStartupDelay != defaultSubscriptionPollInterval {
|
if subscriptionStartupDelay != defaultSubscriptionPollInterval {
|
||||||
t.Fatalf("startup delay = %v, want default poll interval %v", subscriptionStartupDelay, defaultSubscriptionPollInterval)
|
t.Fatalf("startup delay = %v, want default poll interval %v", subscriptionStartupDelay, defaultSubscriptionPollInterval)
|
||||||
|
|||||||
@@ -16,13 +16,22 @@ export function SubscriptionsPage() {
|
|||||||
const [editingId, setEditingId] = useState('')
|
const [editingId, setEditingId] = useState('')
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
const refresh = () =>
|
const refresh = async () => {
|
||||||
Promise.all([subscriptionsAPI.list(), subscriptionsAPI.history()])
|
setLoading(true)
|
||||||
.then(([active, history]) => {
|
void subscriptionsAPI
|
||||||
setItems(active)
|
.history()
|
||||||
setHistoryItems(history)
|
.then(setHistoryItems)
|
||||||
})
|
.catch(() => toast.error('订阅历史加载失败'))
|
||||||
.finally(() => setLoading(false))
|
|
||||||
|
try {
|
||||||
|
const active = await subscriptionsAPI.list()
|
||||||
|
setItems(active)
|
||||||
|
} catch {
|
||||||
|
toast.error('订阅列表加载失败')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refresh().catch(() => undefined)
|
refresh().catch(() => undefined)
|
||||||
|
|||||||
Reference in New Issue
Block a user