From 30c2b0fe8232cc748ab2bded6ab4d76febe32425 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 3 May 2026 19:17:04 +0300 Subject: fix(admin): fix rescan goroutine lifecycle and race on shared ScanProgress - Protect adminService scan state (cancel func + progress pointer) with sync.Mutex. - Allocate fresh ScanProgress per trigger and pass it to the scanner, eliminating races on the previously shared progress struct. - Cancel previous scan context before starting a new one. - Add tests for cancellation, fresh progress per scan, concurrent triggers, and empty progress when never started. - Fix race-prone tests by polling Running==true before waiting for completion. --- internal/model/media.go | 61 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 21 deletions(-) (limited to 'internal/model') diff --git a/internal/model/media.go b/internal/model/media.go index b1b4ef3..74492c1 100644 --- a/internal/model/media.go +++ b/internal/model/media.go @@ -9,6 +9,7 @@ type MediaType string const ( MediaTypeVideo MediaType = "video" MediaTypeAudio MediaType = "audio" + MediaTypeImage MediaType = "image" ) // Role defines the level of access a user has to a set. @@ -46,23 +47,32 @@ type SetPermission struct { CreatedAt time.Time `json:"created_at"` } -// Media represents a single audio or video file within a set. +// 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"` - ThumbnailPath string `json:"thumbnail_path"` - PlayCount int `json:"play_count"` - DeletedAt *time.Time `json:"deleted_at"` - CreatedAt time.Time `json:"created_at"` + 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. @@ -133,9 +143,18 @@ type MediaTag struct { // 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"` + 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"` } -- cgit v1.2.3