From 8d7fbe63c55ec54281b04078c52cec46d6fd517b Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Tue, 6 Jan 2026 16:43:16 -0500 Subject: [PATCH] fix: amll-ttml-db lyrics searching issue (due to metadata matching calculator error) --- .../BetterLyrics.WinUI3/App.xaml.cs | 127 +++++------------- .../Helper/MetadataComparer.cs | 4 +- .../BetterLyrics.WinUI3/Helper/PathHelper.cs | 1 + .../Models/Db/SongSearchMapDbContext.cs | 14 ++ .../Models/FilesIndexItem.cs | 4 +- .../Models/MappedSongSearchQuery.cs | 12 +- .../Models/Settings/AppSettings.cs | 3 +- .../Services/GSMTCService/GSMTCService.cs | 13 -- .../LyricsCacheService/LyricsCacheService.cs | 15 ++- .../LyricsSearchService.cs | 23 ++-- .../ISongSearchMapService.cs | 15 +++ .../SongSearchMapService.cs | 78 +++++++++++ .../LyricsSearchControlViewModel.cs | 67 ++++----- 13 files changed, 206 insertions(+), 170 deletions(-) create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Db/SongSearchMapDbContext.cs create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/ISongSearchMapService.cs create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/SongSearchMapService.cs diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs index c1bb3b6..3b23d44 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/App.xaml.cs @@ -12,6 +12,7 @@ using BetterLyrics.WinUI3.Services.LyricsSearchService; using BetterLyrics.WinUI3.Services.PlayHistoryService; using BetterLyrics.WinUI3.Services.SettingsService; using BetterLyrics.WinUI3.Services.SMTCService; +using BetterLyrics.WinUI3.Services.SongSearchMapService; using BetterLyrics.WinUI3.Services.TranslationService; using BetterLyrics.WinUI3.Services.TransliterationService; using BetterLyrics.WinUI3.ViewModels; @@ -127,13 +128,25 @@ namespace BetterLyrics.WinUI3 protected override async void OnLaunched(LaunchActivatedEventArgs args) { - // 初始化数据库 - await EnsureDatabasesAsync(); + await InitDatabasesAsync(); var settingsService = Ioc.Default.GetRequiredService(); + + // Migrate MappedSongSearchQueries + var songSearchMapService = Ioc.Default.GetRequiredService(); + var obsoleteSongSearchMap = settingsService.AppSettings.MappedSongSearchQueries; + if (obsoleteSongSearchMap.Count > 0) + { + foreach (var item in obsoleteSongSearchMap) + { + await songSearchMapService.SaveMappingAsync(item); + } + obsoleteSongSearchMap.Clear(); + } + + // Start scan tasks in background var fileSystemService = Ioc.Default.GetRequiredService(); - // 开始后台扫描任务 foreach (var item in settingsService.AppSettings.LocalMediaFolders) { if (item.LastSyncTime == null) @@ -143,10 +156,10 @@ namespace BetterLyrics.WinUI3 } fileSystemService.StartAllFolderTimers(); - // 初始化托盘 + // Init system tray m_window = WindowHook.OpenOrShowWindow(); - // 根据设置打开歌词窗口 + // Open lyrics window if set if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow) { var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault); @@ -163,109 +176,39 @@ namespace BetterLyrics.WinUI3 } } - // 根据设置自动打开主界面 + // Open music gallery if set if (settingsService.AppSettings.MusicGallerySettings.AutoOpen) { WindowHook.OpenOrShowWindow(); } } - private async Task EnsureDatabasesAsync() + private async Task InitDatabasesAsync() { + // Init databases var playHistoryFactory = Ioc.Default.GetRequiredService>(); + var songSearchMapFactory = Ioc.Default.GetRequiredService>(); var filesIndexFactory = Ioc.Default.GetRequiredService>(); var lyricsCacheFactory = Ioc.Default.GetRequiredService>(); - await SafeInitDatabaseAsync( - "PlayHistory", - PathHelper.PlayHistoryPath, - async () => - { - using var db = await playHistoryFactory.CreateDbContextAsync(); - await db.Database.EnsureCreatedAsync(); - }, - isCritical: true - ); - - await SafeInitDatabaseAsync( - "FileCache", - PathHelper.FilesIndexPath, - async () => - { - using var db = await filesIndexFactory.CreateDbContextAsync(); - await db.Database.EnsureCreatedAsync(); - }, - isCritical: false - ); - - await SafeInitDatabaseAsync( - "LyricsCache", - PathHelper.FilesIndexPath, - async () => - { - using var db = await lyricsCacheFactory.CreateDbContextAsync(); - await db.Database.EnsureCreatedAsync(); - }, - isCritical: false - ); - } - - private async Task SafeInitDatabaseAsync(string dbName, string dbPath, Func initAction, bool isCritical) - { - try + using (var playHistoryDb = await playHistoryFactory.CreateDbContextAsync()) { - await initAction(); + await playHistoryDb.Database.EnsureCreatedAsync(); } - catch (Exception ex) + + using (var songSearchMapDb = await songSearchMapFactory.CreateDbContextAsync()) { - System.Diagnostics.Debug.WriteLine($"[DB Error] {dbName} init failed: {ex.Message}"); - - try - { - if (File.Exists(dbPath)) - { - // 尝试清理连接池 - SqliteConnection.ClearAllPools(); - - if (isCritical) - { - var backupPath = dbPath + ".bak_" + DateTime.Now.ToString("yyyyMMddHHmmss"); - File.Move(dbPath, backupPath, true); - await ShowErrorDialogAsync("Database Recovery", $"Database {dbName} is damaged, the old database has been backed up to {backupPath}, and the program will create a new database."); - } - else - { - File.Delete(dbPath); - } - } - await initAction(); - System.Diagnostics.Debug.WriteLine($"[DB Info] {dbName} recovered successfully."); - } - catch (Exception fatalEx) - { - System.Diagnostics.Debug.WriteLine($"[] : {fatalEx.Message}"); - await ShowErrorDialogAsync("Fatal Error", $"{dbName} recovery failed, please delete the file at {dbPath} and try again by restarting the program. ({fatalEx.Message})"); - } + await songSearchMapDb.Database.EnsureCreatedAsync(); } - } - private async Task ShowErrorDialogAsync(string title, string content) - { - // 这里假设 m_window 已经存在。如果没有显示主窗口,这个弹窗可能无法显示。 - // 在 App 启动极早期的错误,可能需要退化为 Log 或者 System.Diagnostics.Process.Start 打开记事本报错 - if (m_window != null) + using (var filesIndexDb = await filesIndexFactory.CreateDbContextAsync()) { - m_window.DispatcherQueue.TryEnqueue(async () => - { - var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog - { - Title = title, - Content = content, - CloseButtonText = "OK", - XamlRoot = m_window.Content?.XamlRoot // 确保 Content 不为空 - }; - if (dialog.XamlRoot != null) await dialog.ShowAsync(); - }); + await filesIndexDb.Database.EnsureCreatedAsync(); + } + + using (var lyricsCacheDb = await lyricsCacheFactory.CreateDbContextAsync()) + { + await lyricsCacheDb.Database.EnsureCreatedAsync(); } } @@ -283,6 +226,7 @@ namespace BetterLyrics.WinUI3 .AddDbContextFactory(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}")) .AddDbContextFactory(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}")) .AddDbContextFactory(options => options.UseSqlite($"Data Source={PathHelper.LyricsCachePath}")) + .AddDbContextFactory(options => options.UseSqlite($"Data Source={PathHelper.SongSearchMapPath}")) // 日志 .AddLogging(loggingBuilder => @@ -305,6 +249,7 @@ namespace BetterLyrics.WinUI3 .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() // ViewModels .AddSingleton() diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/MetadataComparer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/MetadataComparer.cs index 4b5b471..1374c0f 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/MetadataComparer.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/MetadataComparer.cs @@ -44,9 +44,9 @@ namespace BetterLyrics.WinUI3.Helper ? $"{local.Title} {local.Artist}" : Path.GetFileNameWithoutExtension(local.LinkedFileName); - string remoteQuery = remoteHasMetadata + string? remoteQuery = remoteHasMetadata ? $"{remote.Title} {remote.Artist}" - : Path.GetFileNameWithoutExtension(remote.Reference); + : null; string fp1 = CreateSortedFingerprint(localQuery); string fp2 = CreateSortedFingerprint(remoteQuery); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs index fe8035b..9ffffc1 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PathHelper.cs @@ -51,6 +51,7 @@ namespace BetterLyrics.WinUI3.Helper public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u"); public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db"); public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db"); + public static string SongSearchMapPath => Path.Combine(LocalFolder, "song-search-map.db"); public static string LyricsCachePath => Path.Combine(LyricsCacheDirectory, "lyrics-cache.db"); public static void EnsureDirectories() diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Db/SongSearchMapDbContext.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Db/SongSearchMapDbContext.cs new file mode 100644 index 0000000..4471f3f --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Db/SongSearchMapDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BetterLyrics.WinUI3.Models.Db +{ + public partial class SongSearchMapDbContext : DbContext + { + public DbSet SongSearchMap { get; set; } + + public SongSearchMapDbContext(DbContextOptions options) : base(options) { } + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/FilesIndexItem.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/FilesIndexItem.cs index 1f14f99..df5abb2 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/FilesIndexItem.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/FilesIndexItem.cs @@ -10,9 +10,7 @@ namespace BetterLyrics.WinUI3.Models [Index(nameof(Uri), IsUnique = true)] // 唯一索引 public class FilesIndexItem { - [Key] // 主键 - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 明确指定为自增 (Identity) - public int Id { get; set; } + [Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } // 关联到 MediaFolder.Id // 注意:作为索引列,必须限制长度,否则 SQL Server 会报错 (索引最大900字节) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MappedSongSearchQuery.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MappedSongSearchQuery.cs index 87c1b8f..a63b9a4 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MappedSongSearchQuery.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/MappedSongSearchQuery.cs @@ -1,10 +1,18 @@ using BetterLyrics.WinUI3.Enums; using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace BetterLyrics.WinUI3.Models { - public partial class MappedSongSearchQuery : ObservableRecipient + [Table("SongSearchMap")] + [Index(nameof(OriginalTitle), nameof(OriginalArtist), nameof(OriginalAlbum))] + public partial class MappedSongSearchQuery : ObservableRecipient, ICloneable { + [Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } + [ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalTitle { get; set; } = string.Empty; [ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalArtist { get; set; } = string.Empty; [ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalAlbum { get; set; } = string.Empty; @@ -17,7 +25,7 @@ namespace BetterLyrics.WinUI3.Models [ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; } - public MappedSongSearchQuery Clone() + public object Clone() { return new MappedSongSearchQuery { diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Settings/AppSettings.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Settings/AppSettings.cs index 89e2824..d57bc7a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Settings/AppSettings.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/Settings/AppSettings.cs @@ -1,5 +1,6 @@ using BetterLyrics.WinUI3.Collections; using CommunityToolkit.Mvvm.ComponentModel; +using System; namespace BetterLyrics.WinUI3.Models.Settings { @@ -14,7 +15,7 @@ namespace BetterLyrics.WinUI3.Models.Settings [ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection LocalMediaFolders { get; set; } = []; [ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection MediaSourceProvidersInfo { get; set; } = []; - [ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection MappedSongSearchQueries { get; set; } = []; + [Obsolete][ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection MappedSongSearchQueries { get; set; } = []; [ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection WindowBoundsRecords { get; set; } = []; [ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection StarredPlaylists { get; set; } = []; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/GSMTCService/GSMTCService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/GSMTCService/GSMTCService.cs index 9db0be2..114ed14 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/GSMTCService/GSMTCService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/GSMTCService/GSMTCService.cs @@ -110,9 +110,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService _settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged; - _settingsService.AppSettings.MappedSongSearchQueries.CollectionChanged += MappedSongSearchQueries_CollectionChanged; - _settingsService.AppSettings.MappedSongSearchQueries.ItemPropertyChanged += MappedSongSearchQueries_ItemPropertyChanged; - InitMediaManager(); } @@ -151,16 +148,6 @@ namespace BetterLyrics.WinUI3.Services.GSMTCService } } - private void MappedSongSearchQueries_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e) - { - UpdateLyrics(); - } - - private void MappedSongSearchQueries_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - UpdateLyrics(); - } - private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { UpdateAlbumArt(); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsCacheService/LyricsCacheService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsCacheService/LyricsCacheService.cs index 3ee5fc3..0226976 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsCacheService/LyricsCacheService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsCacheService/LyricsCacheService.cs @@ -35,7 +35,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService } /// - /// Write or update cache to DB + /// Write cache to DB /// public async Task SaveLyricsAsync(SongInfo songInfo, LyricsCacheItem result) { @@ -46,16 +46,17 @@ namespace BetterLyrics.WinUI3.Services.LyricsCacheService var existingItem = await context.LyricsCache .FirstOrDefaultAsync(x => x.CacheKey == key && x.Provider == result.Provider); - var newItem = (LyricsCacheItem)result.Clone(); - newItem.CacheKey = key; + if (existingItem == null) + { + var newItem = (LyricsCacheItem)result.Clone(); + newItem.CacheKey = key; - if (existingItem != null) - { - context.LyricsCache.Update(newItem); + await context.LyricsCache.AddAsync(newItem); } else { - await context.LyricsCache.AddAsync(newItem); + // No need to handle this case + return; } await context.SaveChangesAsync(); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs index f6098a8..b813ce1 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/LyricsSearchService/LyricsSearchService.cs @@ -8,6 +8,7 @@ using BetterLyrics.WinUI3.Providers; using BetterLyrics.WinUI3.Services.FileSystemService; using BetterLyrics.WinUI3.Services.LyricsCacheService; using BetterLyrics.WinUI3.Services.SettingsService; +using BetterLyrics.WinUI3.Services.SongSearchMapService; using Lyricify.Lyrics.Helpers; using Lyricify.Lyrics.Searchers; using Microsoft.Extensions.Logging; @@ -32,18 +33,21 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService private readonly ISettingsService _settingsService; private readonly IFileSystemService _fileSystemService; private readonly ILyricsCacheService _lyricsCacheService; + private readonly ISongSearchMapService _songSearchMapService; private readonly ILogger _logger; public LyricsSearchService( - ISettingsService settingsService, - IFileSystemService fileSystemService, - ILyricsCacheService lyricsCacheService, + ISettingsService settingsService, + IFileSystemService fileSystemService, + ILyricsCacheService lyricsCacheService, + ISongSearchMapService songSearchMapService, ILogger logger ) { _settingsService = settingsService; _fileSystemService = fileSystemService; _lyricsCacheService = lyricsCacheService; + _songSearchMapService = songSearchMapService; _logger = logger; _lrcLibHttpClient = new(); @@ -117,11 +121,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService _logger.LogInformation("SearchSmartlyAsync {SongInfo}", songInfo); // 先检查该曲目是否已被用户映射 - var found = _settingsService.AppSettings.MappedSongSearchQueries - .FirstOrDefault(x => - x.OriginalTitle == overridenTitle && - x.OriginalArtist == overridenArtist && - x.OriginalAlbum == overridenAlbum); + var found = await _songSearchMapService.GetMappingAsync(overridenTitle, overridenArtist, overridenAlbum); if (found != null) { @@ -408,7 +408,6 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService } } - int bestScore = 0; string? rawLyricFile = null; await foreach (var line in File.ReadLinesAsync(PathHelper.AmllTtmlDbIndexPath)) { @@ -445,7 +444,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService Artist = artist, Album = album, }); - if (score > bestScore) + if (score > lyricsSearchResult.MatchPercentage) { if (root.TryGetProperty("rawLyricFile", out var rawLyricFileProp)) { @@ -453,15 +452,13 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService lyricsSearchResult.Title = title; lyricsSearchResult.Artist = artist; lyricsSearchResult.Album = album; - bestScore = score; + lyricsSearchResult.MatchPercentage = score; } } } catch { } } - lyricsSearchResult.MatchPercentage = MetadataComparer.CalculateScore(songInfo, lyricsSearchResult); - if (string.IsNullOrWhiteSpace(rawLyricFile)) { return lyricsSearchResult; diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/ISongSearchMapService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/ISongSearchMapService.cs new file mode 100644 index 0000000..d96ce13 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/ISongSearchMapService.cs @@ -0,0 +1,15 @@ +using BetterLyrics.WinUI3.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Services.SongSearchMapService +{ + public interface ISongSearchMapService + { + Task SaveMappingAsync(MappedSongSearchQuery mapping); + Task GetMappingAsync(string title, string artist, string album); + Task DeleteMappingAsync(MappedSongSearchQuery mapping); + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/SongSearchMapService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/SongSearchMapService.cs new file mode 100644 index 0000000..02c0a17 --- /dev/null +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/SongSearchMapService/SongSearchMapService.cs @@ -0,0 +1,78 @@ +using BetterLyrics.WinUI3.Models; +using BetterLyrics.WinUI3.Models.Db; +using Microsoft.EntityFrameworkCore; +using System; +using System.Threading.Tasks; + +namespace BetterLyrics.WinUI3.Services.SongSearchMapService +{ + public class SongSearchMapService : ISongSearchMapService + { + private readonly IDbContextFactory _contextFactory; + + public SongSearchMapService(IDbContextFactory contextFactory) + { + _contextFactory = contextFactory; + } + + public async Task SaveMappingAsync(MappedSongSearchQuery mapping) + { + using var context = await _contextFactory.CreateDbContextAsync(); + + var existing = await context.SongSearchMap + .FirstOrDefaultAsync(x => + x.OriginalTitle == mapping.OriginalTitle && + x.OriginalArtist == mapping.OriginalArtist && + x.OriginalAlbum == mapping.OriginalAlbum); + + if (existing != null) + { + existing.MappedTitle = mapping.MappedTitle; + existing.MappedArtist = mapping.MappedArtist; + existing.MappedAlbum = mapping.MappedAlbum; + + existing.IsMarkedAsPureMusic = mapping.IsMarkedAsPureMusic; + existing.LyricsSearchProvider = mapping.LyricsSearchProvider; + + context.SongSearchMap.Update(existing); + } + else + { + var newItem = (MappedSongSearchQuery)mapping.Clone(); + await context.SongSearchMap.AddAsync(newItem); + } + + await context.SaveChangesAsync(); + } + + public async Task GetMappingAsync(string title, string artist, string album) + { + using var context = await _contextFactory.CreateDbContextAsync(); + + return await context.SongSearchMap + .AsNoTracking() + .FirstOrDefaultAsync(x => + x.OriginalTitle == title && + x.OriginalArtist == artist && + x.OriginalAlbum == album); + } + + public async Task DeleteMappingAsync(MappedSongSearchQuery mapping) + { + using var context = await _contextFactory.CreateDbContextAsync(); + + var target = await context.SongSearchMap + .FirstOrDefaultAsync(x => + x.OriginalTitle == mapping.OriginalTitle && + x.OriginalArtist == mapping.OriginalArtist && + x.OriginalAlbum == mapping.OriginalAlbum); + + if (target != null) + { + context.SongSearchMap.Remove(target); + await context.SaveChangesAsync(); + } + } + + } +} diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSearchControlViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSearchControlViewModel.cs index dceb869..ebb7621 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSearchControlViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsSearchControlViewModel.cs @@ -3,9 +3,10 @@ using BetterLyrics.WinUI3.Helper; using BetterLyrics.WinUI3.Models; using BetterLyrics.WinUI3.Models.Settings; using BetterLyrics.WinUI3.Parsers.LyricsParser; -using BetterLyrics.WinUI3.Services.LyricsSearchService; using BetterLyrics.WinUI3.Services.GSMTCService; +using BetterLyrics.WinUI3.Services.LyricsSearchService; using BetterLyrics.WinUI3.Services.SettingsService; +using BetterLyrics.WinUI3.Services.SongSearchMapService; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; @@ -22,6 +23,7 @@ namespace BetterLyrics.WinUI3.ViewModels private readonly ILyricsSearchService _lyricsSearchService; private readonly IGSMTCService _gsmtcService; private readonly ISettingsService _settingsService; + private readonly ISongSearchMapService _songSearchMapService; private LatestOnlyTaskRunner _lyricsSearchRunner = new(); @@ -43,24 +45,34 @@ namespace BetterLyrics.WinUI3.ViewModels [ObservableProperty] public partial bool IsSearching { get; set; } = false; - public LyricsSearchControlViewModel(ILyricsSearchService lyricsSearchService, IGSMTCService gsmtcService, ISettingsService settingsService) + public LyricsSearchControlViewModel( + ILyricsSearchService lyricsSearchService, + IGSMTCService gsmtcService, + ISettingsService settingsService, + ISongSearchMapService songSearchMapService + ) { _lyricsSearchService = lyricsSearchService; _gsmtcService = gsmtcService; _settingsService = settingsService; + _songSearchMapService = songSearchMapService; AppSettings = _settingsService.AppSettings; - InitMappedSongSearchQuery(); + _ = InitMappedSongSearchQueryAsync(); } - private void InitMappedSongSearchQuery() + private async Task InitMappedSongSearchQueryAsync() { LyricsSearchResults.Clear(); LyricsDataArr = null; if (_gsmtcService.CurrentSongInfo != null) { - var found = GetMappedSongSearchQueryFromSettings(); + var found = await _songSearchMapService.GetMappingAsync( + _gsmtcService.CurrentSongInfo.Title, + _gsmtcService.CurrentSongInfo.Artist, + _gsmtcService.CurrentSongInfo.Album); + if (found == null) { MappedSongSearchQuery = new MappedSongSearchQuery @@ -75,27 +87,11 @@ namespace BetterLyrics.WinUI3.ViewModels } else { - MappedSongSearchQuery = found.Clone(); + MappedSongSearchQuery = (MappedSongSearchQuery)found.Clone(); } } } - private MappedSongSearchQuery? GetMappedSongSearchQueryFromSettings() - { - if (_gsmtcService.CurrentSongInfo == null) - { - return null; - } - - var found = AppSettings.MappedSongSearchQueries - .FirstOrDefault(x => - x.OriginalTitle == _gsmtcService.CurrentSongInfo.Title && - x.OriginalArtist == _gsmtcService.CurrentSongInfo.Artist && - x.OriginalAlbum == _gsmtcService.CurrentSongInfo.Album); - - return found; - } - public void PlayLyricsLine(LyricsLine? value) { if (value?.StartMs == null) @@ -134,32 +130,27 @@ namespace BetterLyrics.WinUI3.ViewModels } [RelayCommand] - private void Save() + private async Task SaveAsync() { if (MappedSongSearchQuery == null) { return; } - var existing = GetMappedSongSearchQueryFromSettings(); - if (existing != null) - { - AppSettings.MappedSongSearchQueries.Remove(existing); - } - AppSettings.MappedSongSearchQueries.Add(MappedSongSearchQuery); - MappedSongSearchQuery = MappedSongSearchQuery.Clone(); + await _songSearchMapService.SaveMappingAsync(MappedSongSearchQuery); + MappedSongSearchQuery = (MappedSongSearchQuery)MappedSongSearchQuery.Clone(); + _gsmtcService.UpdateLyrics(); } [RelayCommand] - private void Reset() + private async Task ResetAsync() { - var existing = GetMappedSongSearchQueryFromSettings(); - if (existing != null) - { - AppSettings.MappedSongSearchQueries.Remove(existing); - } - InitMappedSongSearchQuery(); + if (MappedSongSearchQuery == null) return; + + await _songSearchMapService.DeleteMappingAsync(MappedSongSearchQuery); + await InitMappedSongSearchQueryAsync(); SelectedLyricsSearchResult = null; + _gsmtcService.UpdateLyrics(); } [RelayCommand] @@ -200,7 +191,7 @@ namespace BetterLyrics.WinUI3.ViewModels { if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo)) { - InitMappedSongSearchQuery(); + _ = InitMappedSongSearchQueryAsync(); } } }