Restarting the library manager to use repositories

This commit is contained in:
Zoe Roux 2020-06-02 04:01:23 +02:00
parent b9e040673c
commit bf66f0ca9c
4 changed files with 151 additions and 368 deletions

View File

@ -1,9 +1,10 @@
using Kyoo.Models;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Models;
namespace Kyoo.Controllers
{
// ReSharper disable once PossibleInterfaceMemberAmbiguity
public interface ILibraryManager
{
// Get by slug
@ -16,21 +17,22 @@ namespace Kyoo.Controllers
Genre GetGenre(string slug);
Studio GetStudio(string slug);
People GetPeople(string slug);
ProviderID GetProvider(string name);
// Get all
IEnumerable<Library> GetLibraries();
IEnumerable<Collection> GetCollections();
IEnumerable<Show> GetShows();
IEnumerable<Season> GetSeasons();
IEnumerable<Episode> GetEpisodes();
IEnumerable<Track> GetTracks();
IEnumerable<Studio> GetStudios();
IEnumerable<People> GetPeoples();
IEnumerable<Genre> GetGenres();
// Search
IEnumerable<Library> SearchLibraries(string searchQuery);
IEnumerable<Collection> SearchCollections(string searchQuery);
IEnumerable<Show> SearchShows(string searchQuery);
IEnumerable<Season> SearchSeasons(string searchQuery);
IEnumerable<Episode> SearchEpisodes(string searchQuery);
IEnumerable<Genre> SearchGenres(string searchQuery);
IEnumerable<Studio> SearchStudios(string searchQuery);
@ -42,17 +44,34 @@ namespace Kyoo.Controllers
IEnumerable<Episode> GetEpisodes(string showSlug, long seasonNumber);
//Register values
void Register(object obj);
Task Edit(object obj, bool resetOld);
void RegisterShowLinks(Library library, Collection collection, Show show);
Task SaveChanges();
void RegisterLibrary(Library library);
void RegisterCollection(Collection collection);
void RegisterShow(Show show);
void RegisterSeason(Season season);
void RegisterEpisode(Episode episode);
void RegisterGenre(Genre genre);
void RegisterStudio(Studio studio);
void RegisterPeople(People people);
// Validate values
IEnumerable<MetadataID> Validate(IEnumerable<MetadataID> id);
// Edit values
void EditLibrary(Library library, bool resetOld);
void EditCollection(Collection collection, bool resetOld);
void EditShow(Show show, bool resetOld);
void EditSeason(Season season, bool resetOld);
void EditEpisode(Episode episode, bool resetOld);
void EditGenre(Genre genre, bool resetOld);
void EditStudio(Studio studio, bool resetOld);
void EditPeople(People people, bool resetOld);
// Remove values
void RemoveShow(Show show);
void RemoveSeason(Season season);
void RemoveEpisode(Episode episode);
// Delete values
void DelteLibrary(Library library);
void DeleteCollection(Collection collection);
void DeleteShow(Show show);
void DeleteSeason(Season season);
void DeleteEpisode(Episode episode);
void DeleteGenre(Genre genre);
void DeleteStudio(Studio studio);
void DeletePeople(People people);
}
}

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using Kyoo.Models;
namespace Kyoo.Controllers
{
public interface IRepository<T>
{
T Get(string slug);
IEnumerable<T> Search(string query);
IEnumerable<T> GetAll();
T Create(T obj);
T CreateIfNotExists(T obj);
void Edit(T edited, bool resetOld);
void Delete(string slug);
}
public interface IShowRepository : IRepository<Show> {}
public interface ISeasonRepository : IRepository<Season>
{
Season Get(string showSlug, int seasonNumber);
}
public interface IEpisodeRepository : IRepository<Episode>
{
Episode Get(string showSlug, int seasonNumber, int episodeNumber);
}
public interface ILibraryRepository : IRepository<Library> {}
public interface ICollectionRepository : IRepository<Collection> {}
public interface IGenreRepository : IRepository<Genre> {}
public interface IStudioRepository : IRepository<Studio> {}
public interface IPeopleRepository : IRepository<People> {}
}

View File

@ -12,383 +12,46 @@ namespace Kyoo.Controllers
{
public class LibraryManager : ILibraryManager
{
private const int MaxSaveRetry = 3;
private readonly DatabaseContext _database;
private readonly ILibraryRepository _library;
public LibraryManager(DatabaseContext database)
public LibraryManager(ILibraryRepository library)
{
_database = database;
_library = library;
}
#region GetBySlug
public Library GetLibrary(string librarySlug)
public Library Get(string slug)
{
return _database.Libraries.FirstOrDefault(library => library.Slug == librarySlug);
}
public Collection GetCollection(string slug)
{
return _database.Collections.FirstOrDefault(col => col.Slug == slug);
throw new NotImplementedException();
}
public Show GetShow(string slug)
public IEnumerable<Library> Search(string query)
{
return _database.Shows.FirstOrDefault(show => show.Slug == slug);
throw new NotImplementedException();
}
public Season GetSeason(string showSlug, long seasonNumber)
public IEnumerable<Library> GetAll()
{
return _database.Seasons.FirstOrDefault(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber);
throw new NotImplementedException();
}
public Episode GetEpisode(string showSlug, long seasonNumber, long episodeNumber)
public Library Create(Library obj)
{
return _database.Episodes.FirstOrDefault(x => x.EpisodeNumber == episodeNumber
&& x.SeasonNumber == seasonNumber
&& x.Show.Slug == showSlug);
}
public Episode GetMovieEpisode(string movieSlug)
{
return _database.Episodes.FirstOrDefault(x => x.Show.Slug == movieSlug);
throw new NotImplementedException();
}
public Genre GetGenre(string slug)
public Library CreateIfNotExists(Library obj)
{
return _database.Genres.FirstOrDefault(genre => genre.Slug == slug);
throw new NotImplementedException();
}
public Studio GetStudio(string slug)
public void Edit(Library edited, bool resetOld)
{
return _database.Studios.FirstOrDefault(studio => studio.Slug == slug);
throw new NotImplementedException();
}
public People GetPeople(string slug)
public void Delete(string slug)
{
return _database.Peoples.FirstOrDefault(people => people.Slug == slug);
throw new NotImplementedException();
}
public ProviderID GetProvider(string name)
{
return _database.Providers.FirstOrDefault(x => x.Name == name);
}
#endregion
#region GetAll
public IEnumerable<Library> GetLibraries()
{
return _database.Libraries;
}
public IEnumerable<Collection> GetCollections()
{
return _database.Collections;
}
public IEnumerable<Show> GetShows()
{
return _database.Shows;
}
public IEnumerable<Episode> GetEpisodes()
{
return _database.Episodes;
}
public IEnumerable<Genre> GetGenres()
{
return _database.Genres;
}
public IEnumerable<Studio> GetStudios()
{
return _database.Studios;
}
public IEnumerable<People> GetPeoples()
{
return _database.Peoples;
}
public IEnumerable<Track> GetTracks()
{
return _database.Tracks;
}
#endregion
#region GetHelper
public IEnumerable<string> GetLibrariesPath()
{
IEnumerable<string> paths = new List<string>();
return Enumerable.Aggregate(_database.Libraries, paths, (current, lib) => current.Concat(lib.Paths));
}
public Show GetShowByPath(string path)
{
return _database.Shows.FirstOrDefault(show => show.Path == path);
}
public IEnumerable<Episode> GetEpisodes(string showSlug, long seasonNumber)
{
return _database.Episodes.Where(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber);
}
#endregion
#region Search
public IEnumerable<Collection> SearchCollections(string searchQuery)
{
return _database.Collections.Where(collection => EF.Functions.Like(collection.Name, $"%{searchQuery}%"))
.Take(20);
}
public IEnumerable<Show> SearchShows(string searchQuery)
{
return _database.Shows.FromSqlInterpolated($@"SELECT * FROM Shows WHERE Shows.Title LIKE {$"%{searchQuery}%"}
OR Shows.Aliases LIKE {$"%{searchQuery}%"}").Take(20);
}
public IEnumerable<Episode> SearchEpisodes(string searchQuery)
{
return _database.Episodes.Where(x => EF.Functions.Like(x.Title, $"%{searchQuery}%")).Take(20);
}
public IEnumerable<Genre> SearchGenres(string searchQuery)
{
return _database.Genres.Where(genre => EF.Functions.Like(genre.Name, $"%{searchQuery}%"))
.Take(20);
}
public IEnumerable<Studio> SearchStudios(string searchQuery)
{
return _database.Studios.Where(studio => EF.Functions.Like(studio.Name, $"%{searchQuery}%"))
.Take(20);
}
public IEnumerable<People> SearchPeople(string searchQuery)
{
return _database.Peoples.Where(people => EF.Functions.Like(people.Name, $"%{searchQuery}%"))
.OrderBy(x => x.ImgPrimary == null)
.ThenBy(x => x.Name)
.Take(20);
}
#endregion
#region Register
public void Register(object obj)
{
if (obj == null)
return;
ValidateRootEntry(_database.Entry(obj), entry =>
{
if (entry.State != EntityState.Detached)
return false;
entry.State = EntityState.Added;
return true;
});
}
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);
}
private async Task SaveChanges(int retryCount)
{
ValidateChanges();
try
{
await _database.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (retryCount < MaxSaveRetry)
await SaveChanges(retryCount + 1);
else
throw;
}
}
public async Task Edit(object obj, bool resetOld)
{
_database.ChangeTracker.LazyLoadingEnabled = false;
_database.ChangeTracker.AutoDetectChangesEnabled = false;
try
{
object existing = FindExisting(obj);
if (existing == null)
throw new ItemNotFound($"No existing object (of type {obj.GetType().Name}) found on the databse.");
if (resetOld)
Utility.Nullify(existing);
Utility.Merge(existing, obj);
ValidateRootEntry(_database.Entry(existing), entry => entry.State != EntityState.Added);
_database.ChangeTracker.DetectChanges();
await _database.SaveChangesAsync();
}
finally
{
_database.ChangeTracker.LazyLoadingEnabled = true;
_database.ChangeTracker.AutoDetectChangesEnabled = true;
}
}
#endregion
#region ValidateValue
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 ValidateRootEntry(EntityEntry entry, Func<EntityEntry, bool> shouldRun)
{
if (!shouldRun.Invoke(entry))
return;
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)
ValidateRootEntry(_database.Entry(childEntry), shouldRun);
}
else
ValidateRootEntry(_database.Entry(navigation.CurrentValue), shouldRun);
}
}
private void ValidateNavigation(NavigationEntry navigation)
{
object oldValue = navigation.CurrentValue;
if (oldValue == null)
return;
object newValue = Validate(oldValue);
if (ReferenceEquals(oldValue, newValue))
return;
navigation.CurrentValue = newValue;
if (!navigation.Metadata.IsCollection())
_database.Entry(oldValue).State = EntityState.Detached;
}
private T Validate<T>(T obj) where T : class
{
switch (obj)
{
case null:
return null;
case IEnumerable<object> enumerable:
return (T)Utility.RunGenericMethod(
this,
"ValidateList",
Utility.GetEnumerableType(enumerable), new [] {obj});
}
EntityState state = _database.Entry(obj).State;
if (state != EntityState.Added && state != EntityState.Detached)
return obj;
return (T)(FindExisting(obj) ?? obj);
}
public IEnumerable<T> ValidateList<T>(IEnumerable<T> list) where T : class
{
return list.Select(x =>
{
T tmp = Validate(x);
if (tmp != x)
_database.Entry(x).State = EntityState.Detached;
return tmp ?? x;
})/*.GroupBy(GetSlug).Select(x => x.First()).Where(x => x != null)*/.ToList();
}
private object FindExisting(object obj)
{
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),
Genre genre => GetGenre(genre.Slug),
ProviderID provider => GetProvider(provider.Name),
_ => null
};
}
public IEnumerable<MetadataID> Validate(IEnumerable<MetadataID> ids)
{
return ids?.Select(x =>
{
x.Provider = _database.Providers.FirstOrDefault(y => y.Name == x.Provider.Name) ?? x.Provider;
return x;
}).GroupBy(x => x.Provider.Name).Select(x => x.First()).ToList();
}
#endregion
#region Remove
public void RemoveShow(Show show)
{
if (_database.Entry(show).State == EntityState.Detached)
_database.Shows.Attach(show);
_database.Shows.Remove(show);
}
public void RemoveSeason(Season season)
{
if (_database.Entry(season).State == EntityState.Detached)
_database.Seasons.Attach(season);
_database.Seasons.Remove(season);
}
public void RemoveEpisode(Episode episode)
{
if (_database.Entry(episode).State == EntityState.Detached)
_database.Episodes.Attach(episode);
_database.Episodes.Remove(episode);
}
#endregion
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers
{
public class ShowRepository : IShowRepository
{
private readonly DatabaseContext _database;
private readonly ILibraryManager _library;
public ShowRepository(DatabaseContext database, ILibraryManager library)
{
_database = database;
_library = library;
}
public Show Get(string slug)
{
return _database.Shows.FirstOrDefault(x => x.Slug == slug);
}
public IEnumerable<Show> Search(string query)
{
return _database.Shows.FromSqlInterpolated($@"SELECT * FROM Shows WHERE Shows.Title LIKE {$"%{query}%"}
OR Shows.Aliases LIKE {$"%{query}%"}").Take(20);
}
public IEnumerable<Show> GetAll()
{
return _database.Shows.ToList();
}
public Show Create(Show obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
obj.Genres = obj.Genres.Select(_library.CreateIfNotExists).ToList();
_database.Shows.Add(obj);
_database.SaveChanges();
return obj;
}
public Show CreateIfNotExists(Show obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return Get(obj.Slug) ?? Create(obj);
}
public void Edit(Show edited, bool resetOld)
{
throw new System.NotImplementedException();
}
public void Delete(string slug)
{
throw new System.NotImplementedException();
}
}
}