using System.Collections.Immutable; using System.Globalization; using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace ErsatzTV.Infrastructure.Data.Repositories; public class MediaItemRepository : IMediaItemRepository { private readonly IDbContextFactory _dbContextFactory; public MediaItemRepository(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; public async Task> GetAllKnownCultures() { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); var result = new System.Collections.Generic.HashSet(); CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); foreach (LanguageCode code in await dbContext.LanguageCodes.ToListAsync()) { Option maybeCulture = allCultures.Find( c => string.Equals(code.ThreeCode1, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase) || string.Equals( code.ThreeCode2, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo culture in maybeCulture) { result.Add(culture); } } return result.ToList(); } public async Task> GetAllLanguageCodeCultures() { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); var result = new System.Collections.Generic.HashSet(); CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); List mediaCodes = await GetAllLanguageCodes(); foreach (string mediaCode in mediaCodes) { foreach (string code in await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCode)) { Option maybeCulture = allCultures.Find( c => string.Equals(code, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo culture in maybeCulture) { result.Add(culture); } } } return result.ToList(); } public async Task> FlagFileNotFound(LibraryPath libraryPath, string path) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); List ids = await dbContext.Connection.QueryAsync( @"SELECT M.Id FROM MediaItem M INNER JOIN MediaVersion MV on M.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, ImageId, EpisodeId) INNER JOIN MediaFile MF on MV.Id = MF.MediaVersionId WHERE M.LibraryPathId = @LibraryPathId AND MF.Path = @Path", new { LibraryPathId = libraryPath.Id, Path = path }) .Map(result => result.ToList()); await dbContext.Connection.ExecuteAsync( @"UPDATE MediaItem SET State = 1 WHERE Id IN @Ids", new { Ids = ids }); return ids; } public async Task> GetAllTrashedItems(LibraryPath libraryPath) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT MF.Path FROM MediaItem M INNER JOIN MediaVersion MV on M.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId, ImageId) INNER JOIN MediaFile MF on MV.Id = MF.MediaVersionId WHERE M.State IN (1,2) AND M.LibraryPathId = @LibraryPathId", new { LibraryPathId = libraryPath.Id }) .Map(list => list.ToImmutableHashSet()); } public async Task FlagNormal(MediaItem mediaItem) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); mediaItem.State = MediaItemState.Normal; return await dbContext.Connection.ExecuteAsync( @"UPDATE MediaItem SET State = 0 WHERE Id = @Id", new { mediaItem.Id }).ToUnit(); } public async Task> DeleteItems(List mediaItemIds) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); foreach (int mediaItemId in mediaItemIds) { await dbContext.Connection.ExecuteAsync( "DELETE FROM MediaItem WHERE Id = @Id", new { Id = mediaItemId }); } return Unit.Default; } public static async Task MediaFileAlreadyExists( MediaItem incoming, int libraryPathId, TvContext dbContext, ILogger logger) { string path = incoming.GetHeadVersion().MediaFiles.Head().Path; return await MediaFileAlreadyExists(path, libraryPathId, dbContext, logger); } public static async Task MediaFileAlreadyExists( string path, int libraryPathId, TvContext dbContext, ILogger logger) { Option maybeMediaItemId = await dbContext.Connection .QuerySingleOrDefaultAsync( @"select coalesce(EpisodeId, MovieId, MusicVideoId, OtherVideoId, SongId) as MediaItemId from MediaVersion MV inner join MediaFile MF on MV.Id = MF.MediaVersionId where MF.Path = @Path", new { Path = path }) .Map(Optional); foreach (int mediaItemId in maybeMediaItemId) { Option maybeMediaItem = await dbContext.MediaItems .AsNoTracking() .Include(mi => mi.LibraryPath) .ThenInclude(lp => lp.Library) .SelectOneAsync(mi => mi.Id, mi => mi.Id == mediaItemId); foreach (MediaItem mediaItem in maybeMediaItem) { Option maybeIncomingLibrary = await dbContext.Libraries .Filter(l => l.Paths.Any(p => p.Id == libraryPathId)) .SingleOrDefaultAsync() .Map(Optional); foreach (Library incomingLibrary in maybeIncomingLibrary) { string incomingLibraryType = incomingLibrary switch { PlexLibrary => "Plex Library", EmbyLibrary => "Emby Library", JellyfinLibrary => "Jellyfin Library", _ => "Local Library" }; string existingLibraryType = mediaItem.LibraryPath.Library switch { PlexLibrary => "Plex Library", EmbyLibrary => "Emby Library", JellyfinLibrary => "Jellyfin Library", _ => "Local Library" }; string libraryName = mediaItem.LibraryPath.Library.Name; logger.LogDebug( "Unable to add media item to {IncomingLibraryType} '{IncomingLibraryName}'; {LibraryType} '{LibraryName}' already contains path {Path}", incomingLibraryType, incomingLibrary.Name, existingLibraryType, libraryName, path); return true; } } } return false; } private async Task> GetAllLanguageCodes() { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.QueryAsync( @"SELECT LanguageCode FROM (SELECT Language AS LanguageCode FROM MediaStream WHERE Language IS NOT NULL UNION ALL SELECT PreferredAudioLanguageCode AS LanguageCode FROM Channel WHERE PreferredAudioLanguageCode IS NOT NULL) AS A GROUP BY LanguageCode ORDER BY COUNT(LanguageCode) DESC") .Map(result => result.ToList()); } }