feat(quick-8): add FindDuplicateTracksInPlaylist backend method
- Add DuplicateTrackInfo and DuplicateCheckResult types - Implement FindDuplicateTracksInPlaylist on playlist Service - Regenerate Wails TypeScript bindings
This commit is contained in:
@@ -98,6 +98,23 @@ type PhantomSearchResult struct {
|
|||||||
Unmatched []string `json:"Unmatched"`
|
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.
|
// Service manages playlist operations.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// mu protects ctx and favoritesConf from concurrent access
|
// mu protects ctx and favoritesConf from concurrent access
|
||||||
@@ -509,6 +526,64 @@ func (s *Service) AddTracksToPlaylist(
|
|||||||
return nil
|
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
|
// CreatePlaylistWithTracks creates a new playlist and populates
|
||||||
// it with tracks.
|
// it with tracks.
|
||||||
func (s *Service) CreatePlaylistWithTracks(
|
func (s *Service) CreatePlaylistWithTracks(
|
||||||
|
|||||||
@@ -236,6 +236,59 @@ export namespace playlist {
|
|||||||
this.Score = source["Score"];
|
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 {
|
export class PhantomMatch {
|
||||||
PhantomPath: string;
|
PhantomPath: string;
|
||||||
PhantomTitle: string;
|
PhantomTitle: string;
|
||||||
|
|||||||
+2
@@ -15,6 +15,8 @@ export function DeletePlaylist(arg1:number):Promise<void>;
|
|||||||
|
|
||||||
export function EnsureDefaultPlaylist():Promise<void>;
|
export function EnsureDefaultPlaylist():Promise<void>;
|
||||||
|
|
||||||
|
export function FindDuplicateTracksInPlaylist(arg1:number,arg2:Array<string>):Promise<playlist.DuplicateCheckResult>;
|
||||||
|
|
||||||
export function FindPhantomMatches(arg1:number,arg2:Array<string>):Promise<playlist.PhantomSearchResult>;
|
export function FindPhantomMatches(arg1:number,arg2:Array<string>):Promise<playlist.PhantomSearchResult>;
|
||||||
|
|
||||||
export function GetAllPlaylists():Promise<Array<playlist.Summary>>;
|
export function GetAllPlaylists():Promise<Array<playlist.Summary>>;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export function EnsureDefaultPlaylist() {
|
|||||||
return window['go']['playlist']['Service']['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) {
|
export function FindPhantomMatches(arg1, arg2) {
|
||||||
return window['go']['playlist']['Service']['FindPhantomMatches'](arg1, arg2);
|
return window['go']['playlist']['Service']['FindPhantomMatches'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user