summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-30 11:38:21 +0300
committerPaul Buetow <paul@buetow.org>2026-04-30 11:38:21 +0300
commitaf29461e3a21de9b4aafbc741c5b8b0af36d1ced (patch)
tree0a29d922a4816f8e1c928a02fd7e0b1ca9b96978
parent100a9bb282dd4226368b4e5fac398726c7acf653 (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.go10
-rw-r--r--internal/service/admin.go26
-rw-r--r--internal/service/admin_test.go60
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)
}