Fallback to local skip segment overrides
This commit is contained in:
@@ -66,9 +66,18 @@ func (s *playbackService) fetchSkipSegments(ctx context.Context, userID string,
|
|||||||
|
|
||||||
segments, err := s.fetchAniSkipSegments(ctx, malID, episode)
|
segments, err := s.fetchAniSkipSegments(ctx, malID, episode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
overrides := s.loadSkipSegmentOverrides(ctx, userID, malID, episode)
|
||||||
|
if len(overrides) > 0 {
|
||||||
|
return mergeSkipSegments(nil, overrides), nil
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("aniskip: %w", err)
|
return nil, fmt.Errorf("aniskip: %w", err)
|
||||||
}
|
}
|
||||||
return s.applySkipSegmentOverrides(ctx, segments, userID, malID, episode), nil
|
|
||||||
|
overrides := s.loadSkipSegmentOverrides(ctx, userID, malID, episode)
|
||||||
|
if len(overrides) == 0 {
|
||||||
|
return segments, nil
|
||||||
|
}
|
||||||
|
return mergeSkipSegments(segments, overrides), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playbackService) fetchAniSkipSegments(ctx context.Context, malID int, episode string) ([]domain.SkipSegment, error) {
|
func (s *playbackService) fetchAniSkipSegments(ctx context.Context, malID int, episode string) ([]domain.SkipSegment, error) {
|
||||||
@@ -139,28 +148,23 @@ func normalizeSkipSegmentLabel(skipType string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playbackService) applySkipSegmentOverrides(ctx context.Context, segments []domain.SkipSegment, userID string, malID int, episode string) []domain.SkipSegment {
|
func (s *playbackService) loadSkipSegmentOverrides(ctx context.Context, userID string, malID int, episode string) map[string]domain.SkipSegment {
|
||||||
epNum, err := strconv.ParseInt(strings.TrimSpace(episode), 10, 64)
|
epNum, err := strconv.ParseInt(strings.TrimSpace(episode), 10, 64)
|
||||||
if userID == "" || err != nil || epNum <= 0 {
|
if userID == "" || err != nil || epNum <= 0 {
|
||||||
return segments
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := s.repo.HasSkipSegmentOverrideTable(ctx)
|
ok, err := s.repo.HasSkipSegmentOverrideTable(ctx)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return segments
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
overrides, err := s.repo.ListSkipSegmentOverrides(ctx, userID, int64(malID), epNum)
|
overrides, err := s.repo.ListSkipSegmentOverrides(ctx, userID, int64(malID), epNum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return segments
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
overrideByType := buildOverrideSegments(overrides)
|
return buildOverrideSegments(overrides)
|
||||||
if len(overrideByType) == 0 {
|
|
||||||
return segments
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergeSkipSegments(segments, overrideByType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOverrideSegments(overrides []db.SkipSegmentOverrideRow) map[string]domain.SkipSegment {
|
func buildOverrideSegments(overrides []db.SkipSegmentOverrideRow) map[string]domain.SkipSegment {
|
||||||
|
|||||||
150
internal/playback/skip_segments_test.go
Normal file
150
internal/playback/skip_segments_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package playback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"mal/internal/db"
|
||||||
|
"mal/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wantSkipSegment struct {
|
||||||
|
segmentType string
|
||||||
|
start float64
|
||||||
|
end float64
|
||||||
|
source string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchSkipSegmentsFallsBackToOverridesWhenAniSkipFails(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
svc := &playbackService{
|
||||||
|
repo: &skipSegmentRepo{
|
||||||
|
hasTable: true,
|
||||||
|
overrides: []db.SkipSegmentOverrideRow{{
|
||||||
|
UserID: "user-1",
|
||||||
|
AnimeID: 2167,
|
||||||
|
Episode: 13,
|
||||||
|
SkipType: "op",
|
||||||
|
StartTime: 90,
|
||||||
|
EndTime: 180,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
httpClient: &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Body: io.NopCloser(strings.NewReader("")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}, nil
|
||||||
|
})},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := svc.fetchSkipSegments(context.Background(), "user-1", 2167, "13")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("fetchSkipSegments returned error with local fallback: %v", err)
|
||||||
|
}
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("len(got) = %d, want 1", len(got))
|
||||||
|
}
|
||||||
|
assertSkipSegment(t, got[0], wantSkipSegment{segmentType: "opening", start: 90, end: 180, source: "override"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchSkipSegmentsReturnsAniSkipErrorWhenNoOverrideFallbackExists(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
svc := &playbackService{
|
||||||
|
repo: &skipSegmentRepo{hasTable: true},
|
||||||
|
httpClient: &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Body: io.NopCloser(strings.NewReader("")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}, nil
|
||||||
|
})},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := svc.fetchSkipSegments(context.Background(), "user-1", 2167, "13")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("fetchSkipSegments returned nil error without local fallback")
|
||||||
|
}
|
||||||
|
if got != nil {
|
||||||
|
t.Fatalf("got segments = %+v, want nil", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "aniskip: unexpected status: 500") {
|
||||||
|
t.Fatalf("err = %v, want ani-skip status context", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchSkipSegmentsMergesOverridesWhenAniSkipSucceeds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
svc := &playbackService{
|
||||||
|
repo: &skipSegmentRepo{
|
||||||
|
hasTable: true,
|
||||||
|
overrides: []db.SkipSegmentOverrideRow{{
|
||||||
|
UserID: "user-1",
|
||||||
|
AnimeID: 2167,
|
||||||
|
Episode: 13,
|
||||||
|
SkipType: "op",
|
||||||
|
StartTime: 95,
|
||||||
|
EndTime: 185,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
httpClient: &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(strings.NewReader(`{
|
||||||
|
"found": true,
|
||||||
|
"results": [
|
||||||
|
{"skip_type": "op", "interval": {"start_time": 80, "end_time": 170}},
|
||||||
|
{"skip_type": "ed", "interval": {"start_time": 1300, "end_time": 1390}}
|
||||||
|
]
|
||||||
|
}`)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}, nil
|
||||||
|
})},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := svc.fetchSkipSegments(context.Background(), "user-1", 2167, "13")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("fetchSkipSegments returned error: %v", err)
|
||||||
|
}
|
||||||
|
if len(got) != 2 {
|
||||||
|
t.Fatalf("len(got) = %d, want 2", len(got))
|
||||||
|
}
|
||||||
|
assertSkipSegment(t, got[0], wantSkipSegment{segmentType: "opening", start: 95, end: 185, source: "override"})
|
||||||
|
assertSkipSegment(t, got[1], wantSkipSegment{segmentType: "ending", start: 1300, end: 1390, source: "aniskip"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSkipSegment(t *testing.T, got domain.SkipSegment, want wantSkipSegment) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if got.Type != want.segmentType || got.Start != want.start || got.End != want.end || got.Source != want.source {
|
||||||
|
t.Fatalf("got segment = %+v, want type=%q start=%v end=%v source=%q", got, want.segmentType, want.start, want.end, want.source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type skipSegmentRepo struct {
|
||||||
|
domain.PlaybackRepository
|
||||||
|
hasTable bool
|
||||||
|
hasErr error
|
||||||
|
overrides []db.SkipSegmentOverrideRow
|
||||||
|
listErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *skipSegmentRepo) HasSkipSegmentOverrideTable(context.Context) (bool, error) {
|
||||||
|
return r.hasTable, r.hasErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *skipSegmentRepo) ListSkipSegmentOverrides(context.Context, string, int64, int64) ([]db.SkipSegmentOverrideRow, error) {
|
||||||
|
return r.overrides, r.listErr
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user