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.Emby; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Jellyfin; using ErsatzTV.Core.Metadata; using ErsatzTV.Infrastructure.Extensions; using LanguageExt; using LanguageExt.UnsafeValueAccess; using Microsoft.EntityFrameworkCore; using static LanguageExt.Prelude; namespace ErsatzTV.Infrastructure.Data.Repositories { public class MovieRepository : IMovieRepository { private readonly IDbConnection _dbConnection; private readonly IDbContextFactory _dbContextFactory; public MovieRepository(IDbContextFactory dbContextFactory, IDbConnection dbConnection) { _dbContextFactory = dbContextFactory; _dbConnection = dbConnection; } public Task AllMoviesExist(List movieIds) => _dbConnection.QuerySingleAsync( "SELECT COUNT(*) FROM Movie WHERE Id in @MovieIds", new { MovieIds = movieIds }) .Map(c => c == movieIds.Count); public async Task> GetMovie(int movieId) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); return await dbContext.Movies .Include(m => m.MovieMetadata) .ThenInclude(m => m.Artwork) .Include(m => m.MovieMetadata) .ThenInclude(m => m.Genres) .Include(m => m.MovieMetadata) .ThenInclude(m => m.Tags) .Include(m => m.MovieMetadata) .ThenInclude(m => m.Studios) .Include(m => m.MovieMetadata) .ThenInclude(m => m.Actors) .ThenInclude(a => a.Artwork) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Directors) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Writers) .Include(m => m.MediaVersions) .ThenInclude(mv => mv.Streams) .OrderBy(m => m.Id) .SingleOrDefaultAsync(m => m.Id == movieId) .Map(Optional); } public async Task>> GetOrAdd(LibraryPath libraryPath, string path) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Option maybeExisting = await dbContext.Movies .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Artwork) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Genres) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Tags) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Studios) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Guids) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Actors) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Actors) .ThenInclude(a => a.Artwork) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Directors) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Writers) .Include(i => i.LibraryPath) .ThenInclude(lp => lp.Library) .Include(i => i.MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.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 AddMovie(dbContext, libraryPath.Id, path)); } public async Task>> GetOrAdd( PlexLibrary library, PlexMovie item) { await using TvContext context = _dbContextFactory.CreateDbContext(); Option maybeExisting = await context.PlexMovies .AsNoTracking() .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Genres) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Tags) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Studios) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Actors) .ThenInclude(a => a.Artwork) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Artwork) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Directors) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Writers) .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Guids) .Include(i => i.MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(i => i.MediaVersions) .ThenInclude(mv => mv.Streams) .Include(i => i.LibraryPath) .ThenInclude(lp => lp.Library) .Include(i => i.TraktListItems) .ThenInclude(tli => tli.TraktList) .OrderBy(i => i.Key) .SingleOrDefaultAsync(i => i.Key == item.Key); return await maybeExisting.Match( plexMovie => Right>( new MediaItemScanResult(plexMovie) { IsAdded = false }).AsTask(), async () => await AddPlexMovie(context, library, item)); } public async Task> GetMoviesForCards(List ids) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); return await dbContext.MovieMetadata .AsNoTracking() .Filter(mm => ids.Contains(mm.MovieId)) .Include(mm => mm.Artwork) .OrderBy(mm => mm.SortTitle) .ToListAsync(); } public Task> FindMoviePaths(LibraryPath libraryPath) => _dbConnection.QueryAsync( @"SELECT MF.Path FROM MediaFile MF INNER JOIN MediaVersion MV on MF.MediaVersionId = MV.Id INNER JOIN Movie M on MV.MovieId = M.Id INNER JOIN MediaItem MI on M.Id = MI.Id WHERE MI.LibraryPathId = @LibraryPathId", new { LibraryPathId = libraryPath.Id }); public async Task> DeleteByPath(LibraryPath libraryPath, string path) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); List ids = await _dbConnection.QueryAsync( @"SELECT M.Id FROM Movie M INNER JOIN MediaItem MI on M.Id = MI.Id INNER JOIN MediaVersion MV on M.Id = MV.MovieId 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()); foreach (int movieId in ids) { Movie movie = await dbContext.Movies.FindAsync(movieId); dbContext.Movies.Remove(movie); } bool changed = await dbContext.SaveChangesAsync() > 0; return changed ? ids : new List(); } public Task AddGenre(MovieMetadata metadata, Genre genre) => _dbConnection.ExecuteAsync( "INSERT INTO Genre (Name, MovieMetadataId) VALUES (@Name, @MetadataId)", new { genre.Name, MetadataId = metadata.Id }).Map(result => result > 0); public Task AddTag(MovieMetadata metadata, Tag tag) => _dbConnection.ExecuteAsync( "INSERT INTO Tag (Name, MovieMetadataId) VALUES (@Name, @MetadataId)", new { tag.Name, MetadataId = metadata.Id }).Map(result => result > 0); public Task AddStudio(MovieMetadata metadata, Studio studio) => _dbConnection.ExecuteAsync( "INSERT INTO Studio (Name, MovieMetadataId) VALUES (@Name, @MetadataId)", new { studio.Name, MetadataId = metadata.Id }).Map(result => result > 0); public async Task AddActor(MovieMetadata metadata, Actor actor) { int? artworkId = null; if (actor.Artwork != null) { artworkId = await _dbConnection.QuerySingleAsync( @"INSERT INTO Artwork (ArtworkKind, DateAdded, DateUpdated, Path) VALUES (@ArtworkKind, @DateAdded, @DateUpdated, @Path); SELECT last_insert_rowid()", new { ArtworkKind = (int) actor.Artwork.ArtworkKind, actor.Artwork.DateAdded, actor.Artwork.DateUpdated, actor.Artwork.Path }); } return await _dbConnection.ExecuteAsync( "INSERT INTO Actor (Name, Role, \"Order\", MovieMetadataId, ArtworkId) VALUES (@Name, @Role, @Order, @MetadataId, @ArtworkId)", new { actor.Name, actor.Role, actor.Order, MetadataId = metadata.Id, ArtworkId = artworkId }) .Map(result => result > 0); } public async Task> RemoveMissingPlexMovies(PlexLibrary library, List movieKeys) { List ids = await _dbConnection.QueryAsync( @"SELECT m.Id FROM MediaItem m INNER JOIN PlexMovie pm ON pm.Id = m.Id INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId WHERE lp.LibraryId = @LibraryId AND pm.Key not in @Keys", new { LibraryId = library.Id, Keys = movieKeys }).Map(result => result.ToList()); await _dbConnection.ExecuteAsync( "DELETE FROM MediaItem WHERE Id IN @Ids", new { Ids = ids }); return ids; } public Task UpdateSortTitle(MovieMetadata movieMetadata) => _dbConnection.ExecuteAsync( @"UPDATE MovieMetadata SET SortTitle = @SortTitle WHERE Id = @Id", new { movieMetadata.SortTitle, movieMetadata.Id }).Map(result => result > 0); public Task> GetExistingJellyfinMovies(JellyfinLibrary library) => _dbConnection.QueryAsync( @"SELECT ItemId, Etag FROM JellyfinMovie INNER JOIN Movie M on JellyfinMovie.Id = M.Id INNER JOIN MediaItem MI on M.Id = MI.Id INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id WHERE LP.LibraryId = @LibraryId", new { LibraryId = library.Id }) .Map(result => result.ToList()); public async Task> RemoveMissingJellyfinMovies(JellyfinLibrary library, List movieIds) { List ids = await _dbConnection.QueryAsync( @"SELECT JellyfinMovie.Id FROM JellyfinMovie INNER JOIN Movie M on JellyfinMovie.Id = M.Id INNER JOIN MediaItem MI on M.Id = MI.Id INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id WHERE LP.LibraryId = @LibraryId AND ItemId IN @ItemIds", new { LibraryId = library.Id, ItemIds = movieIds }).Map(result => result.ToList()); await _dbConnection.ExecuteAsync( "DELETE FROM MediaItem WHERE Id IN @Ids", new { Ids = ids }); return ids; } public async Task AddJellyfin(JellyfinMovie movie) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await dbContext.AddAsync(movie); if (await dbContext.SaveChangesAsync() <= 0) { return false; } await dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync(); await dbContext.Entry(movie.LibraryPath).Reference(lp => lp.Library).LoadAsync(); return true; } public async Task> UpdateJellyfin(JellyfinMovie movie) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Option maybeExisting = await dbContext.JellyfinMovies .Include(m => m.LibraryPath) .ThenInclude(lp => lp.Library) .Include(m => m.MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(m => m.MediaVersions) .ThenInclude(mv => mv.Streams) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Genres) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Tags) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Studios) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Actors) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Artwork) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Directors) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Writers) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Guids) .Include(m => m.TraktListItems) .ThenInclude(tli => tli.TraktList) .Filter(m => m.ItemId == movie.ItemId) .OrderBy(m => m.ItemId) .SingleOrDefaultAsync(); if (maybeExisting.IsSome) { JellyfinMovie existing = maybeExisting.ValueUnsafe(); // library path is used for search indexing later movie.LibraryPath = existing.LibraryPath; movie.Id = existing.Id; existing.Etag = movie.Etag; // metadata MovieMetadata metadata = existing.MovieMetadata.Head(); MovieMetadata incomingMetadata = movie.MovieMetadata.Head(); metadata.MetadataKind = incomingMetadata.MetadataKind; metadata.ContentRating = incomingMetadata.ContentRating; metadata.Title = incomingMetadata.Title; metadata.SortTitle = incomingMetadata.SortTitle; metadata.Plot = incomingMetadata.Plot; metadata.Year = incomingMetadata.Year; metadata.Tagline = incomingMetadata.Tagline; metadata.DateAdded = incomingMetadata.DateAdded; metadata.DateUpdated = DateTime.UtcNow; // genres foreach (Genre genre in metadata.Genres .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Genres.Remove(genre); } foreach (Genre genre in incomingMetadata.Genres .Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Genres.Add(genre); } // tags foreach (Tag tag in metadata.Tags .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Tags.Remove(tag); } foreach (Tag tag in incomingMetadata.Tags .Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Tags.Add(tag); } // studios foreach (Studio studio in metadata.Studios .Filter(g => incomingMetadata.Studios.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Studios.Remove(studio); } foreach (Studio studio in incomingMetadata.Studios .Filter(g => metadata.Studios.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Studios.Add(studio); } // actors foreach (Actor actor in metadata.Actors .Filter( a => incomingMetadata.Actors.All( a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); } foreach (Actor actor in incomingMetadata.Actors .Filter(a => metadata.Actors.All(a2 => a2.Name != a.Name)) .ToList()) { metadata.Actors.Add(actor); } // directors foreach (Director director in metadata.Directors .Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name)) .ToList()) { metadata.Directors.Remove(director); } foreach (Director director in incomingMetadata.Directors .Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)) .ToList()) { metadata.Directors.Add(director); } // writers foreach (Writer writer in metadata.Writers .Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name)) .ToList()) { metadata.Writers.Remove(writer); } foreach (Writer writer in incomingMetadata.Writers .Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)) .ToList()) { metadata.Writers.Add(writer); } // guids foreach (MetadataGuid guid in metadata.Guids .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { metadata.Guids.Remove(guid); } foreach (MetadataGuid guid in incomingMetadata.Guids .Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { metadata.Guids.Add(guid); } metadata.ReleaseDate = incomingMetadata.ReleaseDate; // poster Artwork incomingPoster = incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster); if (incomingPoster != null) { Artwork poster = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster); if (poster == null) { poster = new Artwork { ArtworkKind = ArtworkKind.Poster }; metadata.Artwork.Add(poster); } poster.Path = incomingPoster.Path; poster.DateAdded = incomingPoster.DateAdded; poster.DateUpdated = incomingPoster.DateUpdated; } // fan art Artwork incomingFanArt = incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt); if (incomingFanArt != null) { Artwork fanArt = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt); if (fanArt == null) { fanArt = new Artwork { ArtworkKind = ArtworkKind.FanArt }; metadata.Artwork.Add(fanArt); } fanArt.Path = incomingFanArt.Path; fanArt.DateAdded = incomingFanArt.DateAdded; fanArt.DateUpdated = incomingFanArt.DateUpdated; } // version MediaVersion version = existing.MediaVersions.Head(); MediaVersion incomingVersion = movie.MediaVersions.Head(); version.Name = incomingVersion.Name; version.DateAdded = incomingVersion.DateAdded; // media file MediaFile file = version.MediaFiles.Head(); MediaFile incomingFile = incomingVersion.MediaFiles.Head(); file.Path = incomingFile.Path; } await dbContext.SaveChangesAsync(); return maybeExisting; } public Task> GetExistingEmbyMovies(EmbyLibrary library) => _dbConnection.QueryAsync( @"SELECT ItemId, Etag FROM EmbyMovie INNER JOIN Movie M on EmbyMovie.Id = M.Id INNER JOIN MediaItem MI on M.Id = MI.Id INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id WHERE LP.LibraryId = @LibraryId", new { LibraryId = library.Id }) .Map(result => result.ToList()); public async Task> RemoveMissingEmbyMovies(EmbyLibrary library, List movieIds) { List ids = await _dbConnection.QueryAsync( @"SELECT EmbyMovie.Id FROM EmbyMovie INNER JOIN Movie M on EmbyMovie.Id = M.Id INNER JOIN MediaItem MI on M.Id = MI.Id INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id WHERE LP.LibraryId = @LibraryId AND ItemId IN @ItemIds", new { LibraryId = library.Id, ItemIds = movieIds }).Map(result => result.ToList()); await _dbConnection.ExecuteAsync( "DELETE FROM MediaItem WHERE Id IN @Ids", new { Ids = ids }); return ids; } public async Task AddEmby(EmbyMovie movie) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); await dbContext.AddAsync(movie); if (await dbContext.SaveChangesAsync() <= 0) { return false; } await dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync(); await dbContext.Entry(movie.LibraryPath).Reference(lp => lp.Library).LoadAsync(); return true; } public async Task> UpdateEmby(EmbyMovie movie) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Option maybeExisting = await dbContext.EmbyMovies .Include(m => m.LibraryPath) .ThenInclude(lp => lp.Library) .Include(m => m.MediaVersions) .ThenInclude(mv => mv.MediaFiles) .Include(m => m.MediaVersions) .ThenInclude(mv => mv.Streams) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Genres) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Tags) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Studios) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Actors) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Directors) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Writers) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Artwork) .Include(m => m.MovieMetadata) .ThenInclude(mm => mm.Guids) .Filter(m => m.ItemId == movie.ItemId) .Include(m => m.TraktListItems) .ThenInclude(tli => tli.TraktList) .OrderBy(m => m.ItemId) .SingleOrDefaultAsync(); if (maybeExisting.IsSome) { EmbyMovie existing = maybeExisting.ValueUnsafe(); // library path is used for search indexing later movie.LibraryPath = existing.LibraryPath; movie.Id = existing.Id; existing.Etag = movie.Etag; // metadata MovieMetadata metadata = existing.MovieMetadata.Head(); MovieMetadata incomingMetadata = movie.MovieMetadata.Head(); metadata.MetadataKind = incomingMetadata.MetadataKind; metadata.ContentRating = incomingMetadata.ContentRating; metadata.Title = incomingMetadata.Title; metadata.SortTitle = incomingMetadata.SortTitle; metadata.Plot = incomingMetadata.Plot; metadata.Year = incomingMetadata.Year; metadata.Tagline = incomingMetadata.Tagline; metadata.DateAdded = incomingMetadata.DateAdded; metadata.DateUpdated = DateTime.UtcNow; // genres foreach (Genre genre in metadata.Genres .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Genres.Remove(genre); } foreach (Genre genre in incomingMetadata.Genres .Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Genres.Add(genre); } // tags foreach (Tag tag in metadata.Tags .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Tags.Remove(tag); } foreach (Tag tag in incomingMetadata.Tags .Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Tags.Add(tag); } // studios foreach (Studio studio in metadata.Studios .Filter(g => incomingMetadata.Studios.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Studios.Remove(studio); } foreach (Studio studio in incomingMetadata.Studios .Filter(g => metadata.Studios.All(g2 => g2.Name != g.Name)) .ToList()) { metadata.Studios.Add(studio); } // actors foreach (Actor actor in metadata.Actors .Filter( a => incomingMetadata.Actors.All( a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); } foreach (Actor actor in incomingMetadata.Actors .Filter(a => metadata.Actors.All(a2 => a2.Name != a.Name)) .ToList()) { metadata.Actors.Add(actor); } // directors foreach (Director director in metadata.Directors .Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name)) .ToList()) { metadata.Directors.Remove(director); } foreach (Director director in incomingMetadata.Directors .Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)) .ToList()) { metadata.Directors.Add(director); } // writers foreach (Writer writer in metadata.Writers .Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name)) .ToList()) { metadata.Writers.Remove(writer); } foreach (Writer writer in incomingMetadata.Writers .Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)) .ToList()) { metadata.Writers.Add(writer); } // guids foreach (MetadataGuid guid in metadata.Guids .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { metadata.Guids.Remove(guid); } foreach (MetadataGuid guid in incomingMetadata.Guids .Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { metadata.Guids.Add(guid); } metadata.ReleaseDate = incomingMetadata.ReleaseDate; // poster Artwork incomingPoster = incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster); if (incomingPoster != null) { Artwork poster = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster); if (poster == null) { poster = new Artwork { ArtworkKind = ArtworkKind.Poster }; metadata.Artwork.Add(poster); } poster.Path = incomingPoster.Path; poster.DateAdded = incomingPoster.DateAdded; poster.DateUpdated = incomingPoster.DateUpdated; } // fan art Artwork incomingFanArt = incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt); if (incomingFanArt != null) { Artwork fanArt = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.FanArt); if (fanArt == null) { fanArt = new Artwork { ArtworkKind = ArtworkKind.FanArt }; metadata.Artwork.Add(fanArt); } fanArt.Path = incomingFanArt.Path; fanArt.DateAdded = incomingFanArt.DateAdded; fanArt.DateUpdated = incomingFanArt.DateUpdated; } // version MediaVersion version = existing.MediaVersions.Head(); MediaVersion incomingVersion = movie.MediaVersions.Head(); version.Name = incomingVersion.Name; version.DateAdded = incomingVersion.DateAdded; // media file MediaFile file = version.MediaFiles.Head(); MediaFile incomingFile = incomingVersion.MediaFiles.Head(); file.Path = incomingFile.Path; } await dbContext.SaveChangesAsync(); return maybeExisting; } public Task AddDirector(MovieMetadata metadata, Director director) => _dbConnection.ExecuteAsync( "INSERT INTO Director (Name, MovieMetadataId) VALUES (@Name, @MetadataId)", new { director.Name, MetadataId = metadata.Id }).Map(result => result > 0); public Task AddWriter(MovieMetadata metadata, Writer writer) => _dbConnection.ExecuteAsync( "INSERT INTO Writer (Name, MovieMetadataId) VALUES (@Name, @MetadataId)", new { writer.Name, MetadataId = metadata.Id }).Map(result => result > 0); public Task UpdatePath(int mediaFileId, string path) => _dbConnection.ExecuteAsync( "UPDATE MediaFile SET Path = @Path WHERE Id = @MediaFileId", new { Path = path, MediaFileId = mediaFileId }).Map(_ => Unit.Default); private static async Task>> AddMovie( TvContext dbContext, int libraryPathId, string path) { try { var movie = new Movie { LibraryPathId = libraryPathId, MediaVersions = new List { new() { MediaFiles = new List { new() { Path = path } }, Streams = new List() } }, TraktListItems = new List() }; await dbContext.Movies.AddAsync(movie); await dbContext.SaveChangesAsync(); await dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync(); await dbContext.Entry(movie.LibraryPath).Reference(lp => lp.Library).LoadAsync(); return new MediaItemScanResult(movie) { IsAdded = true }; } catch (Exception ex) { return BaseError.New(ex.Message); } } private async Task>> AddPlexMovie( TvContext context, PlexLibrary library, PlexMovie item) { try { item.LibraryPathId = library.Paths.Head().Id; await context.PlexMovies.AddAsync(item); await context.SaveChangesAsync(); await context.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); await context.Entry(item.LibraryPath).Reference(lp => lp.Library).LoadAsync(); return new MediaItemScanResult(item) { IsAdded = true }; } catch (Exception ex) { return BaseError.New(ex.ToString()); } } } }