using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using LanguageExt; using Microsoft.EntityFrameworkCore; using static LanguageExt.Prelude; namespace ErsatzTV.Infrastructure.Data.Repositories { public class SongRepository : ISongRepository { private readonly IDbConnection _dbConnection; private readonly IDbContextFactory _dbContextFactory; public SongRepository(IDbConnection dbConnection, IDbContextFactory dbContextFactory) { _dbConnection = dbConnection; _dbContextFactory = dbContextFactory; } public async Task>> GetOrAdd( LibraryPath libraryPath, string path) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); Option maybeExisting = await dbContext.Songs .AsNoTracking() .Include(ov => ov.SongMetadata) .ThenInclude(ovm => ovm.Artwork) .Include(ov => ov.SongMetadata) .ThenInclude(ovm => ovm.Tags) .Include(ov => ov.LibraryPath) .ThenInclude(lp => lp.Library) .Include(ov => ov.MediaVersions) .ThenInclude(ov => ov.MediaFiles) .Include(ov => ov.MediaVersions) .ThenInclude(ov => ov.Streams) .Include(ov => ov.TraktListItems) .ThenInclude(tli => tli.TraktList) .OrderBy(i => i.MediaVersions.First().MediaFiles.First().Path) .SingleOrDefaultAsync(i => i.MediaVersions.First().MediaFiles.First().Path == path); return await maybeExisting.Match( mediaItem => Right>( new MediaItemScanResult(mediaItem) { IsAdded = false }).AsTask(), async () => await AddSong(dbContext, libraryPath.Id, path)); } public Task> FindSongPaths(LibraryPath libraryPath) => _dbConnection.QueryAsync( @"SELECT MF.Path FROM MediaFile MF INNER JOIN MediaVersion MV on MF.MediaVersionId = MV.Id INNER JOIN Song O on MV.SongId = O.Id INNER JOIN MediaItem MI on O.Id = MI.Id WHERE MI.LibraryPathId = @LibraryPathId", new { LibraryPathId = libraryPath.Id }); public async Task> DeleteByPath(LibraryPath libraryPath, string path) { List ids = await _dbConnection.QueryAsync( @"SELECT O.Id FROM Song O INNER JOIN MediaItem MI on O.Id = MI.Id INNER JOIN MediaVersion MV on O.Id = MV.SongId INNER JOIN MediaFile MF on MV.Id = MF.MediaVersionId WHERE MI.LibraryPathId = @LibraryPathId AND MF.Path = @Path", new { LibraryPathId = libraryPath.Id, Path = path }).Map(result => result.ToList()); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); foreach (int songId in ids) { Song song = await dbContext.Songs.FindAsync(songId); if (song != null) { dbContext.Songs.Remove(song); } } await dbContext.SaveChangesAsync(); return ids; } public Task AddTag(SongMetadata metadata, Tag tag) => _dbConnection.ExecuteAsync( "INSERT INTO Tag (Name, SongMetadataId) VALUES (@Name, @MetadataId)", new { tag.Name, MetadataId = metadata.Id }).Map(result => result > 0); public async Task> GetSongsForCards(List ids) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.SongMetadata .AsNoTracking() .Filter(ovm => ids.Contains(ovm.SongId)) .Include(ovm => ovm.Song) .Include(ovm => ovm.Artwork) .OrderBy(ovm => ovm.SortTitle) .ToListAsync(); } private static async Task>> AddSong( TvContext dbContext, int libraryPathId, string path) { try { var song = new Song { LibraryPathId = libraryPathId, MediaVersions = new List { new() { MediaFiles = new List { new() { Path = path } }, Streams = new List() } }, TraktListItems = new List() }; await dbContext.Songs.AddAsync(song); await dbContext.SaveChangesAsync(); await dbContext.Entry(song).Reference(m => m.LibraryPath).LoadAsync(); await dbContext.Entry(song.LibraryPath).Reference(lp => lp.Library).LoadAsync(); return new MediaItemScanResult(song) { IsAdded = true }; } catch (Exception ex) { return BaseError.New(ex.Message); } } } }