summaryrefslogtreecommitdiff
path: root/internal/model
diff options
context:
space:
mode:
Diffstat (limited to 'internal/model')
-rw-r--r--internal/model/media.go167
-rw-r--r--internal/model/media_test.go27
-rw-r--r--internal/model/podcast.go54
-rw-r--r--internal/model/scan.go88
-rw-r--r--internal/model/scan_test.go157
5 files changed, 0 insertions, 493 deletions
diff --git a/internal/model/media.go b/internal/model/media.go
deleted file mode 100644
index 39ae4fd..0000000
--- a/internal/model/media.go
+++ /dev/null
@@ -1,167 +0,0 @@
-// Package model defines the core domain entities.
-package model
-
-import "time"
-
-// MediaType distinguishes between video and audio files.
-type MediaType string
-
-const (
- // MediaTypeVideo identifies video files.
- MediaTypeVideo MediaType = "video"
- // MediaTypeAudio identifies audio files.
- MediaTypeAudio MediaType = "audio"
- // MediaTypeImage identifies image files.
- MediaTypeImage MediaType = "image"
-)
-
-// Role defines the level of access a user has to a set.
-type Role string
-
-const (
- // RoleOwner allows browsing, uploading, deleting and thumbnail updates.
- RoleOwner Role = "owner"
- // RoleViewer allows browsing and playback.
- RoleViewer Role = "viewer"
-)
-
-// User represents an application account.
-type User struct {
- ID int64 `json:"id"`
- Username string `json:"username"`
- PasswordHash string `json:"-"`
- IsAdmin bool `json:"is_admin"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// Set represents a top-level media collection (a directory under MEDIA_ROOT).
-type Set struct {
- ID int64 `json:"id"`
- Name string `json:"name"`
- RootPath string `json:"root_path"`
- CoverThumbnailPath string `json:"cover_thumbnail_path"`
- IsPodcast bool `json:"is_podcast"`
- Permissions []SetPermission `json:"permissions"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// SetPermission grants a user access to a set.
-type SetPermission struct {
- SetID int64 `json:"set_id"`
- UserID int64 `json:"user_id"`
- Role Role `json:"role"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// Media represents a single media file within a set.
-type Media struct {
- ID int64 `json:"id"`
- SetID int64 `json:"set_id"`
- RelPath string `json:"rel_path"`
- FileName string `json:"file_name"`
- AbsPath string `json:"abs_path"`
- Type MediaType `json:"type"`
- Duration float64 `json:"duration"`
- Codec string `json:"codec"`
- Resolution string `json:"resolution"`
- Bitrate int `json:"bitrate"`
- FileSizeBytes int64 `json:"file_size_bytes"`
- Width int `json:"width"`
- Height int `json:"height"`
- EXIFCamera string `json:"exif_camera"`
- EXIFLens string `json:"exif_lens"`
- EXIFDate string `json:"exif_date"`
- EXIFISO string `json:"exif_iso"`
- EXIFFNumber string `json:"exif_f_number"`
- EXIFExposure string `json:"exif_exposure"`
- EXIFFocalLength string `json:"exif_focal_length"`
- ThumbnailPath string `json:"thumbnail_path"`
- PlayCount int `json:"play_count"`
- DeletedAt *time.Time `json:"deleted_at"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// Tag is a label that can be attached to media items.
-type Tag struct {
- ID int64 `json:"id"`
- Name string `json:"name"`
-}
-
-// Session is an authenticated browser session.
-type Session struct {
- ID string `json:"id"`
- UserID int64 `json:"user_id"`
- ExpiresAt time.Time `json:"expires_at"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// Share is a time-bounded public link to a media item.
-type Share struct {
- Token string `json:"token"`
- MediaID int64 `json:"media_id"`
- CreatedBy int64 `json:"created_by"`
- CreatedAt time.Time `json:"created_at"`
- ExpiresAt time.Time `json:"expires_at"`
- MaxUses *int `json:"max_uses"`
- UsedCount int `json:"used_count"`
-}
-
-// Note is a per-user, per-media text note.
-type Note struct {
- ID int64 `json:"id"`
- MediaID int64 `json:"media_id"`
- UserID int64 `json:"user_id"`
- Content string `json:"content"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// PlaybackProgress stores the last known playback position.
-type PlaybackProgress struct {
- UserID int64 `json:"user_id"`
- MediaID int64 `json:"media_id"`
- PositionSeconds float64 `json:"position_seconds"`
- Finished bool `json:"finished"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// PlaybackAccumulator tracks deltas for the 60-second playback counter rule.
-type PlaybackAccumulator struct {
- SessionID string `json:"session_id"`
- MediaID int64 `json:"media_id"`
- LastPosition float64 `json:"last_position"`
- AccumulatedSeconds float64 `json:"accumulated_seconds"`
- Counted bool `json:"counted"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// Favorite records that a user has favorited a media item.
-type Favorite struct {
- UserID int64 `json:"user_id"`
- MediaID int64 `json:"media_id"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// MediaTag is the join table between media and tags.
-type MediaTag struct {
- MediaID int64 `json:"media_id"`
- TagID int64 `json:"tag_id"`
-}
-
-// Metadata holds extracted file properties from ffprobe and os.Stat.
-type Metadata struct {
- Duration float64 `json:"duration"`
- Codec string `json:"codec"`
- Resolution string `json:"resolution"`
- Bitrate int `json:"bitrate"`
- FileSizeBytes int64 `json:"file_size_bytes"`
- Width int `json:"width"`
- Height int `json:"height"`
- EXIFCamera string `json:"exif_camera"`
- EXIFLens string `json:"exif_lens"`
- EXIFDate string `json:"exif_date"`
- EXIFISO string `json:"exif_iso"`
- EXIFFNumber string `json:"exif_f_number"`
- EXIFExposure string `json:"exif_exposure"`
- EXIFFocalLength string `json:"exif_focal_length"`
-}
diff --git a/internal/model/media_test.go b/internal/model/media_test.go
deleted file mode 100644
index 1692698..0000000
--- a/internal/model/media_test.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package model
-
-import (
- "testing"
- "time"
-)
-
-func TestStructsInstantiate(t *testing.T) {
- now := time.Now()
- _ = User{ID: 1, Username: "u", PasswordHash: "h", IsAdmin: true, CreatedAt: now}
- _ = Set{ID: 1, Name: "s", RootPath: "/r", CreatedAt: now}
- _ = Media{ID: 1, SetID: 1, RelPath: "r", FileName: "f", AbsPath: "a", Type: MediaTypeVideo, Duration: 1, DeletedAt: &now, CreatedAt: now}
- _ = Tag{ID: 1, Name: "t"}
- _ = Session{ID: "s", UserID: 1, ExpiresAt: now, CreatedAt: now}
- max := 1
- _ = Share{Token: "t", MediaID: 1, CreatedBy: 1, CreatedAt: now, ExpiresAt: now, MaxUses: &max}
- _ = Note{ID: 1, MediaID: 1, UserID: 1, Content: "c", CreatedAt: now, UpdatedAt: now}
- _ = PlaybackProgress{UserID: 1, MediaID: 1, PositionSeconds: 1, Finished: true, UpdatedAt: now}
- _ = PlaybackAccumulator{SessionID: "s", MediaID: 1, UpdatedAt: now}
- _ = Favorite{UserID: 1, MediaID: 1, CreatedAt: now}
- _ = MediaTag{MediaID: 1, TagID: 1}
- _ = SetPermission{SetID: 1, UserID: 1, Role: RoleOwner, CreatedAt: now}
- _ = Metadata{Duration: 1, Codec: "c", Resolution: "r", Bitrate: 1, FileSizeBytes: 1}
- if MediaTypeVideo != "video" || MediaTypeAudio != "audio" || RoleOwner != "owner" || RoleViewer != "viewer" {
- t.Fatal("constants mismatch")
- }
-}
diff --git a/internal/model/podcast.go b/internal/model/podcast.go
deleted file mode 100644
index f0b016d..0000000
--- a/internal/model/podcast.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Package model defines domain entities for podcast feeds and episodes.
-package model
-
-import "time"
-
-// PodcastFeed represents a subscribed RSS/Atom feed linked to a set.
-type PodcastFeed struct {
- ID int64 `json:"id"`
- SetID int64 `json:"set_id"`
- FeedURL string `json:"feed_url"`
- Title string `json:"title"`
- Description string `json:"description"`
- ImageURL string `json:"image_url"`
- LastCheckedAt *time.Time `json:"last_checked_at"`
- LastETag string `json:"last_etag"`
- CheckIntervalMinutes int `json:"check_interval_minutes"`
- AutoDownload bool `json:"auto_download"`
- ConsecutiveFailures int `json:"consecutive_failures"`
- NextCheckAt *time.Time `json:"next_check_at"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// PodcastEpisode represents an individual episode from a feed.
-type PodcastEpisode struct {
- ID int64 `json:"id"`
- FeedID int64 `json:"feed_id"`
- MediaID *int64 `json:"media_id"`
- GUID string `json:"guid"`
- Title string `json:"title"`
- Description string `json:"description"`
- PublishedAt *time.Time `json:"published_at"`
- EpisodeURL string `json:"episode_url"`
- DurationSeconds *float64 `json:"duration_seconds"`
- FileSize *int64 `json:"file_size"`
- FileName string `json:"file_name"`
- IsDownloaded bool `json:"is_downloaded"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-// PodcastStatus tracks per-user completion and progress for an episode.
-type PodcastStatus struct {
- UserID int64 `json:"user_id"`
- EpisodeID int64 `json:"episode_id"`
- IsCompleted bool `json:"is_completed"`
- PositionSeconds float64 `json:"position_seconds"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// PodcastEpisodeWithStatus is a PodcastEpisode augmented with per-user status.
-type PodcastEpisodeWithStatus struct {
- PodcastEpisode
- IsCompleted bool `json:"is_completed"`
- PositionSeconds float64 `json:"position_seconds"`
-}
diff --git a/internal/model/scan.go b/internal/model/scan.go
deleted file mode 100644
index aeec15e..0000000
--- a/internal/model/scan.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package model
-
-import "sync"
-
-// ScanProgress tracks the state of an in-progress or recently completed scan.
-type ScanProgress struct {
- mu sync.RWMutex
- Running bool `json:"running"`
- CurrentSet string `json:"current_set,omitempty"`
- SetsTotal int `json:"sets_total"`
- SetsDone int `json:"sets_done"`
- FilesTotal int `json:"files_total"`
- FilesDone int `json:"files_done"`
- LastError string `json:"last_error,omitempty"`
-}
-
-// Start marks a scan running and resets counters for the given set count.
-func (p *ScanProgress) Start(setsTotal int) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.Running = true
- p.SetsTotal = setsTotal
- p.SetsDone = 0
- p.FilesTotal = 0
- p.FilesDone = 0
- p.LastError = ""
-}
-
-// SetCurrentSet records the set currently being scanned.
-func (p *ScanProgress) SetCurrentSet(name string) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.CurrentSet = name
-}
-
-// IncrementFile increments the completed file count.
-func (p *ScanProgress) IncrementFile() {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.FilesDone++
-}
-
-// SetFilesTotal records the number of files to scan.
-func (p *ScanProgress) SetFilesTotal(total int) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.FilesTotal = total
-}
-
-// AddFilesTotal adds to the total number of files discovered during the scan.
-func (p *ScanProgress) AddFilesTotal(total int) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.FilesTotal += total
-}
-
-// IncrementSet increments the completed set count.
-func (p *ScanProgress) IncrementSet() {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.SetsDone++
-}
-
-// Done marks the scan complete and records an error message when provided.
-func (p *ScanProgress) Done(err error) {
- p.mu.Lock()
- defer p.mu.Unlock()
- p.Running = false
- p.CurrentSet = ""
- if err != nil {
- p.LastError = err.Error()
- }
-}
-
-// Copy returns a race-safe snapshot of the scan progress.
-func (p *ScanProgress) Copy() ScanProgress {
- p.mu.RLock()
- defer p.mu.RUnlock()
- return ScanProgress{
- Running: p.Running,
- CurrentSet: p.CurrentSet,
- SetsTotal: p.SetsTotal,
- SetsDone: p.SetsDone,
- FilesTotal: p.FilesTotal,
- FilesDone: p.FilesDone,
- LastError: p.LastError,
- }
-}
diff --git a/internal/model/scan_test.go b/internal/model/scan_test.go
deleted file mode 100644
index 7d370c3..0000000
--- a/internal/model/scan_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package model
-
-import (
- "errors"
- "sync"
- "testing"
-)
-
-func TestScanProgress_Start(t *testing.T) {
- var p ScanProgress
- p.Start(3)
- cp := p.Copy()
- if !cp.Running {
- t.Error("expected Running to be true")
- }
- if cp.SetsTotal != 3 {
- t.Errorf("SetsTotal = %d, want 3", cp.SetsTotal)
- }
- if cp.SetsDone != 0 {
- t.Errorf("SetsDone = %d, want 0", cp.SetsDone)
- }
- if cp.FilesTotal != 0 {
- t.Errorf("FilesTotal = %d, want 0", cp.FilesTotal)
- }
- if cp.FilesDone != 0 {
- t.Errorf("FilesDone = %d, want 0", cp.FilesDone)
- }
- if cp.LastError != "" {
- t.Errorf("LastError = %q, want empty", cp.LastError)
- }
-}
-
-func TestScanProgress_SetCurrentSet(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- p.SetCurrentSet("movies")
- cp := p.Copy()
- if cp.CurrentSet != "movies" {
- t.Errorf("CurrentSet = %q, want movies", cp.CurrentSet)
- }
-}
-
-func TestScanProgress_SetFilesTotal(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- p.SetFilesTotal(42)
- cp := p.Copy()
- if cp.FilesTotal != 42 {
- t.Errorf("FilesTotal = %d, want 42", cp.FilesTotal)
- }
-}
-
-func TestScanProgress_AddFilesTotal(t *testing.T) {
- var p ScanProgress
- p.Start(2)
- p.AddFilesTotal(10)
- p.AddFilesTotal(15)
- cp := p.Copy()
- if cp.FilesTotal != 25 {
- t.Errorf("FilesTotal = %d, want 25", cp.FilesTotal)
- }
-}
-
-func TestScanProgress_IncrementFile(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- p.IncrementFile()
- cp := p.Copy()
- if cp.FilesDone != 1 {
- t.Errorf("FilesDone = %d, want 1", cp.FilesDone)
- }
-}
-
-func TestScanProgress_IncrementSet(t *testing.T) {
- var p ScanProgress
- p.Start(2)
- p.IncrementSet()
- cp := p.Copy()
- if cp.SetsDone != 1 {
- t.Errorf("SetsDone = %d, want 1", cp.SetsDone)
- }
-}
-
-func TestScanProgress_Done(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- p.Done(nil)
- cp := p.Copy()
- if cp.Running {
- t.Error("expected Running to be false")
- }
- if cp.CurrentSet != "" {
- t.Errorf("CurrentSet = %q, want empty", cp.CurrentSet)
- }
- if cp.LastError != "" {
- t.Errorf("LastError = %q, want empty", cp.LastError)
- }
-}
-
-func TestScanProgress_Done_WithError(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- p.Done(errors.New("scan failed"))
- cp := p.Copy()
- if cp.LastError != "scan failed" {
- t.Errorf("LastError = %q, want \"scan failed\"", cp.LastError)
- }
-}
-
-func TestScanProgress_Copy_Isolation(t *testing.T) {
- var p ScanProgress
- p.Start(1)
- cp1 := p.Copy()
- p.IncrementSet()
- cp2 := p.Copy()
-
- if cp1.SetsDone != 0 {
- t.Errorf("cp1.SetsDone = %d, want 0", cp1.SetsDone)
- }
- if cp2.SetsDone != 1 {
- t.Errorf("cp2.SetsDone = %d, want 1", cp2.SetsDone)
- }
-}
-
-func TestScanProgress_ConcurrentAccess(t *testing.T) {
- var p ScanProgress
- p.Start(2)
- p.SetFilesTotal(100)
-
- start := make(chan struct{})
- done := make(chan struct{})
- var copies sync.WaitGroup
- go func() {
- <-start
- for i := 0; i < 50; i++ {
- p.IncrementFile()
- }
- close(done)
- }()
-
- for i := 0; i < 50; i++ {
- copies.Add(1)
- go func() {
- defer copies.Done()
- <-start
- _ = p.Copy()
- }()
- }
- close(start)
- copies.Wait()
- <-done
-
- cp := p.Copy()
- if cp.FilesDone != 50 {
- t.Errorf("FilesDone = %d, want 50", cp.FilesDone)
- }
-}