diff --git a/backend/playlist/playlist.go b/backend/playlist/playlist.go index 0b62333..889b265 100644 --- a/backend/playlist/playlist.go +++ b/backend/playlist/playlist.go @@ -98,6 +98,23 @@ type PhantomSearchResult struct { Unmatched []string `json:"Unmatched"` } +// DuplicateTrackInfo holds metadata for a track that already +// exists in a playlist. +type DuplicateTrackInfo struct { + FilePath string `json:"FilePath"` + Title string `json:"Title"` + Artist string `json:"Artist"` + Album string `json:"Album"` + Duration string `json:"Duration"` +} + +// DuplicateCheckResult contains the outcome of checking for +// duplicate tracks in a playlist. +type DuplicateCheckResult struct { + Duplicates []DuplicateTrackInfo `json:"Duplicates"` + Unique []string `json:"Unique"` +} + // Service manages playlist operations. type Service struct { // mu protects ctx and favoritesConf from concurrent access @@ -509,6 +526,64 @@ func (s *Service) AddTracksToPlaylist( return nil } +// FindDuplicateTracksInPlaylist checks which of the given file +// paths already exist in the specified playlist. Returns metadata +// for each duplicate and a list of non-duplicate file paths. +func (s *Service) FindDuplicateTracksInPlaylist( + playlistID int64, + filePaths []string, +) (DuplicateCheckResult, error) { + rows, err := s.db.Queries.GetPlaylistTracksWithMetadata( + s.db.Ctx, + playlistID, + ) + if err != nil { + s.logger.Error( + "Failed to get playlist tracks for duplicate check", + "playlistId", playlistID, + "err", err, + ) + + return DuplicateCheckResult{}, fmt.Errorf( + "failed to get playlist tracks: %w", err, + ) + } + + existingPaths := make( + map[string]sqlcgen.GetPlaylistTracksWithMetadataRow, + len(rows), + ) + + for _, row := range rows { + existingPaths[row.FilePath] = row + } + + var duplicates []DuplicateTrackInfo + + var unique []string + + for _, fp := range filePaths { + if row, exists := existingPaths[fp]; exists { + duplicates = append(duplicates, DuplicateTrackInfo{ + FilePath: fp, + Title: row.Title, + Artist: row.Artist, + Album: row.Album, + Duration: strconv.FormatInt( + row.LengthMilliseconds, 10, + ), + }) + } else { + unique = append(unique, fp) + } + } + + return DuplicateCheckResult{ + Duplicates: duplicates, + Unique: unique, + }, nil +} + // CreatePlaylistWithTracks creates a new playlist and populates // it with tracks. func (s *Service) CreatePlaylistWithTracks( diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index b13eeed..6f850c9 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -236,6 +236,59 @@ export namespace playlist { this.Score = source["Score"]; } } + export class DuplicateTrackInfo { + FilePath: string; + Title: string; + Artist: string; + Album: string; + Duration: string; + + static createFrom(source: any = {}) { + return new DuplicateTrackInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.FilePath = source["FilePath"]; + this.Title = source["Title"]; + this.Artist = source["Artist"]; + this.Album = source["Album"]; + this.Duration = source["Duration"]; + } + } + export class DuplicateCheckResult { + Duplicates: DuplicateTrackInfo[]; + Unique: string[]; + + static createFrom(source: any = {}) { + return new DuplicateCheckResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.Duplicates = this.convertValues(source["Duplicates"], DuplicateTrackInfo); + this.Unique = source["Unique"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class PhantomMatch { PhantomPath: string; PhantomTitle: string; diff --git a/frontend/wailsjs/go/playlist/Service.d.ts b/frontend/wailsjs/go/playlist/Service.d.ts index 0007e28..4046d6f 100755 --- a/frontend/wailsjs/go/playlist/Service.d.ts +++ b/frontend/wailsjs/go/playlist/Service.d.ts @@ -15,6 +15,8 @@ export function DeletePlaylist(arg1:number):Promise; export function EnsureDefaultPlaylist():Promise; +export function FindDuplicateTracksInPlaylist(arg1:number,arg2:Array):Promise; + export function FindPhantomMatches(arg1:number,arg2:Array):Promise; export function GetAllPlaylists():Promise>; diff --git a/frontend/wailsjs/go/playlist/Service.js b/frontend/wailsjs/go/playlist/Service.js index 406b8fa..5208a13 100755 --- a/frontend/wailsjs/go/playlist/Service.js +++ b/frontend/wailsjs/go/playlist/Service.js @@ -26,6 +26,10 @@ export function EnsureDefaultPlaylist() { return window['go']['playlist']['Service']['EnsureDefaultPlaylist'](); } +export function FindDuplicateTracksInPlaylist(arg1, arg2) { + return window['go']['playlist']['Service']['FindDuplicateTracksInPlaylist'](arg1, arg2); +} + export function FindPhantomMatches(arg1, arg2) { return window['go']['playlist']['Service']['FindPhantomMatches'](arg1, arg2); }