mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
661 lines
29 KiB
661 lines
29 KiB
using Dapper; |
|
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 MetadataRepository : IMetadataRepository |
|
{ |
|
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
|
private readonly ILogger<MetadataRepository> _logger; |
|
|
|
public MetadataRepository(IDbContextFactory<TvContext> dbContextFactory, ILogger<MetadataRepository> logger) |
|
{ |
|
_dbContextFactory = dbContextFactory; |
|
_logger = logger; |
|
} |
|
|
|
public async Task<bool> RemoveActor(Actor actor) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Actor WHERE Id = @ActorId", |
|
new { ActorId = actor.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> Update(Core.Domain.Metadata metadata) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
dbContext.Entry(metadata).State = EntityState.Modified; |
|
return await dbContext.SaveChangesAsync() > 0; |
|
} |
|
|
|
public async Task<bool> Add(Core.Domain.Metadata metadata) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
dbContext.Entry(metadata).State = EntityState.Added; |
|
|
|
foreach (Genre genre in Optional(metadata.Genres).Flatten()) |
|
{ |
|
dbContext.Entry(genre).State = EntityState.Added; |
|
} |
|
|
|
foreach (Tag tag in Optional(metadata.Tags).Flatten()) |
|
{ |
|
dbContext.Entry(tag).State = EntityState.Added; |
|
} |
|
|
|
foreach (Studio studio in Optional(metadata.Studios).Flatten()) |
|
{ |
|
dbContext.Entry(studio).State = EntityState.Added; |
|
} |
|
|
|
if (metadata is ArtistMetadata artistMetadata) |
|
{ |
|
foreach (Style style in Optional(artistMetadata.Styles).Flatten()) |
|
{ |
|
dbContext.Entry(style).State = EntityState.Added; |
|
} |
|
|
|
foreach (Mood mood in Optional(artistMetadata.Moods).Flatten()) |
|
{ |
|
dbContext.Entry(mood).State = EntityState.Added; |
|
} |
|
} |
|
|
|
foreach (Actor actor in Optional(metadata.Actors).Flatten()) |
|
{ |
|
dbContext.Entry(actor).State = EntityState.Added; |
|
if (actor.Artwork != null) |
|
{ |
|
dbContext.Entry(actor.Artwork).State = EntityState.Added; |
|
} |
|
} |
|
|
|
foreach (MetadataGuid guid in Optional(metadata.Guids).Flatten()) |
|
{ |
|
dbContext.Entry(guid).State = EntityState.Added; |
|
} |
|
|
|
if (metadata is MovieMetadata movieMetadata) |
|
{ |
|
foreach (Director director in Optional(movieMetadata.Directors).Flatten()) |
|
{ |
|
dbContext.Entry(director).State = EntityState.Added; |
|
} |
|
|
|
foreach (Writer writer in Optional(movieMetadata.Writers).Flatten()) |
|
{ |
|
dbContext.Entry(writer).State = EntityState.Added; |
|
} |
|
} |
|
|
|
if (metadata is EpisodeMetadata episodeMetadata) |
|
{ |
|
foreach (Director director in Optional(episodeMetadata.Directors).Flatten()) |
|
{ |
|
dbContext.Entry(director).State = EntityState.Added; |
|
} |
|
|
|
foreach (Writer writer in Optional(episodeMetadata.Writers).Flatten()) |
|
{ |
|
dbContext.Entry(writer).State = EntityState.Added; |
|
} |
|
} |
|
|
|
if (metadata is MusicVideoMetadata musicVideoMetadata) |
|
{ |
|
foreach (MusicVideoArtist artist in Optional(musicVideoMetadata.Artists).Flatten()) |
|
{ |
|
dbContext.Entry(artist).State = EntityState.Added; |
|
} |
|
} |
|
|
|
return await dbContext.SaveChangesAsync() > 0; |
|
} |
|
|
|
public async Task<bool> UpdateStatistics( |
|
MediaItem mediaItem, |
|
MediaVersion incoming, |
|
bool updateVersion = true) |
|
{ |
|
int mediaVersionId = mediaItem.GetHeadVersion().Id; |
|
|
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
Option<MediaVersion> maybeVersion = await dbContext.MediaVersions |
|
.Include(v => v.Streams) |
|
.Include(v => v.Chapters) |
|
.Include(v => v.MediaFiles) |
|
.OrderBy(v => v.Id) |
|
.SingleOrDefaultAsync(v => v.Id == mediaVersionId) |
|
.Map(Optional); |
|
|
|
return await maybeVersion.Match( |
|
async existing => |
|
{ |
|
if (updateVersion) |
|
{ |
|
existing.DateUpdated = incoming.DateUpdated; |
|
existing.Duration = incoming.Duration; |
|
existing.SampleAspectRatio = incoming.SampleAspectRatio; |
|
existing.DisplayAspectRatio = incoming.DisplayAspectRatio; |
|
existing.Width = incoming.Width; |
|
existing.Height = incoming.Height; |
|
existing.VideoScanKind = incoming.VideoScanKind; |
|
existing.RFrameRate = incoming.RFrameRate; |
|
} |
|
|
|
var toAdd = incoming.Streams.Filter(s => existing.Streams.All(es => es.Index != s.Index)).ToList(); |
|
var toRemove = existing.Streams.Filter(es => incoming.Streams.All(s => s.Index != es.Index)) |
|
.ToList(); |
|
var toUpdate = incoming.Streams.Except(toAdd).ToList(); |
|
|
|
// add |
|
existing.Streams.AddRange(toAdd); |
|
|
|
// remove |
|
existing.Streams.RemoveAll(s => toRemove.Contains(s)); |
|
|
|
// update |
|
foreach (MediaStream incomingStream in toUpdate) |
|
{ |
|
MediaStream existingStream = existing.Streams.First(s => s.Index == incomingStream.Index); |
|
|
|
existingStream.Codec = incomingStream.Codec; |
|
existingStream.Profile = incomingStream.Profile; |
|
existingStream.MediaStreamKind = incomingStream.MediaStreamKind; |
|
existingStream.Language = incomingStream.Language; |
|
existingStream.Channels = incomingStream.Channels; |
|
existingStream.Title = incomingStream.Title; |
|
existingStream.Default = incomingStream.Default; |
|
existingStream.Forced = incomingStream.Forced; |
|
existingStream.AttachedPic = incomingStream.AttachedPic; |
|
existingStream.PixelFormat = incomingStream.PixelFormat; |
|
existingStream.ColorRange = incomingStream.ColorRange; |
|
existingStream.ColorSpace = incomingStream.ColorSpace; |
|
existingStream.ColorTransfer = incomingStream.ColorTransfer; |
|
existingStream.ColorPrimaries = incomingStream.ColorPrimaries; |
|
existingStream.BitsPerRawSample = incomingStream.BitsPerRawSample; |
|
existingStream.FileName = incomingStream.FileName; |
|
existingStream.MimeType = incomingStream.MimeType; |
|
} |
|
|
|
var chaptersToAdd = incoming.Chapters |
|
.Filter(s => existing.Chapters.All(es => es.ChapterId != s.ChapterId)) |
|
.ToList(); |
|
var chaptersToRemove = existing.Chapters |
|
.Filter(es => incoming.Chapters.All(s => s.ChapterId != es.ChapterId)) |
|
.ToList(); |
|
var chaptersToUpdate = incoming.Chapters.Except(chaptersToAdd).ToList(); |
|
|
|
// add |
|
existing.Chapters.AddRange(chaptersToAdd); |
|
|
|
// remove |
|
existing.Chapters.RemoveAll(chaptersToRemove.Contains); |
|
|
|
// update |
|
foreach (MediaChapter incomingChapter in chaptersToUpdate) |
|
{ |
|
MediaChapter existingChapter = existing.Chapters |
|
.First(s => s.ChapterId == incomingChapter.ChapterId); |
|
|
|
existingChapter.StartTime = incomingChapter.StartTime; |
|
existingChapter.EndTime = incomingChapter.EndTime; |
|
existingChapter.Title = incomingChapter.Title; |
|
} |
|
|
|
if (await dbContext.SaveChangesAsync() <= 0) |
|
{ |
|
return false; |
|
} |
|
|
|
// reload the media versions so we can properly index the duration |
|
switch (mediaItem) |
|
{ |
|
case Movie movie: |
|
movie.MediaVersions.Clear(); |
|
movie.MediaVersions.Add(existing); |
|
break; |
|
case Episode episode: |
|
episode.MediaVersions.Clear(); |
|
episode.MediaVersions.Add(existing); |
|
break; |
|
case MusicVideo musicVideo: |
|
musicVideo.MediaVersions.Clear(); |
|
musicVideo.MediaVersions.Add(existing); |
|
break; |
|
case OtherVideo otherVideo: |
|
otherVideo.MediaVersions.Clear(); |
|
otherVideo.MediaVersions.Add(existing); |
|
break; |
|
case Song song: |
|
song.MediaVersions.Clear(); |
|
song.MediaVersions.Add(existing); |
|
break; |
|
} |
|
|
|
return true; |
|
}, |
|
() => Task.FromResult(false)); |
|
} |
|
|
|
public async Task<Unit> UpdateArtworkPath(Artwork artwork) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"UPDATE Artwork SET Path = @Path, SourcePath = @SourcePath, DateUpdated = @DateUpdated, BlurHash43 = @BlurHash43, BlurHash54 = @BlurHash54, BlurHash64 = @BlurHash64 WHERE Id = @Id", |
|
new |
|
{ |
|
artwork.Path, artwork.SourcePath, artwork.DateUpdated, artwork.BlurHash43, artwork.BlurHash54, |
|
artwork.BlurHash64, artwork.Id |
|
}).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> AddArtwork(Core.Domain.Metadata metadata, Artwork artwork) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
|
|
var parameters = new |
|
{ |
|
artwork.ArtworkKind, metadata.Id, artwork.DateAdded, artwork.DateUpdated, artwork.Path, |
|
artwork.SourcePath, artwork.BlurHash43, artwork.BlurHash54, artwork.BlurHash64 |
|
}; |
|
|
|
return metadata switch |
|
{ |
|
MovieMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, MovieMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
ShowMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, ShowMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
SeasonMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, SeasonMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
EpisodeMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, EpisodeMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
ArtistMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, ArtistMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
Values (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
MusicVideoMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, MusicVideoMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
SongMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, SongMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
OtherVideoMetadata => await dbContext.Connection.ExecuteAsync( |
|
@"INSERT INTO Artwork (ArtworkKind, OtherVideoMetadataId, DateAdded, DateUpdated, Path, SourcePath, BlurHash43, BlurHash54, BlurHash64) |
|
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @SourcePath, @BlurHash43, @BlurHash54, @BlurHash64)", |
|
parameters) |
|
.ToUnit(), |
|
_ => Unit.Default |
|
}; |
|
} |
|
|
|
public async Task<Unit> RemoveArtwork(Core.Domain.Metadata metadata, ArtworkKind artworkKind) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"DELETE FROM Artwork WHERE ArtworkKind = @ArtworkKind AND (MovieMetadataId = @Id |
|
OR ShowMetadataId = @Id OR SeasonMetadataId = @Id OR EpisodeMetadataId = @Id OR OtherVideoMetadataId = @Id)", |
|
new { ArtworkKind = artworkKind, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<bool> CloneArtwork( |
|
Core.Domain.Metadata metadata, |
|
Option<Artwork> maybeArtwork, |
|
ArtworkKind artworkKind, |
|
string sourcePath, |
|
DateTime lastWriteTime) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
Option<Artwork> maybeExisting = await dbContext.Artwork |
|
.AsNoTracking() |
|
.Filter( |
|
a => a.SourcePath == sourcePath && a.ArtworkKind == artworkKind && a.DateUpdated == lastWriteTime) |
|
.FirstOrDefaultAsync() |
|
.Map(Optional); |
|
foreach (Artwork existing in maybeExisting) |
|
{ |
|
Artwork artwork = await maybeArtwork.IfNoneAsync(new Artwork()); |
|
if (maybeArtwork.IsNone) |
|
{ |
|
metadata.Artwork.Add(artwork); |
|
} |
|
|
|
artwork.Path = existing.Path; |
|
artwork.SourcePath = existing.SourcePath; |
|
artwork.ArtworkKind = artworkKind; |
|
artwork.BlurHash43 = existing.BlurHash43; |
|
artwork.BlurHash54 = existing.BlurHash54; |
|
artwork.BlurHash64 = existing.BlurHash64; |
|
artwork.DateAdded = existing.DateAdded; |
|
artwork.DateUpdated = existing.DateUpdated; |
|
|
|
if (maybeArtwork.IsNone) |
|
{ |
|
await AddArtwork(metadata, artwork); |
|
} |
|
else |
|
{ |
|
await UpdateArtworkPath(artwork); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public async Task<Unit> MarkAsUpdated(ShowMetadata metadata, DateTime dateUpdated) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE ShowMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id", |
|
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsUpdated(SeasonMetadata metadata, DateTime dateUpdated) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE SeasonMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id", |
|
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsUpdated(MovieMetadata metadata, DateTime dateUpdated) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE MovieMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id", |
|
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsUpdated(EpisodeMetadata metadata, DateTime dateUpdated) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE EpisodeMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id", |
|
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsExternal(ShowMetadata metadata) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE ShowMetadata SET MetadataKind = @Kind WHERE Id = @Id", |
|
new { metadata.Id, Kind = (int)MetadataKind.External }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> SetContentRating(ShowMetadata metadata, string contentRating) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE ShowMetadata SET ContentRating = @ContentRating WHERE Id = @Id", |
|
new { metadata.Id, ContentRating = contentRating }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsExternal(MovieMetadata metadata) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE MovieMetadata SET MetadataKind = @Kind WHERE Id = @Id", |
|
new { metadata.Id, Kind = (int)MetadataKind.External }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> SetContentRating(MovieMetadata metadata, string contentRating) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE MovieMetadata SET ContentRating = @ContentRating WHERE Id = @Id", |
|
new { metadata.Id, ContentRating = contentRating }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsUpdated(OtherVideoMetadata metadata, DateTime dateUpdated) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE OtherVideoMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id", |
|
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> MarkAsExternal(OtherVideoMetadata metadata) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE OtherVideoMetadata SET MetadataKind = @Kind WHERE Id = @Id", |
|
new { metadata.Id, Kind = (int)MetadataKind.External }).ToUnit(); |
|
} |
|
|
|
public async Task<Unit> SetContentRating(OtherVideoMetadata metadata, string contentRating) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
@"UPDATE OtherVideoMetadata SET ContentRating = @ContentRating WHERE Id = @Id", |
|
new { metadata.Id, ContentRating = contentRating }).ToUnit(); |
|
} |
|
|
|
public async Task<bool> RemoveGuid(MetadataGuid guid) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM MetadataGuid WHERE Id = @GuidId", |
|
new { GuidId = guid.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> AddGuid(Core.Domain.Metadata metadata, MetadataGuid guid) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return metadata switch |
|
{ |
|
MovieMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, MovieMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
ShowMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, ShowMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
SeasonMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, SeasonMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
EpisodeMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, EpisodeMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
ArtistMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, ArtistMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
OtherVideoMetadata => |
|
await dbContext.Connection.ExecuteAsync( |
|
"INSERT INTO MetadataGuid (Guid, OtherVideoMetadataId) VALUES (@Guid, @MetadataId)", |
|
new { guid.Guid, MetadataId = metadata.Id }).Map(result => result > 0), |
|
_ => throw new NotSupportedException() |
|
}; |
|
} |
|
|
|
public async Task<bool> RemoveDirector(Director director) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Director WHERE Id = @DirectorId", |
|
new { DirectorId = director.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> RemoveWriter(Writer writer) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Writer WHERE Id = @WriterId", |
|
new { WriterId = writer.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> UpdateSubtitles(Core.Domain.Metadata metadata, List<Subtitle> subtitles) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await UpdateSubtitles(dbContext, metadata, subtitles); |
|
} |
|
|
|
public async Task<bool> RemoveGenre(Genre genre) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Genre WHERE Id = @GenreId", |
|
new { GenreId = genre.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> RemoveTag(Tag tag) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Tag WHERE Id = @TagId AND ExternalCollectionId = @ExternalCollectionId", |
|
new { TagId = tag.Id, tag.ExternalCollectionId }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> RemoveStudio(Studio studio) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Studio WHERE Id = @StudioId", |
|
new { StudioId = studio.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> RemoveStyle(Style style) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync( |
|
"DELETE FROM Style WHERE Id = @StyleId", |
|
new { StyleId = style.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
public async Task<bool> RemoveMood(Mood mood) |
|
{ |
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); |
|
return await dbContext.Connection.ExecuteAsync("DELETE FROM Mood WHERE Id = @MoodId", new { MoodId = mood.Id }) |
|
.Map(result => result > 0); |
|
} |
|
|
|
private static async Task<bool> UpdateSubtitles( |
|
TvContext dbContext, |
|
Core.Domain.Metadata metadata, |
|
List<Subtitle> subtitles) |
|
{ |
|
// _logger.LogDebug( |
|
// "Updating {Count} subtitles; metadata is {Metadata}", |
|
// subtitles.Count, |
|
// metadata.GetType().Name); |
|
|
|
int metadataId = metadata.Id; |
|
|
|
Option<Core.Domain.Metadata> maybeMetadata = metadata switch |
|
{ |
|
EpisodeMetadata => await dbContext.EpisodeMetadata |
|
.Include(em => em.Subtitles) |
|
.SelectOneAsync(em => em.Id, em => em.Id == metadataId) |
|
.MapT(em => (Core.Domain.Metadata)em), |
|
MovieMetadata => await dbContext.MovieMetadata |
|
.Include(mm => mm.Subtitles) |
|
.SelectOneAsync(mm => mm.Id, mm => mm.Id == metadataId) |
|
.MapT(mm => (Core.Domain.Metadata)mm), |
|
MusicVideoMetadata => await dbContext.MusicVideoMetadata |
|
.Include(mvm => mvm.Subtitles) |
|
.SelectOneAsync(mvm => mvm.Id, mm => mm.Id == metadataId) |
|
.MapT(mvm => (Core.Domain.Metadata)mvm), |
|
OtherVideoMetadata => await dbContext.OtherVideoMetadata |
|
.Include(ovm => ovm.Subtitles) |
|
.SelectOneAsync(ovm => ovm.Id, mm => mm.Id == metadataId) |
|
.MapT(ovm => (Core.Domain.Metadata)ovm), |
|
_ => None |
|
}; |
|
|
|
// _logger.LogDebug( |
|
// "Existing metadata is {Metadata}", |
|
// await maybeMetadata.Map(m => m.GetType().Name).IfNoneAsync("[none]")); |
|
|
|
foreach (Core.Domain.Metadata existing in maybeMetadata) |
|
{ |
|
var toAdd = subtitles.Filter(s => existing.Subtitles.All(es => es.StreamIndex != s.StreamIndex)).ToList(); |
|
var toRemove = existing.Subtitles.Filter(es => subtitles.All(s => s.StreamIndex != es.StreamIndex)) |
|
.ToList(); |
|
var toUpdate = subtitles.Except(toAdd).ToList(); |
|
|
|
// _logger.LogDebug( |
|
// "Subtitles to add: {ToAdd}, to remove: {ToRemove}, to update: {ToUpdate}", |
|
// toAdd.Count, |
|
// toRemove.Count, |
|
// toUpdate.Count); |
|
|
|
if (toAdd.Count != 0 || toRemove.Count != 0 || toUpdate.Count != 0) |
|
{ |
|
// add |
|
existing.Subtitles.AddRange(toAdd); |
|
|
|
// remove |
|
existing.Subtitles.RemoveAll(s => toRemove.Contains(s)); |
|
|
|
// update |
|
foreach (Subtitle incomingSubtitle in toUpdate) |
|
{ |
|
Subtitle existingSubtitle = |
|
existing.Subtitles.First( |
|
s => s.StreamIndex == incomingSubtitle.StreamIndex); |
|
|
|
existingSubtitle.Codec = incomingSubtitle.Codec; |
|
existingSubtitle.Default = incomingSubtitle.Default; |
|
existingSubtitle.Forced = incomingSubtitle.Forced; |
|
existingSubtitle.SDH = incomingSubtitle.SDH; |
|
existingSubtitle.Language = incomingSubtitle.Language; |
|
existingSubtitle.SubtitleKind = incomingSubtitle.SubtitleKind; |
|
existingSubtitle.DateUpdated = incomingSubtitle.DateUpdated; |
|
existingSubtitle.Path = incomingSubtitle.Path; |
|
|
|
dbContext.Entry(existingSubtitle).State = EntityState.Modified; |
|
} |
|
|
|
int count = await dbContext.SaveChangesAsync(); |
|
|
|
// _logger.LogDebug("Subtitles update changed {Count} records in the db", count); |
|
|
|
return count > 0; |
|
} |
|
|
|
// nothing to do |
|
// _logger.LogDebug("Subtitle update requires no database changes"); |
|
return true; |
|
} |
|
|
|
// no metadata |
|
// _logger.LogDebug("Subtitle update failure due to missing metadata"); |
|
return false; |
|
} |
|
}
|
|
|