summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/repository/playback_progress.go16
-rw-r--r--internal/repository/repository.go2
-rw-r--r--internal/repository/sqlite_test.go24
3 files changed, 38 insertions, 4 deletions
diff --git a/internal/repository/playback_progress.go b/internal/repository/playback_progress.go
index a2f753e..d044f92 100644
--- a/internal/repository/playback_progress.go
+++ b/internal/repository/playback_progress.go
@@ -76,10 +76,22 @@ func (s *SQLite) ListProgressByUser(ctx context.Context, userID int64) ([]model.
return pp, rows.Err()
}
-// ListInProgressMedia returns unfinished, non-deleted media with saved progress for a user.
+// ListInProgressMedia returns unfinished, non-deleted media with at least 60s accumulated playback.
func (s *SQLite) ListInProgressMedia(ctx context.Context, userID int64, filter MediaFilter) ([]model.Media, error) {
args := []any{userID}
- conds := []string{`pp.user_id = ?`, `pp.finished = 0`, `media.deleted_at IS NULL`}
+ conds := []string{
+ `pp.user_id = ?`,
+ `pp.finished = 0`,
+ `media.deleted_at IS NULL`,
+ `EXISTS (
+ SELECT 1
+ FROM playback_accumulator pa
+ INNER JOIN sessions s ON s.id = pa.session_id
+ WHERE pa.media_id = media.id
+ AND s.user_id = pp.user_id
+ AND pa.accumulated_seconds >= 60
+ )`,
+ }
query := `SELECT media.id, media.set_id, media.rel_path, media.file_name, media.abs_path, media.type, media.duration, media.codec, media.resolution, media.bitrate, media.file_size_bytes, media.width, media.height, media.exif_camera, media.exif_lens, media.exif_date, media.exif_iso, media.exif_f_number, media.exif_exposure, media.exif_focal_length, media.thumbnail_path, media.play_count, media.deleted_at, media.created_at FROM playback_progress pp INNER JOIN media ON media.id = pp.media_id`
if len(filter.AllowedSetIDs) > 0 {
diff --git a/internal/repository/repository.go b/internal/repository/repository.go
index 2dacc90..4c4741e 100644
--- a/internal/repository/repository.go
+++ b/internal/repository/repository.go
@@ -266,7 +266,7 @@ type PlaybackProgressRepo interface {
MarkFinished(ctx context.Context, userID, mediaID int64) error
// ListProgressByUser returns all saved playback positions for a user.
ListProgressByUser(ctx context.Context, userID int64) ([]model.PlaybackProgress, error)
- // ListInProgressMedia returns unfinished media with saved playback positions.
+ // ListInProgressMedia returns unfinished media with at least 60s accumulated playback.
ListInProgressMedia(ctx context.Context, userID int64, filter MediaFilter) ([]model.Media, error)
}
diff --git a/internal/repository/sqlite_test.go b/internal/repository/sqlite_test.go
index adb24d7..79e7f81 100644
--- a/internal/repository/sqlite_test.go
+++ b/internal/repository/sqlite_test.go
@@ -507,19 +507,28 @@ func TestSQLite_PlaybackProgressRepo(t *testing.T) {
},
},
{
- name: "list in-progress media respects allowed sets and excludes finished and deleted",
+ name: "list in-progress media respects threshold allowed sets and excludes finished and deleted",
run: func(t *testing.T, ctx context.Context, s *SQLite) {
now := time.Now().Truncate(time.Second)
uid, _ := s.CreateUser(ctx, &model.User{Username: "u", PasswordHash: "h", CreatedAt: now})
+ otherUserID, _ := s.CreateUser(ctx, &model.User{Username: "other", PasswordHash: "h", CreatedAt: now})
setAllowed, _ := s.CreateSet(ctx, &model.Set{Name: "allowed", RootPath: "/allowed", CreatedAt: now})
setOther, _ := s.CreateSet(ctx, &model.Set{Name: "other", RootPath: "/other", CreatedAt: now})
keepID, _ := s.CreateMedia(ctx, &model.Media{SetID: setAllowed, RelPath: "keep.mp4", FileName: "keep.mp4", AbsPath: "/allowed/keep.mp4", Type: model.MediaTypeVideo, CreatedAt: now})
+ belowThresholdID, _ := s.CreateMedia(ctx, &model.Media{SetID: setAllowed, RelPath: "below.mp4", FileName: "below.mp4", AbsPath: "/allowed/below.mp4", Type: model.MediaTypeVideo, CreatedAt: now})
finishedID, _ := s.CreateMedia(ctx, &model.Media{SetID: setAllowed, RelPath: "finished.mp4", FileName: "finished.mp4", AbsPath: "/allowed/finished.mp4", Type: model.MediaTypeVideo, CreatedAt: now})
deletedID, _ := s.CreateMedia(ctx, &model.Media{SetID: setAllowed, RelPath: "deleted.mp4", FileName: "deleted.mp4", AbsPath: "/allowed/deleted.mp4", Type: model.MediaTypeVideo, CreatedAt: now})
otherID, _ := s.CreateMedia(ctx, &model.Media{SetID: setOther, RelPath: "other.mp4", FileName: "other.mp4", AbsPath: "/other/other.mp4", Type: model.MediaTypeVideo, CreatedAt: now})
+ if err := s.CreateSession(ctx, &model.Session{ID: "sess", UserID: uid, ExpiresAt: now.Add(time.Hour), CreatedAt: now}); err != nil {
+ t.Fatalf("create session: %v", err)
+ }
+ if err := s.CreateSession(ctx, &model.Session{ID: "other-sess", UserID: otherUserID, ExpiresAt: now.Add(time.Hour), CreatedAt: now}); err != nil {
+ t.Fatalf("create other session: %v", err)
+ }
progress := []model.PlaybackProgress{
{UserID: uid, MediaID: keepID, PositionSeconds: 10, UpdatedAt: now.Add(3 * time.Second)},
+ {UserID: uid, MediaID: belowThresholdID, PositionSeconds: 10, UpdatedAt: now.Add(4 * time.Second)},
{UserID: uid, MediaID: finishedID, PositionSeconds: 20, Finished: true, UpdatedAt: now.Add(2 * time.Second)},
{UserID: uid, MediaID: deletedID, PositionSeconds: 30, UpdatedAt: now.Add(time.Second)},
{UserID: uid, MediaID: otherID, PositionSeconds: 40, UpdatedAt: now},
@@ -529,6 +538,19 @@ func TestSQLite_PlaybackProgressRepo(t *testing.T) {
t.Fatalf("upsert progress %d: %v", i, err)
}
}
+ accs := []model.PlaybackAccumulator{
+ {SessionID: "sess", MediaID: keepID, LastPosition: 61, AccumulatedSeconds: 61, UpdatedAt: now},
+ {SessionID: "sess", MediaID: belowThresholdID, LastPosition: 59, AccumulatedSeconds: 59, UpdatedAt: now},
+ {SessionID: "other-sess", MediaID: belowThresholdID, LastPosition: 120, AccumulatedSeconds: 120, UpdatedAt: now},
+ {SessionID: "sess", MediaID: finishedID, LastPosition: 61, AccumulatedSeconds: 61, UpdatedAt: now},
+ {SessionID: "sess", MediaID: deletedID, LastPosition: 61, AccumulatedSeconds: 61, UpdatedAt: now},
+ {SessionID: "sess", MediaID: otherID, LastPosition: 61, AccumulatedSeconds: 61, UpdatedAt: now},
+ }
+ for i := range accs {
+ if err := s.UpsertAccumulator(ctx, &accs[i]); err != nil {
+ t.Fatalf("upsert accumulator %d: %v", i, err)
+ }
+ }
if err := s.SoftDeleteMedia(ctx, deletedID); err != nil {
t.Fatalf("soft delete: %v", err)
}