From 03940a99ba9cc591735218b26a1531a2c328dbeb Mon Sep 17 00:00:00 2001 From: sherl Date: Wed, 28 Jan 2026 05:16:17 +0100 Subject: [PATCH] feat: orphan songs not present in the media directory --- Tools/LibraryWatcher.cs | 67 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/Tools/LibraryWatcher.cs b/Tools/LibraryWatcher.cs index 1168600..c3036b3 100644 --- a/Tools/LibraryWatcher.cs +++ b/Tools/LibraryWatcher.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using Shadow.Data; using Shadow.Entities; using System; @@ -65,27 +66,79 @@ public class LibraryWatcher(string watchPath, string[] excludedPaths, Applicatio { Console.WriteLine("Performing full library scan..."); - List multimedia = await GetAllMultimediaAsync(); - foreach (string filepath in multimedia) + // Current library state as present in database + List currentLibraryMedia = await db.Songs + .Where(s => s.State == 0) + .ToListAsync(); + + // Updated library state + List updatedSongList = []; + List newMultimediaPathNames = await GetAllMultimediaAsync(); + foreach (string filepath in newMultimediaPathNames) { Console.WriteLine(filepath); Dictionary fileInfo = await MetadataExtractor.ExtractAsync(filepath); // Pretend we are doing parsing here... Console.WriteLine(GeneralUseHelpers.DictAsJson(fileInfo)); - MediaParser.CreateSong(db, fileInfo); + Song? songInDb = db.Songs + .Include(s => s.Album) + .FirstOrDefault(s => s.Uri == fileInfo["_shadow:fileHash"]); + if (songInDb != null) + { + // Don't parse the song + Console.WriteLine("Skipping song as it already exists in database..."); + + // But update it's location in case it has been moved + songInDb.Filepath = filepath; + + // And state in case it has been reinstated + songInDb.State = 0; // Set non-orphaned state + songInDb.Album.State = 0; // -||- + db.Update(songInDb); // Is this necessary? + await db.SaveChangesAsync(); + + // Afterwards include it in the updated song list + updatedSongList.Add(songInDb); + } + else + { + // A new song? Parse it and add to DB + Song? newSong = MediaParser.CreateSong(db, fileInfo); + + // Sanity check + if (newSong != null) + updatedSongList.Add(newSong); + } } - Console.WriteLine($"Full scan complete! Processed {multimedia.Count} files."); + Console.WriteLine($"Full scan complete! Processed {newMultimediaPathNames.Count} files."); + + List orphanedSongs = currentLibraryMedia + .Except(updatedSongList) + .ToList(); + + Console.WriteLine($"Detected {orphanedSongs.Count} new orphaned songs"); + foreach (Song s in orphanedSongs) + { + Song dbSong = db.Songs + .Include(d => d.Artist) + .First(d => d.Id == s.Id); + Console.WriteLine($"- {dbSong.Title} by {dbSong.Artist.Name} (previous path: {dbSong.Filepath})"); + dbSong.State = 1; + await db.SaveChangesAsync(); + } + + Console.WriteLine(); // Update state inside of DB - string currentLibraryState = MetadataExtractor.GetStringMD5(string.Join("\n", multimedia)); + string updatedLibraryState = MetadataExtractor.GetStringMD5(string.Join("\n", newMultimediaPathNames)); Global lastLibraryState = db.Globals.FirstOrDefault(g => g.Key == "libraryState") ?? new() { Key = "libraryState"}; - lastLibraryState.Value = currentLibraryState; + lastLibraryState.Value = updatedLibraryState; db.Update(lastLibraryState); await db.SaveChangesAsync(); - return multimedia; + return newMultimediaPathNames; } }