diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 8268cb66..cec60504 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -44,7 +44,6 @@ namespace Kyoo.Controllers //Register values void Register(object obj); Task Edit(object obj, bool resetOld); - void RegisterShowLinks(Library library, Collection collection, Show show); Task SaveChanges(); // Validate values diff --git a/Kyoo.Common/Models/Show.cs b/Kyoo.Common/Models/Show.cs index 4801fb35..d540dd24 100644 --- a/Kyoo.Common/Models/Show.cs +++ b/Kyoo.Common/Models/Show.cs @@ -7,7 +7,7 @@ namespace Kyoo.Models { public class Show : IOnMerge { - [JsonIgnore] public long ID { get; set; } + [NotMergableAttribute] [JsonIgnore] public long ID { get; set; } public string Slug { get; set; } public string Title { get; set; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 1183e5ea..364235af 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -99,7 +99,10 @@ namespace Kyoo if (second == null) return first; - Type type = typeof(T); + Type type = first.GetType(); + if (second.GetType() != type && second.GetType().IsAssignableFrom(type)) + type = second.GetType(); + foreach (PropertyInfo property in type.GetProperties().Where(x => x.CanRead && x.CanWrite)) { if (Attribute.GetCustomAttribute(property, typeof(NotMergableAttribute)) != null) @@ -131,10 +134,10 @@ namespace Kyoo public static T Nullify(T obj) { - Type type = typeof(T); + Type type = obj.GetType(); foreach (PropertyInfo property in type.GetProperties()) { - if (!property.CanWrite) + if (!property.CanWrite || Attribute.GetCustomAttribute(property, typeof(NotMergableAttribute)) != null) continue; object defaultValue = property.PropertyType.IsValueType diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs index 8d7aaaf2..ebfdf467 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo/Controllers/LibraryManager.cs @@ -191,20 +191,6 @@ namespace Kyoo.Controllers }); } - public void RegisterShowLinks(Library library, Collection collection, Show show) - { - if (collection != null) - { - _database.LibraryLinks.AddIfNotExist(new LibraryLink {Library = library, Collection = collection}, - x => x.Library == library && x.Collection == collection && x.ShowID == null); - _database.CollectionLinks.AddIfNotExist(new CollectionLink { Collection = collection, Show = show}, - x => x.Collection == collection && x.Show == show); - } - else - _database.LibraryLinks.AddIfNotExist(new LibraryLink {Library = library, Show = show}, - x => x.Library == library && x.Collection == null && x.Show == show); - } - public Task SaveChanges() { return SaveChanges(0); @@ -240,11 +226,12 @@ namespace Kyoo.Controllers if (resetOld) Utility.Nullify(existing); + _database.ChangeTracker.DetectChanges(); Utility.Merge(existing, obj); - ValidateRootEntry(_database.Entry(existing), entry => entry.State != EntityState.Added); - _database.ChangeTracker.DetectChanges(); + ValidateRootEntry(_database.Entry(existing), entry => entry.State != EntityState.Unchanged + && entry.State != EntityState.Deleted); await _database.SaveChangesAsync(); } finally @@ -277,12 +264,14 @@ namespace Kyoo.Controllers } } - private void ValidateRootEntry(EntityEntry entry, Func shouldRun) + private void ValidateRootEntry(EntityEntry entry, Func shouldRun, object parentObject = null) { if (!shouldRun.Invoke(entry)) return; foreach (NavigationEntry navigation in entry.Navigations) { + if (!navigation.Metadata.IsCollection() && ReferenceEquals(navigation.CurrentValue, parentObject)) + continue; ValidateNavigation(navigation); if (navigation.CurrentValue == null) continue; @@ -290,10 +279,14 @@ namespace Kyoo.Controllers { IEnumerable entities = (IEnumerable)navigation.CurrentValue; foreach (object childEntry in entities) - ValidateRootEntry(_database.Entry(childEntry), shouldRun); + { + if (ReferenceEquals(childEntry, parentObject)) + continue; + ValidateRootEntry(_database.Entry(childEntry), shouldRun, entry.Entity); + } } else - ValidateRootEntry(_database.Entry(navigation.CurrentValue), shouldRun); + ValidateRootEntry(_database.Entry(navigation.CurrentValue), shouldRun, entry.Entity); } } @@ -335,7 +328,7 @@ namespace Kyoo.Controllers return list.Select(x => { T tmp = Validate(x); - if (tmp != x) + if (!ReferenceEquals(x, tmp)) _database.Entry(x).State = EntityState.Detached; return tmp ?? x; })/*.GroupBy(GetSlug).Select(x => x.First()).Where(x => x != null)*/.ToList(); @@ -345,13 +338,41 @@ namespace Kyoo.Controllers { return obj switch { - Library library => GetLibrary(library.Slug), - Collection collection => GetCollection(collection.Slug), - Show show => GetShow(show.Slug), - Season season => GetSeason(season.Show.Slug, season.SeasonNumber), - Episode episode => GetEpisode(episode.Show.Slug, episode.SeasonNumber, episode.EpisodeNumber), - Studio studio => GetStudio(studio.Slug), - People people => GetPeople(people.Slug), + Library library => _database.Libraries + .Include(x => x.Links) + .Include(x => x.Providers) + .FirstOrDefault(x => x.Slug == library.Slug), + Collection collection => _database.Collections + .Include(x => x.Links) + .FirstOrDefault(x => x.Slug == collection.Slug), + Show show => _database.Shows + .Include(x => x.Seasons) + .Include(x => x.Episodes) + .Include(x => x.People) + .Include(x => x.GenreLinks) + .Include(x => x.Studio) + .Include(x => x.ExternalIDs) + .FirstOrDefault(x => x.Slug == show.Slug), + Season season => _database.Seasons + .Include(x => x.Episodes) + .Include(x => x.ExternalIDs) + .Include(x => x.Show) + .FirstOrDefault(x => x.Show.Slug == season.Show.Slug && x.SeasonNumber == season.SeasonNumber), + Episode episode => _database.Episodes + .Include(x => x.Season) + .Include(x => x.Show) + .Include(x => x.ExternalIDs) + .Include(x => x.Tracks) + .FirstOrDefault(x => x.EpisodeNumber == episode.EpisodeNumber + && x.SeasonNumber == episode.SeasonNumber + && x.Show.Slug == episode.Show.Slug), + Studio studio => _database.Studios + .Include(x => x.Shows) + .FirstOrDefault(x => x.Slug == studio.Slug), + People people => _database.Peoples + .Include(x => x.Roles) + .Include(x => x.ExternalIDs) + .FirstOrDefault(x => x.Slug == people.Slug), Genre genre => GetGenre(genre.Slug), ProviderID provider => GetProvider(provider.Name), _ => null diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index 5dc6791c..fa1a0573 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -76,9 +76,12 @@ namespace Kyoo.Controllers return collection; } - public async Task CompleteShow(Show show, Library library) + public async Task CompleteShow(Show old, Library library) { - return await GetMetadata(provider => provider.GetShowByID(show), library, $"the show {show.Title}"); + Show show = await GetMetadata(provider => provider.GetShowByID(old), library, $"the show {old.Title}"); + show.GenreLinks = show.GenreLinks?.GroupBy(x => x.Genre.Slug).Select(x => x.First()).ToList(); + show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList(); + return show; } public async Task SearchShow(string showName, bool isMovie, Library library) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 22baaf05..afeb6590 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -51,9 +51,9 @@ namespace Kyoo services.AddDbContext(options => { options.UseLazyLoadingProxies() - .UseNpgsql(_configuration.GetConnectionString("Database")); - // .EnableSensitiveDataLogging() - // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + .UseNpgsql(_configuration.GetConnectionString("Database")) + .EnableSensitiveDataLogging(); + //.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }); services.AddDbContext(options => diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 065bc613..3225dfe2 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -142,8 +142,12 @@ namespace Kyoo.Controllers long absoluteNumber = long.TryParse(match.Groups["Absolute"].Value, out tmp) ? tmp : -1; Collection collection = await GetCollection(libraryManager, collectionName, library); + + if (collection != null) + libraryManager.Register(collection); + bool isMovie = seasonNumber == -1 && episodeNumber == -1 && absoluteNumber == -1; - Show show = await GetShow(libraryManager, showName, showPath, isMovie, library); + Show show = await GetShow(libraryManager, collection, showName, showPath, isMovie, library); if (isMovie) libraryManager.Register(await GetMovie(show, path)); else @@ -152,9 +156,6 @@ namespace Kyoo.Controllers Episode episode = await GetEpisode(libraryManager, show, season, episodeNumber, absoluteNumber, path, library); libraryManager.Register(episode); } - if (collection != null) - libraryManager.Register(collection); - libraryManager.RegisterShowLinks(library, collection, show); Console.WriteLine($"Registering episode at: {path}"); await libraryManager.SaveChanges(); } @@ -162,14 +163,19 @@ namespace Kyoo.Controllers private async Task GetCollection(ILibraryManager libraryManager, string collectionName, Library library) { if (string.IsNullOrEmpty(collectionName)) - return await Task.FromResult(null); + return default; Collection name = libraryManager.GetCollection(Utility.ToSlug(collectionName)); if (name != null) return name; return await _metadataProvider.GetCollectionFromName(collectionName, library); } - private async Task GetShow(ILibraryManager libraryManager, string showTitle, string showPath, bool isMovie, Library library) + private async Task GetShow(ILibraryManager libraryManager, + Collection collection, + string showTitle, + string showPath, + bool isMovie, + Library library) { Show show = libraryManager.GetShowByPath(showPath); if (show != null) @@ -179,6 +185,15 @@ namespace Kyoo.Controllers show.People = await _metadataProvider.GetPeople(show, library); await _thumbnailsManager.Validate(show.People); await _thumbnailsManager.Validate(show); + + if (collection != null) + { + libraryManager.Register(new LibraryLink {Library = library, Collection = collection}); + libraryManager.Register(new CollectionLink { Collection = collection, Show = show}); + } + else + libraryManager.Register(new LibraryLink {Library = library, Show = show}); + return show; } @@ -196,9 +211,20 @@ namespace Kyoo.Controllers return season; } - private async Task GetEpisode(ILibraryManager libraryManager, Show show, Season season, long episodeNumber, long absoluteNumber, string episodePath, Library library) + private async Task GetEpisode(ILibraryManager libraryManager, + Show show, + Season season, + long episodeNumber, + long absoluteNumber, + string episodePath, + Library library) { - Episode episode = await _metadataProvider.GetEpisode(show, episodePath, season?.SeasonNumber ?? -1, episodeNumber, absoluteNumber, library); + Episode episode = await _metadataProvider.GetEpisode(show, + episodePath, + season?.SeasonNumber ?? -1, + episodeNumber, + absoluteNumber, + library); if (season == null) season = await GetSeason(libraryManager, show, episode.SeasonNumber, library); episode.Season = season; @@ -223,7 +249,8 @@ namespace Kyoo.Controllers private async Task> GetTracks(Episode episode) { IEnumerable tracks = await _transcoder.GetTrackInfo(episode.Path); - List epTracks = tracks.Where(x => x.Type != StreamType.Subtitle).Concat(GetExtractedSubtitles(episode)).ToList(); + List epTracks = tracks.Where(x => x.Type != StreamType.Subtitle) + .Concat(GetExtractedSubtitles(episode)).ToList(); if (epTracks.Count(x => !x.IsExternal) < tracks.Count()) epTracks.AddRange(await _transcoder.ExtractSubtitles(episode.Path)); episode.Tracks = epTracks; @@ -262,7 +289,33 @@ namespace Kyoo.Controllers return tracks; } - private static readonly string[] VideoExtensions = { ".webm", ".mkv", ".flv", ".vob", ".ogg", ".ogv", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".asf", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".3gp", ".3g2" }; + private static readonly string[] VideoExtensions = + { + ".webm", + ".mkv", + ".flv", + ".vob", + ".ogg", + ".ogv", + ".avi", + ".mts", + ".m2ts", + ".ts", + ".mov", + ".qt", + ".asf", + ".mp4", + ".m4p", + ".m4v", + ".mpg", + ".mp2", + ".mpeg", + ".mpe", + ".mpv", + ".m2v", + ".3gp", + ".3g2" + }; private static bool IsVideo(string filePath) { diff --git a/Kyoo/Tasks/ReScan.cs b/Kyoo/Tasks/ReScan.cs index 22c7aeb5..3565dafb 100644 --- a/Kyoo/Tasks/ReScan.cs +++ b/Kyoo/Tasks/ReScan.cs @@ -62,6 +62,8 @@ namespace Kyoo.Tasks edited.ID = old.ID; edited.Slug = old.Slug; edited.Path = old.Path; + edited.Seasons = old.Seasons; + edited.Episodes = old.Episodes; await libraryManager.Edit(edited, true); await _thumbnailsManager.Validate(edited, true); }