Finishing the reflection based validation

This commit is contained in:
Zoe Roux 2020-05-19 02:49:47 +02:00
parent 8ce6e1cc1b
commit 500ba56943
9 changed files with 154 additions and 100 deletions

View File

@ -3,4 +3,9 @@ using System;
namespace Kyoo.Models.Attributes
{
public class NotMergableAttribute : Attribute { }
public interface IOnMerge
{
void OnMerge(object merged);
}
}

View File

@ -15,22 +15,22 @@ namespace Kyoo.Models
public IEnumerable<ProviderID> Providers
{
get => ProviderLinks.Select(x => x.Provider);
set => ProviderLinks = value.Select(x => new ProviderLink(x, this));
get => ProviderLinks?.Select(x => x.Provider);
set => ProviderLinks = value.Select(x => new ProviderLink(x, this)).ToList();
}
[NotMergable] [JsonIgnore] public virtual IEnumerable<ProviderLink> ProviderLinks { get; set; }
[NotMergable] [JsonIgnore] public virtual IEnumerable<LibraryLink> Links { get; set; }
[JsonIgnore] public IEnumerable<Show> Shows
{
get => Links.Where(x => x.Show != null).Select(x => x.Show);
get => Links?.Where(x => x.Show != null).Select(x => x.Show);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Show == null));
}
[JsonIgnore] public IEnumerable<Collection> Collections
{
get => Links.Where(x => x.Collection != null).Select(x => x.Collection);
get => Links?.Where(x => x.Collection != null).Select(x => x.Collection);
set => Links = Utility.MergeLists(
value?.Select(x => new LibraryLink(this, x)),
Links?.Where(x => x.Collection == null));

View File

@ -5,7 +5,7 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
public class Show
public class Show : IOnMerge
{
[JsonIgnore] public long ID { get; set; }
@ -102,6 +102,25 @@ namespace Kyoo.Models
{
return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID;
}
public void OnMerge(object merged)
{
if (ExternalIDs != null)
foreach (MetadataID id in ExternalIDs)
id.Show = this;
if (GenreLinks != null)
foreach (GenreLink genre in GenreLinks)
genre.Show = this;
if (People != null)
foreach (PeopleLink link in People)
link.Show = this;
if (Seasons != null)
foreach (Season season in Seasons)
season.Show = this;
if (Episodes != null)
foreach (Episode episode in Episodes)
episode.Show = this;
}
}
public enum Status { Finished, Airing, Planned }

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Kyoo.Models
{
@ -7,6 +8,8 @@ namespace Kyoo.Models
[JsonIgnore] public long ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Show> Shows { get; set; }
public Studio() { }

View File

@ -124,6 +124,8 @@ namespace Kyoo
}
}
if (first is IOnMerge)
((IOnMerge)first).OnMerge(second);
return first;
}

View File

@ -1,9 +1,9 @@
using System;
using System.Collections;
using Kyoo.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
@ -184,27 +184,6 @@ namespace Kyoo.Controllers
ValidateNewEntry(_database.Entry(obj));
}
private void ValidateNewEntry(EntityEntry entry)
{
if (entry.State != EntityState.Detached)
return;
entry.State = EntityState.Added;
foreach (NavigationEntry navigation in entry.Navigations)
{
ValidateNavigation(navigation);
if (navigation.CurrentValue == null)
continue;
if (navigation.Metadata.IsCollection())
{
IEnumerable entities = (IEnumerable)navigation.CurrentValue;
foreach (object childEntry in entities)
ValidateNewEntry(_database.Entry(childEntry));
}
else
ValidateNewEntry(_database.Entry(navigation.CurrentValue));
}
}
public void RegisterShowLinks(Library library, Collection collection, Show show)
{
if (collection != null)
@ -239,43 +218,6 @@ namespace Kyoo.Controllers
throw;
}
}
private void ValidateChanges()
{
_database.ChangeTracker.AutoDetectChangesEnabled = false;
try
{
foreach (EntityEntry sourceEntry in _database.ChangeTracker.Entries())
{
if (sourceEntry.State != EntityState.Added && sourceEntry.State != EntityState.Modified)
continue;
foreach (NavigationEntry navigation in sourceEntry.Navigations)
ValidateNavigation(navigation);
}
}
finally
{
_database.ChangeTracker.AutoDetectChangesEnabled = true;
_database.ChangeTracker.DetectChanges();
}
}
private void ValidateNavigation(NavigationEntry navigation)
{
// if (navigation.IsModified == false)
// return;
object oldValue = navigation.CurrentValue;
if (oldValue == null)
return;
object newValue = Validate(oldValue);
if (oldValue == newValue)
return;
navigation.CurrentValue = newValue;
if (!navigation.Metadata.IsCollection())
_database.Entry(oldValue).State = EntityState.Detached;
}
#endregion
#region Edit
@ -476,30 +418,82 @@ namespace Kyoo.Controllers
#endregion
#region ValidateValue
private T Validate<T>(T obj) where T : class
private void ValidateChanges()
{
_database.ChangeTracker.AutoDetectChangesEnabled = false;
try
{
foreach (EntityEntry sourceEntry in _database.ChangeTracker.Entries())
{
if (sourceEntry.State != EntityState.Added && sourceEntry.State != EntityState.Modified)
continue;
foreach (NavigationEntry navigation in sourceEntry.Navigations)
ValidateNavigation(navigation, EntityState.Added);
}
}
finally
{
_database.ChangeTracker.AutoDetectChangesEnabled = true;
_database.ChangeTracker.DetectChanges();
}
}
private void ValidateNewEntry(EntityEntry entry)
{
if (entry.State != EntityState.Detached)
return;
entry.State = EntityState.Added;
foreach (NavigationEntry navigation in entry.Navigations)
{
ValidateNavigation(navigation, EntityState.Detached);
if (navigation.CurrentValue == null)
continue;
if (navigation.Metadata.IsCollection())
{
IEnumerable entities = (IEnumerable)navigation.CurrentValue;
foreach (object childEntry in entities)
ValidateNewEntry(_database.Entry(childEntry));
}
else
ValidateNewEntry(_database.Entry(navigation.CurrentValue));
}
}
private void ValidateNavigation(NavigationEntry navigation, EntityState? skipOtherState)
{
object oldValue = navigation.CurrentValue;
if (oldValue == null)
return;
object newValue = Validate(oldValue, skipOtherState);
if (oldValue == newValue)
return;
navigation.CurrentValue = newValue;
if (!navigation.Metadata.IsCollection())
_database.Entry(oldValue).State = EntityState.Detached;
}
private T Validate<T>(T obj, EntityState? skipOtherState) where T : class
{
if (obj == null)
return null;
if (!(obj is IEnumerable) && _database.Entry(obj).State != EntityState.Added)
if (skipOtherState != null && !(obj is IEnumerable) && _database.Entry(obj).State != skipOtherState)
return obj;
switch(obj)
{
case ProviderLink link:
link.Provider = ValidateLink(link.Provider);
link.Library = ValidateLink(link.Library);
_database.Entry(link).State = EntityState.Added;
link.Provider = ValidateLink(link.Provider, skipOtherState);
link.Library = ValidateLink(link.Library, skipOtherState);
return obj;
case GenreLink link:
link.Show = ValidateLink(link.Show);
link.Genre = ValidateLink(link.Genre);
_database.Entry(link).State = EntityState.Added;
link.Show = ValidateLink(link.Show, skipOtherState);
link.Genre = ValidateLink(link.Genre, skipOtherState);
return obj;
case PeopleLink link:
link.Show = ValidateLink(link.Show);
link.People = ValidateLink(link.People);
_database.Entry(link).State = EntityState.Added;
link.Show = ValidateLink(link.Show, skipOtherState);
link.People = ValidateLink(link.People, skipOtherState);
return obj;
}
@ -509,27 +503,27 @@ namespace Kyoo.Controllers
Collection collection => GetCollection(collection.Slug) ?? collection,
Show show => GetShow(show.Slug) ?? show,
Season season => GetSeason(season.Show.Slug, season.SeasonNumber) ?? season,
Episode episode => GetEpisode(episode.Show.Slug, episode.SeasonNumber, episode.SeasonNumber) ?? episode,
Episode episode => GetEpisode(episode.Show.Slug, episode.SeasonNumber, episode.EpisodeNumber) ?? episode,
Studio studio => GetStudio(studio.Slug) ?? studio,
People people => GetPeople(people.Slug) ?? people,
Genre genre => GetGenre(genre.Slug) ?? genre,
ProviderID provider => GetProvider(provider.Name) ?? provider,
IEnumerable<dynamic> list => Utility.RunGenericMethod(this, "ValidateList",
Utility.GetEnumerableType(list), new [] {list}),
Utility.GetEnumerableType(list), new object[] {list, skipOtherState}),
_ => obj
});
}
public IEnumerable<T> ValidateList<T>(IEnumerable<T> list) where T : class
public IEnumerable<T> ValidateList<T>(IEnumerable<T> list, EntityState? skipOtherState) where T : class
{
return list.Select(x =>
{
T tmp = Validate(x);
T tmp = Validate(x, skipOtherState);
if (tmp != x)
_database.Entry(x).State = EntityState.Detached;
return tmp ?? x;
}).GroupBy(GetSlug).Select(x => x.First()).Where(x => x != null).ToList();
})/*.GroupBy(GetSlug).Select(x => x.First()).Where(x => x != null)*/.ToList();
}
private static object GetSlug(object obj)
@ -556,9 +550,9 @@ namespace Kyoo.Controllers
};
}
private T ValidateLink<T>(T oldValue) where T : class
private T ValidateLink<T>(T oldValue, EntityState? skipOtherState) where T : class
{
T newValue = Validate(oldValue);
T newValue = Validate(oldValue, skipOtherState);
if (!ReferenceEquals(oldValue, newValue))
_database.Entry(oldValue).State = EntityState.Detached;
return newValue;

View File

@ -15,7 +15,7 @@ namespace Kyoo.Controllers
_providers = pluginManager.GetPlugins<IMetadataProvider>();
}
private async Task<T> GetMetadata<T>(Func<IMetadataProvider, Task<T>> providerCall, Library library, string what)
private async Task<T> GetMetadata<T>(Func<IMetadataProvider, Task<T>> providerCall, Library library, string what)
where T : new()
{
T ret = new T();
@ -32,13 +32,17 @@ namespace Kyoo.Controllers
ret = Utility.Merge(ret, await providerCall(provider));
} catch (Exception ex)
{
Console.Error.WriteLine($"\tThe provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
await Console.Error.WriteLineAsync(
$"The provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
}
}
return ret;
}
private async Task<IEnumerable<T>> GetMetadata<T>(Func<IMetadataProvider, Task<IEnumerable<T>>> providerCall, Library library, string what)
private async Task<List<T>> GetMetadata<T>(
Func<IMetadataProvider, Task<IEnumerable<T>>> providerCall,
Library library,
string what)
{
List<T> ret = new List<T>();
@ -54,7 +58,8 @@ namespace Kyoo.Controllers
ret.AddRange(await providerCall(provider) ?? new List<T>());
} catch (Exception ex)
{
Console.Error.WriteLine($"\tThe provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
await Console.Error.WriteLineAsync(
$"The provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
}
}
return ret;
@ -62,7 +67,10 @@ namespace Kyoo.Controllers
public async Task<Collection> GetCollectionFromName(string name, Library library)
{
Collection collection = await GetMetadata(provider => provider.GetCollectionFromName(name), library, $"the collection {name}");
Collection collection = await GetMetadata(
provider => provider.GetCollectionFromName(name),
library,
$"the collection {name}");
collection.Name ??= name;
collection.Slug ??= Utility.ToSlug(name);
return collection;
@ -85,12 +93,17 @@ namespace Kyoo.Controllers
show.Slug = Utility.ToSlug(showName);
show.Title ??= showName;
show.IsMovie = isMovie;
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<IEnumerable<Show>> SearchShows(string showName, bool isMovie, Library library)
{
IEnumerable<Show> shows = await GetMetadata(provider => provider.SearchShows(showName, isMovie), library, $"the show {showName}");
IEnumerable<Show> shows = await GetMetadata(
provider => provider.SearchShows(showName, isMovie),
library,
$"the show {showName}");
return shows.Select(show =>
{
show.Slug = Utility.ToSlug(showName);
@ -102,16 +115,27 @@ namespace Kyoo.Controllers
public async Task<Season> GetSeason(Show show, long seasonNumber, Library library)
{
Season season = await GetMetadata(provider => provider.GetSeason(show, seasonNumber), library, $"the season {seasonNumber} of {show.Title}");
Season season = await GetMetadata(
provider => provider.GetSeason(show, seasonNumber),
library,
$"the season {seasonNumber} of {show.Title}");
season.Show = show;
season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber;
season.Title ??= $"Season {season.SeasonNumber}";
return season;
}
public async Task<Episode> GetEpisode(Show show, string episodePath, long seasonNumber, long episodeNumber, long absoluteNumber, Library library)
public async Task<Episode> GetEpisode(Show show,
string episodePath,
long seasonNumber,
long episodeNumber,
long absoluteNumber,
Library library)
{
Episode episode = await GetMetadata(provider => provider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber), library, "an episode");
Episode episode = await GetMetadata(
provider => provider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber),
library,
"an episode");
episode.Show = show;
episode.Path = episodePath;
episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber;
@ -122,8 +146,17 @@ namespace Kyoo.Controllers
public async Task<IEnumerable<PeopleLink>> GetPeople(Show show, Library library)
{
IEnumerable<PeopleLink> people = await GetMetadata(provider => provider.GetPeople(show), library, $"a cast member of {show.Title}");
return people;
List<PeopleLink> people = await GetMetadata(
provider => provider.GetPeople(show),
library,
$"a cast member of {show.Title}");
return people?.GroupBy(x => x.Slug)
.Select(x => x.First())
.Select(x =>
{
x.Show = show;
return x;
}).ToList();
}
}
}

View File

@ -52,9 +52,9 @@ namespace Kyoo
services.AddDbContext<DatabaseContext>(options =>
{
options.UseLazyLoadingProxies()
.UseSqlite(_configuration.GetConnectionString("Database"));
//.EnableSensitiveDataLogging();
//.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
.UseSqlite(_configuration.GetConnectionString("Database"))
.EnableSensitiveDataLogging()
.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
});
services.AddDbContext<IdentityDatabase>(options =>

View File

@ -174,9 +174,7 @@ namespace Kyoo.Controllers
return show;
show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
show.Path = showPath;
show.People = (await _metadataProvider.GetPeople(show, library))
.GroupBy(x => x.Slug)
.Select(x => x.First());
show.People = await _metadataProvider.GetPeople(show, library);
await _thumbnailsManager.Validate(show.People);
await _thumbnailsManager.Validate(show);
return show;