201 lines
5.9 KiB
C#
201 lines
5.9 KiB
C#
using Harmonia.Core.Scanner;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SongModel = Harmonia.Core.Models.Song;
|
|
|
|
namespace Harmonia.Core.Library;
|
|
|
|
public interface IAudioLibrary
|
|
{
|
|
ValueTask AddLocationAsync(string path, CancellationToken cancellationToken);
|
|
void RemoveLocation(string path);
|
|
void RemoveLocations(string[] paths);
|
|
void Scan(string path);
|
|
void Scan(string[] paths);
|
|
|
|
event EventHandler<EventArgs>? LocationAdded;
|
|
event EventHandler<EventArgs>? LocationRemoved;
|
|
event EventHandler<EventArgs>? ScanStarted;
|
|
event EventHandler<EventArgs>? ScanFinished;
|
|
}
|
|
|
|
public abstract class AudioLibrary : IAudioLibrary
|
|
{
|
|
public event EventHandler<EventArgs>? LocationAdded;
|
|
public event EventHandler<EventArgs>? LocationRemoved;
|
|
public event EventHandler<EventArgs>? ScanStarted;
|
|
public event EventHandler<EventArgs>? ScanFinished;
|
|
|
|
protected abstract ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken);
|
|
protected abstract ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken);
|
|
|
|
public async ValueTask AddLocationAsync(string path, CancellationToken cancellationToken)
|
|
{
|
|
if (await IncludeLocationAsync(path, cancellationToken) == false)
|
|
return;
|
|
|
|
LocationAdded?.Invoke(this, new EventArgs());
|
|
}
|
|
|
|
public void RemoveLocation(string path)
|
|
{
|
|
RemoveLocations([path]);
|
|
}
|
|
|
|
public void RemoveLocations(string[] paths)
|
|
{
|
|
//foreach (string path in paths)
|
|
//{
|
|
// //if (_locations.Contains(path) == false)
|
|
// // continue;
|
|
|
|
|
|
//}
|
|
|
|
LocationRemoved?.Invoke(this, new EventArgs());
|
|
}
|
|
|
|
public void Scan(string path)
|
|
{
|
|
Scan([path]);
|
|
}
|
|
|
|
public void Scan(string[] paths)
|
|
{
|
|
ScanStarted?.Invoke(this, new EventArgs());
|
|
|
|
ScanFinished?.Invoke(this, new EventArgs());
|
|
}
|
|
}
|
|
|
|
public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibraryContext context) : AudioLibrary
|
|
{
|
|
protected override async ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken)
|
|
{
|
|
if (CanAddLocation(path) == false)
|
|
return false;
|
|
|
|
Location location = GetLocation(path) ?? CreateLocation(path);
|
|
await ScanLocationAsync(location, cancellationToken);
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CanAddLocation(string path)
|
|
{
|
|
return context.Folders.Any(folder => folder.Name == path) == false;
|
|
}
|
|
|
|
private Location CreateLocation(string path)
|
|
{
|
|
Location location = new()
|
|
{
|
|
Name = path,
|
|
Type = 0
|
|
};
|
|
|
|
context.Locations.Add(location);
|
|
context.SaveChanges();
|
|
|
|
return location;
|
|
}
|
|
|
|
private Location? GetLocation(string path)
|
|
{
|
|
return context.Locations.FirstOrDefault(location => location.Name == path);
|
|
}
|
|
|
|
protected override async ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken)
|
|
{
|
|
Location? location = GetLocation(path);
|
|
|
|
if (location == null)
|
|
return;
|
|
|
|
await ScanLocationAsync(location, cancellationToken);
|
|
}
|
|
|
|
private async ValueTask ScanLocationAsync(Location location, CancellationToken cancellationToken)
|
|
{
|
|
SongModel[] songModels = await audioFileScanner.GetSongsFromPathAsync(location.Name, cancellationToken);
|
|
|
|
AddFolders(location, songModels);
|
|
AddSongs(songModels);
|
|
|
|
// Remove old songs, and clear out folders that no longer hold songs
|
|
}
|
|
|
|
private void AddFolders(Location location, SongModel[] songModels)
|
|
{
|
|
string[] folderNames = [.. songModels.Select(songModel => Directory.GetParent(songModel.FileName)?.FullName ?? string.Empty)
|
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
.Distinct()];
|
|
|
|
Folder[] folders = [.. context.Folders.Where(folder => folder.Location == location)];
|
|
}
|
|
|
|
private void AddSongs(SongModel[] songModels)
|
|
{
|
|
foreach (SongModel songModel in songModels)
|
|
{
|
|
Folder? folder = context.Folders.FirstOrDefault(x => x.Name == songModel.FileDirectory);
|
|
|
|
if (folder == null)
|
|
continue;
|
|
|
|
Song song = AddOrUpdateSong(songModel, folder);
|
|
AddOrUpdateSongTag(songModel, song);
|
|
}
|
|
}
|
|
|
|
private Song AddOrUpdateSong(SongModel songModel, Folder folder)
|
|
{
|
|
string shortFileName = Path.GetFileName(songModel.FileName);
|
|
|
|
var song = context.Songs.FirstOrDefault(x => x.Folder == folder && string.Equals(x.FileName, shortFileName, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (song == null)
|
|
{
|
|
song = new()
|
|
{
|
|
FileName = shortFileName,
|
|
Folder = folder
|
|
};
|
|
|
|
context.Songs.Add(song);
|
|
}
|
|
|
|
song.Size = (long)songModel.Size;
|
|
song.Duration = songModel.Length.Ticks;
|
|
song.Modified = songModel.LastModified;
|
|
song.BitRate = (int)songModel.BitRate;
|
|
song.SampleRate = (int)songModel.SampleRate;
|
|
|
|
return song;
|
|
}
|
|
|
|
private SongTag AddOrUpdateSongTag(SongModel songModel, Song song)
|
|
{
|
|
var songTag = context.SongTags.FirstOrDefault(x => x.Song == song);
|
|
|
|
if (songTag == null)
|
|
{
|
|
songTag = new SongTag()
|
|
{
|
|
Song = song
|
|
};
|
|
|
|
context.SongTags.Add(songTag);
|
|
}
|
|
|
|
songTag.Title = songModel.Title;
|
|
songTag.Album = songModel.Album;
|
|
songTag.Artist = string.Join(";", songModel.Artists);
|
|
songTag.AlbumArtist = string.Join(";", songModel.AlbumArtists);
|
|
songTag.TrackNumber = songModel.TrackNumber;
|
|
songTag.DiscNumber = songModel.DiscNumber;
|
|
songTag.Genre = songModel.Genre;
|
|
songTag.ReleaseDate = songModel.Year != null ? new DateTime(songModel.Year.Value, 1, 1) : null;
|
|
|
|
return songTag;
|
|
}
|
|
} |