diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-30 11:38:21 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-30 11:38:21 +0300 |
| commit | af29461e3a21de9b4aafbc741c5b8b0af36d1ced (patch) | |
| tree | 0a29d922a4816f8e1c928a02fd7e0b1ca9b96978 | |
| parent | 100a9bb282dd4226368b4e5fac398726c7acf653 (diff) | |
task ea: wire filesystem scanner into app and admin rescan endpoint
- Add scanner.Scanner and mediaRoot fields to adminService.
- Update NewAdminService signature to accept scanner and mediaRoot.
- Implement TriggerRescan delegation to injected scanner.
- Wire probe.NewFFProber, thumb.NewFFmpegGenerator, and scanner.NewFSScanner
in cmd/mediaplayer/main.go.
- Add tests for TriggerRescan delegation, error propagation, and nil scanner.
| -rw-r--r-- | cmd/mediaplayer/main.go | 10 | ||||
| -rw-r--r-- | internal/service/admin.go | 26 | ||||
| -rw-r--r-- | internal/service/admin_test.go | 60 |
3 files changed, 79 insertions, 17 deletions
diff --git a/cmd/mediaplayer/main.go b/cmd/mediaplayer/main.go index fcc83e7..cb99869 100644 --- a/cmd/mediaplayer/main.go +++ b/cmd/mediaplayer/main.go @@ -15,8 +15,11 @@ import ( "codeberg.org/snonux/play/internal/api" "codeberg.org/snonux/play/internal/auth" "codeberg.org/snonux/play/internal/clock" + "codeberg.org/snonux/play/internal/probe" "codeberg.org/snonux/play/internal/repository" + "codeberg.org/snonux/play/internal/scanner" "codeberg.org/snonux/play/internal/service" + "codeberg.org/snonux/play/internal/thumb" ) func main() { @@ -48,7 +51,12 @@ func main() { sm := auth.NewSessionManager(store, clk, time.Duration(cfg.SessionTimeoutHours)*time.Hour) mediaSvc := service.NewMediaService(store, clk, cfg.MediaRoot) - adminSvc := service.NewAdminService(store, clk, hasher) + + prober := probe.NewFFProber() + thumbGen := thumb.NewFFmpegGenerator() + fsScanner := scanner.NewFSScanner(store, prober, thumbGen, clk, cfg.MediaRoot) + adminSvc := service.NewAdminService(store, clk, hasher, fsScanner, cfg.MediaRoot) + progressSvc := service.NewProgressService(store, clk) staticFS := http.Dir("web") diff --git a/internal/service/admin.go b/internal/service/admin.go index 853ee68..fe0d508 100644 --- a/internal/service/admin.go +++ b/internal/service/admin.go @@ -8,21 +8,26 @@ import ( "codeberg.org/snonux/play/internal/clock" "codeberg.org/snonux/play/internal/model" "codeberg.org/snonux/play/internal/repository" + "codeberg.org/snonux/play/internal/scanner" ) // adminService is the concrete implementation of AdminService. type adminService struct { - store repository.AdminServiceStore - clock clock.Clock - hasher auth.Hasher + store repository.AdminServiceStore + clock clock.Clock + hasher auth.Hasher + scanner scanner.Scanner + mediaRoot string } // NewAdminService creates a concrete AdminService. -func NewAdminService(store repository.AdminServiceStore, clk clock.Clock, hasher auth.Hasher) AdminService { +func NewAdminService(store repository.AdminServiceStore, clk clock.Clock, hasher auth.Hasher, sc scanner.Scanner, mediaRoot string) AdminService { return &adminService{ - store: store, - clock: clk, - hasher: hasher, + store: store, + clock: clk, + hasher: hasher, + scanner: sc, + mediaRoot: mediaRoot, } } @@ -31,7 +36,12 @@ func (s *adminService) ListTrash(ctx context.Context) ([]model.Media, error) { } func (s *adminService) TriggerRescan(ctx context.Context) error { - // No-op; scanner will be wired later. + if s.scanner == nil { + return fmt.Errorf("scanner not configured") + } + if err := s.scanner.Scan(ctx, s.mediaRoot); err != nil { + return fmt.Errorf("scan failed: %w", err) + } return nil } diff --git a/internal/service/admin_test.go b/internal/service/admin_test.go index c24c82c..bc6e9c5 100644 --- a/internal/service/admin_test.go +++ b/internal/service/admin_test.go @@ -9,6 +9,17 @@ import ( "codeberg.org/snonux/play/internal/repository" ) +type fakeScanner struct { + scanFunc func(ctx context.Context, root string) error +} + +func (f *fakeScanner) Scan(ctx context.Context, root string) error { + if f.scanFunc != nil { + return f.scanFunc(ctx, root) + } + return nil +} + type fakeHasher struct { fixed string err error @@ -33,7 +44,7 @@ func TestAdminService_ListTrash(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") items, err := svc.ListTrash(ctx) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -45,10 +56,43 @@ func TestAdminService_ListTrash(t *testing.T) { func TestAdminService_TriggerRescan(t *testing.T) { ctx := context.Background() - svc := NewAdminService(&repository.MockStore{}, newMockClock(), &fakeHasher{fixed: "hash"}) + var scannedRoot string + sc := &fakeScanner{ + scanFunc: func(_ context.Context, root string) error { + scannedRoot = root + return nil + }, + } + svc := NewAdminService(&repository.MockStore{}, newMockClock(), &fakeHasher{fixed: "hash"}, sc, "/media") if err := svc.TriggerRescan(ctx); err != nil { t.Fatalf("unexpected error: %v", err) } + if scannedRoot != "/media" { + t.Fatalf("expected root %q, got %q", "/media", scannedRoot) + } +} + +func TestAdminService_TriggerRescan_Error(t *testing.T) { + ctx := context.Background() + sc := &fakeScanner{ + scanFunc: func(_ context.Context, _ string) error { + return errors.New("scan failed") + }, + } + svc := NewAdminService(&repository.MockStore{}, newMockClock(), &fakeHasher{fixed: "hash"}, sc, "/media") + err := svc.TriggerRescan(ctx) + if err == nil { + t.Fatal("expected error") + } +} + +func TestAdminService_TriggerRescan_NilScanner(t *testing.T) { + ctx := context.Background() + svc := NewAdminService(&repository.MockStore{}, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") + err := svc.TriggerRescan(ctx) + if err == nil { + t.Fatal("expected error when scanner is nil") + } } func TestAdminService_ListUsers(t *testing.T) { @@ -60,7 +104,7 @@ func TestAdminService_ListUsers(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") users, err := svc.ListUsers(ctx) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -104,7 +148,7 @@ func TestAdminService_CreateUser(t *testing.T) { }, } hasher := &fakeHasher{fixed: "hashed", err: tt.hashErr} - svc := NewAdminService(store, newMockClock(), hasher) + svc := NewAdminService(store, newMockClock(), hasher, nil, "") user, err := svc.CreateUser(ctx, "alice", "secret", false) if tt.wantErr { if err == nil { @@ -133,7 +177,7 @@ func TestAdminService_DeleteUser(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") if err := svc.DeleteUser(ctx, 1); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -156,7 +200,7 @@ func TestAdminService_ListPermissions(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") perms, err := svc.ListPermissions(ctx) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -177,7 +221,7 @@ func TestAdminService_GrantPermission(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") if err := svc.GrantPermission(ctx, 1, 2, model.RoleViewer); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -200,7 +244,7 @@ func TestAdminService_RevokePermission(t *testing.T) { }, }, } - svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}) + svc := NewAdminService(store, newMockClock(), &fakeHasher{fixed: "hash"}, nil, "") if err := svc.RevokePermission(ctx, 1, 2); err != nil { t.Fatalf("unexpected error: %v", err) } |
