Finishing the repositories's rework

This commit is contained in:
Zoe Roux 2020-07-19 00:15:34 +02:00
parent 2ad4c89806
commit 81f555ca7e
11 changed files with 144 additions and 708 deletions

View File

@ -93,9 +93,10 @@ namespace Kyoo.Models
{ {
if (Type != StreamType.Subtitle) if (Type != StreamType.Subtitle)
return null; return null;
string slug = $"/subtitle/{Episode.Slug}.{Language ?? ID.ToString()}";
if (IsForced) string slug = string.IsNullOrEmpty(Language)
slug += "-forced"; ? ID.ToString()
: $"{Episode.Slug}.{Language}{(IsForced ? "-forced" : "")}";
switch (Codec) switch (Codec)
{ {
case "ass": case "ass":

View File

@ -22,7 +22,7 @@ namespace Kyoo.Controllers
public override async Task<ICollection<Collection>> Search(string query) public override async Task<ICollection<Collection>> Search(string query)
{ {
return await _database.Collections return await _database.Collections
.Where(x => EF.Functions.Like(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -38,7 +38,7 @@ namespace Kyoo.Controllers
public override Task<Episode> Get(string slug) public override Task<Episode> Get(string slug)
{ {
Match match = Regex.Match(slug, @"(<show>.*)-s(<season>\d*)-e(<episode>\d*)"); Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)-e(?<episode>\d*)");
if (!match.Success) if (!match.Success)
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug);
@ -57,7 +57,7 @@ namespace Kyoo.Controllers
public override async Task<ICollection<Episode>> Search(string query) public override async Task<ICollection<Episode>> Search(string query)
{ {
return await _database.Episodes return await _database.Episodes
.Where(x => EF.Functions.Like(x.Title, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -21,10 +21,10 @@ namespace Kyoo.Controllers
} }
public async Task<ICollection<Genre>> Search(string query) public override async Task<ICollection<Genre>> Search(string query)
{ {
return await _database.Genres return await _database.Genres
.Where(genre => EF.Functions.Like(genre.Name, $"%{query}%")) .Where(genre => EF.Functions.ILike(genre.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
@ -57,7 +57,7 @@ namespace Kyoo.Controllers
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task Delete(Genre obj) public override async Task Delete(Genre obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));

View File

@ -9,54 +9,41 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class LibraryRepository : ILibraryRepository public class LibraryRepository : LocalRepository<Library>, ILibraryRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
protected override Expression<Func<Library, object>> DefaultSort => x => x.ID;
public LibraryRepository(DatabaseContext database, IProviderRepository providers) public LibraryRepository(DatabaseContext database, IProviderRepository providers) : base(database)
{ {
_database = database; _database = database;
_providers = providers; _providers = providers;
} }
public void Dispose()
public override void Dispose()
{ {
_database.Dispose(); _database.Dispose();
_providers.Dispose();
} }
public ValueTask DisposeAsync() public override async ValueTask DisposeAsync()
{ {
return _database.DisposeAsync(); await _database.DisposeAsync();
await _providers.DisposeAsync();
} }
public Task<Library> Get(int id) public override async Task<ICollection<Library>> Search(string query)
{
return _database.Libraries.FirstOrDefaultAsync(x => x.ID == id);
}
public Task<Library> Get(string slug)
{
return _database.Libraries.FirstOrDefaultAsync(x => x.Slug == slug);
}
public async Task<ICollection<Library>> Search(string query)
{ {
return await _database.Libraries return await _database.Libraries
.Where(x => EF.Functions.Like(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<Library>> GetAll(Expression<Func<Library, bool>> where = null, public override async Task<Library> Create(Library obj)
Sort<Library> sort = default,
Pagination limit = default)
{
return await _database.Libraries.ToListAsync();
}
public async Task<Library> Create(Library obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -74,73 +61,22 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated library (slug {obj.Slug} already exists).");
throw; throw;
} }
return obj; return obj;
} }
public async Task<Library> CreateIfNotExists(Library obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Library old = await Get(obj.Slug); protected override async Task Validate(Library obj)
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
}
public async Task<Library> Edit(Library edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
Library old = await Get(edited.Name);
if (old == null)
throw new ItemNotFound($"No library found with the name {edited.Name}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await Validate(old);
await _database.SaveChangesAsync();
return old;
}
private async Task Validate(Library obj)
{ {
if (obj.ProviderLinks != null) if (obj.ProviderLinks != null)
foreach (ProviderLink link in obj.ProviderLinks) foreach (ProviderLink link in obj.ProviderLinks)
link.Provider = await _providers.CreateIfNotExists(link.Provider); link.Provider = await _providers.CreateIfNotExists(link.Provider);
} }
public async Task Delete(int id) public override async Task Delete(Library obj)
{
Library obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
Library obj = await Get(slug);
await Delete(obj);
}
public async Task Delete(Library obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -154,23 +90,5 @@ namespace Kyoo.Controllers
_database.Entry(entry).State = EntityState.Deleted; _database.Entry(entry).State = EntityState.Deleted;
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task DeleteRange(IEnumerable<Library> objs)
{
foreach (Library obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -9,53 +9,40 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class PeopleRepository : IPeopleRepository public class PeopleRepository : LocalRepository<People>, IPeopleRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
protected override Expression<Func<People, object>> DefaultSort => x => x.Name;
public PeopleRepository(DatabaseContext database, IProviderRepository providers) public PeopleRepository(DatabaseContext database, IProviderRepository providers) : base(database)
{ {
_database = database; _database = database;
_providers = providers; _providers = providers;
} }
public void Dispose()
public override void Dispose()
{ {
_database.Dispose(); _database.Dispose();
_providers.Dispose();
} }
public ValueTask DisposeAsync() public override async ValueTask DisposeAsync()
{ {
return _database.DisposeAsync(); await _database.DisposeAsync();
await _providers.DisposeAsync();
} }
public Task<People> Get(int id) public override async Task<ICollection<People>> Search(string query)
{
return _database.Peoples.FirstOrDefaultAsync(x => x.ID == id);
}
public Task<People> Get(string slug)
{
return _database.Peoples.FirstOrDefaultAsync(x => x.Slug == slug);
}
public async Task<ICollection<People>> Search(string query)
{ {
return await _database.Peoples return await _database.Peoples
.Where(people => EF.Functions.Like(people.Name, $"%{query}%")) .Where(people => EF.Functions.ILike(people.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<People>> GetAll(Expression<Func<People, bool>> where = null, public override async Task<People> Create(People obj)
Sort<People> sort = default,
Pagination limit = default)
{
return await _database.Peoples.ToListAsync();
}
public async Task<People> Create(People obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -73,7 +60,7 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated people (slug {obj.Slug} already exists).");
throw; throw;
} }
@ -81,65 +68,14 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
public async Task<People> CreateIfNotExists(People obj) protected override async Task Validate(People obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
People old = await Get(obj.Slug);
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
}
public async Task<People> Edit(People edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
People old = await Get(edited.Slug);
if (old == null)
throw new ItemNotFound($"No people found with the slug {edited.Slug}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await Validate(old);
await _database.SaveChangesAsync();
return old;
}
private async Task Validate(People obj)
{ {
if (obj.ExternalIDs != null) if (obj.ExternalIDs != null)
foreach (MetadataID link in obj.ExternalIDs) foreach (MetadataID link in obj.ExternalIDs)
link.Provider = await _providers.CreateIfNotExists(link.Provider); link.Provider = await _providers.CreateIfNotExists(link.Provider);
} }
public async Task Delete(int id) public override async Task Delete(People obj)
{
People obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
People obj = await Get(slug);
await Delete(obj);
}
public async Task Delete(People obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -153,23 +89,5 @@ namespace Kyoo.Controllers
_database.Entry(link).State = EntityState.Deleted; _database.Entry(link).State = EntityState.Deleted;
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task DeleteRange(IEnumerable<People> objs)
{
foreach (People obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -9,52 +9,26 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class ProviderRepository : IProviderRepository public class ProviderRepository : LocalRepository<ProviderID>, IProviderRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
protected override Expression<Func<ProviderID, object>> DefaultSort => x => x.Slug;
public ProviderRepository(DatabaseContext database) public ProviderRepository(DatabaseContext database) : base(database)
{ {
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync() public override async Task<ICollection<ProviderID>> Search(string query)
{
return _database.DisposeAsync();
}
public async Task<ProviderID> Get(int id)
{
return await _database.Providers.FirstOrDefaultAsync(x => x.ID == id);
}
public async Task<ProviderID> Get(string slug)
{
return await _database.Providers.FirstOrDefaultAsync(x => x.Name == slug);
}
public async Task<ICollection<ProviderID>> Search(string query)
{ {
return await _database.Providers return await _database.Providers
.Where(x => EF.Functions.Like(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<ProviderID>> GetAll(Expression<Func<ProviderID, bool>> where = null, public override async Task<ProviderID> Create(ProviderID obj)
Sort<ProviderID> sort = default,
Pagination limit = default)
{
return await _database.Providers.ToListAsync();
}
public async Task<ProviderID> Create(ProviderID obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -68,7 +42,7 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated provider (name {obj.Name} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated provider (name {obj.Name} already exists).");
throw; throw;
} }
@ -76,57 +50,12 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
public async Task<ProviderID> CreateIfNotExists(ProviderID obj) protected override Task Validate(ProviderID ressource)
{ {
if (obj == null) return Task.CompletedTask;
throw new ArgumentNullException(nameof(obj));
ProviderID old = await Get(obj.Name);
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Name);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
} }
public async Task<ProviderID> Edit(ProviderID edited, bool resetOld) public override async Task Delete(ProviderID obj)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
ProviderID old = await Get(edited.Name);
if (old == null)
throw new ItemNotFound($"No provider found with the name {edited.Name}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await _database.SaveChangesAsync();
return old;
}
public async Task Delete(int id)
{
ProviderID obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
ProviderID obj = await Get(slug);
await Delete(obj);
}
public async Task Delete(ProviderID obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -135,23 +64,5 @@ namespace Kyoo.Controllers
// TODO handle ExternalID deletion when they refer to this providerID. // TODO handle ExternalID deletion when they refer to this providerID.
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task DeleteRange(IEnumerable<ProviderID> objs)
{
foreach (ProviderID obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
@ -9,44 +10,44 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class SeasonRepository : ISeasonRepository public class SeasonRepository : LocalRepository<Season>, ISeasonRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
private readonly IEpisodeRepository _episodes; private readonly IEpisodeRepository _episodes;
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber;
public SeasonRepository(DatabaseContext database, IProviderRepository providers, IEpisodeRepository episodes) public SeasonRepository(DatabaseContext database, IProviderRepository providers, IEpisodeRepository episodes)
: base(database)
{ {
_database = database; _database = database;
_providers = providers; _providers = providers;
_episodes = episodes; _episodes = episodes;
} }
public void Dispose()
public override void Dispose()
{ {
_database.Dispose(); _database.Dispose();
_providers.Dispose();
_episodes.Dispose();
} }
public ValueTask DisposeAsync() public override async ValueTask DisposeAsync()
{ {
return _database.DisposeAsync(); await _database.DisposeAsync();
} await _providers.DisposeAsync();
await _episodes.DisposeAsync();
public Task<Season> Get(int id)
{
return _database.Seasons.FirstOrDefaultAsync(x => x.ID == id);
} }
public Task<Season> Get(string slug) public override Task<Season> Get(string slug)
{ {
int index = slug.IndexOf("-s", StringComparison.Ordinal); Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)");
if (index == -1)
throw new InvalidOperationException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); if (!match.Success)
string showSlug = slug.Substring(0, index); throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
if (!int.TryParse(slug.Substring(index + 2), out int seasonNumber)) return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value));
throw new InvalidOperationException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
return Get(showSlug, seasonNumber);
} }
public Task<Season> Get(string showSlug, int seasonNumber) public Task<Season> Get(string showSlug, int seasonNumber)
@ -55,22 +56,15 @@ namespace Kyoo.Controllers
&& x.SeasonNumber == seasonNumber); && x.SeasonNumber == seasonNumber);
} }
public async Task<ICollection<Season>> Search(string query) public override async Task<ICollection<Season>> Search(string query)
{ {
return await _database.Seasons return await _database.Seasons
.Where(x => EF.Functions.Like(x.Title, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<Season>> GetAll(Expression<Func<Season, bool>> where = null, public override async Task<Season> Create(Season obj)
Sort<Season> sort = default,
Pagination limit = default)
{
return await _database.Seasons.ToListAsync();
}
public async Task<Season> Create(Season obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -88,55 +82,15 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated season (slug {obj.Slug} already exists).");
throw; throw;
} }
return obj; return obj;
} }
public async Task<Season> CreateIfNotExists(Season obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Season old = await Get(obj.Slug); protected override async Task Validate(Season obj)
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
}
public async Task<Season> Edit(Season edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
Season old = await Get(edited.Slug);
if (old == null)
throw new ItemNotFound($"No season found with the slug {edited.Slug}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await Validate(old);
await _database.SaveChangesAsync();
return old;
}
private async Task Validate(Season obj)
{ {
if (obj.ShowID <= 0) if (obj.ShowID <= 0)
throw new InvalidOperationException($"Can't store a season not related to any show (showID: {obj.ShowID})."); throw new InvalidOperationException($"Can't store a season not related to any show (showID: {obj.ShowID}).");
@ -158,25 +112,13 @@ namespace Kyoo.Controllers
return await _database.Seasons.Where(x => x.Show.Slug == showSlug).ToListAsync(); return await _database.Seasons.Where(x => x.Show.Slug == showSlug).ToListAsync();
} }
public async Task Delete(int id)
{
Season obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
Season obj = await Get(slug);
await Delete(obj);
}
public async Task Delete(string showSlug, int seasonNumber) public async Task Delete(string showSlug, int seasonNumber)
{ {
Season obj = await Get(showSlug, seasonNumber); Season obj = await Get(showSlug, seasonNumber);
await Delete(obj); await Delete(obj);
} }
public async Task Delete(Season obj) public override async Task Delete(Season obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -192,23 +134,5 @@ namespace Kyoo.Controllers
if (obj.Episodes != null) if (obj.Episodes != null)
await _episodes.DeleteRange(obj.Episodes); await _episodes.DeleteRange(obj.Episodes);
} }
public async Task DeleteRange(IEnumerable<Season> objs)
{
foreach (Season obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -3,14 +3,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class ShowRepository : IShowRepository public class ShowRepository : LocalRepository<Show>, IShowRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IStudioRepository _studios; private readonly IStudioRepository _studios;
@ -19,6 +18,7 @@ namespace Kyoo.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
private readonly ISeasonRepository _seasons; private readonly ISeasonRepository _seasons;
private readonly IEpisodeRepository _episodes; private readonly IEpisodeRepository _episodes;
protected override Expression<Func<Show, object>> DefaultSort => x => x.Title;
public ShowRepository(DatabaseContext database, public ShowRepository(DatabaseContext database,
IStudioRepository studios, IStudioRepository studios,
@ -27,6 +27,7 @@ namespace Kyoo.Controllers
IProviderRepository providers, IProviderRepository providers,
ISeasonRepository seasons, ISeasonRepository seasons,
IEpisodeRepository episodes) IEpisodeRepository episodes)
: base(database)
{ {
_database = database; _database = database;
_studios = studios; _studios = studios;
@ -36,70 +37,39 @@ namespace Kyoo.Controllers
_seasons = seasons; _seasons = seasons;
_episodes = episodes; _episodes = episodes;
} }
public void Dispose() public override void Dispose()
{ {
_database.Dispose(); _database.Dispose();
_studios.Dispose(); _studios.Dispose();
_people.Dispose();
_genres.Dispose();
_providers.Dispose();
_seasons.Dispose();
_episodes.Dispose();
} }
public async ValueTask DisposeAsync() public override async ValueTask DisposeAsync()
{ {
await Task.WhenAll(_database.DisposeAsync().AsTask(), _studios.DisposeAsync().AsTask()); await _database.DisposeAsync();
} await _studios.DisposeAsync();
await _people.DisposeAsync();
public Task<Show> Get(int id) await _genres.DisposeAsync();
{ await _providers.DisposeAsync();
return _database.Shows.FirstOrDefaultAsync(x => x.ID == id); await _seasons.DisposeAsync();
} await _episodes.DisposeAsync();
public Task<Show> Get(string slug)
{
return _database.Shows.FirstOrDefaultAsync(x => x.Slug == slug);
} }
public Task<Show> GetByPath(string path) public override async Task<ICollection<Show>> Search(string query)
{
return _database.Shows.FirstOrDefaultAsync(x => x.Path == path);
}
public async Task<ICollection<Show>> Search(string query)
{ {
return await _database.Shows return await _database.Shows
.FromSqlInterpolated($@"SELECT * FROM Shows WHERE 'Shows.Title' LIKE {$"%{query}%"} .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")
OR 'Shows.Aliases' LIKE {$"%{query}%"}") /*|| EF.Functions.ILike(x.Aliases, $"%{query}%")*/)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<Show>> GetAll(Expression<Func<Show, bool>> where = null, public override async Task<Show> Create(Show obj)
Sort<Show> sort = default,
Pagination limit = default)
{
IQueryable<Show> query = _database.Shows;
if (where != null)
query = query.Where(where);
Expression<Func<Show, object>> sortKey = sort.Key ?? (x => x.Title);
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
if (limit.AfterID != 0)
{
Show after = await Get(limit.AfterID);
object afterObj = sortKey.Compile()(after);
query = query.Where(Expression.Lambda<Func<Show, bool>>(
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)),
(ParameterExpression)((MemberExpression)sortKey.Body).Expression
));
}
if (limit.Count > 0)
query = query.Take(limit.Count);
return await query.ToListAsync();
}
public async Task<Show> Create(Show obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -123,7 +93,7 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated show (slug {obj.Slug} already exists).");
throw; throw;
} }
@ -131,46 +101,7 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
public async Task<Show> CreateIfNotExists(Show obj) protected override async Task Validate(Show obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Show old = await Get(obj.Slug);
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
}
public async Task<Show> Edit(Show edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
Show old = await Get(edited.Slug);
if (old == null)
throw new ItemNotFound($"No show found with the slug {edited.Slug}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await Validate(old);
await _database.SaveChangesAsync();
return old;
}
private async Task Validate(Show obj)
{ {
if (obj.Studio != null) if (obj.Studio != null)
obj.Studio = await _studios.CreateIfNotExists(obj.Studio); obj.Studio = await _studios.CreateIfNotExists(obj.Studio);
@ -210,20 +141,8 @@ namespace Kyoo.Controllers
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task Delete(int id)
{
Show obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
Show obj = await Get(slug);
await Delete(obj);
}
public async Task Delete(Show obj) public override async Task Delete(Show obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -258,23 +177,5 @@ namespace Kyoo.Controllers
if (obj.Episodes != null) if (obj.Episodes != null)
await _episodes.DeleteRange(obj.Episodes); await _episodes.DeleteRange(obj.Episodes);
} }
public async Task DeleteRange(IEnumerable<Show> objs)
{
foreach (Show obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -9,52 +9,26 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class StudioRepository : IStudioRepository public class StudioRepository : LocalRepository<Studio>, IStudioRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
public StudioRepository(DatabaseContext database) public StudioRepository(DatabaseContext database) : base(database)
{ {
_database = database; _database = database;
} }
public void Dispose() public override async Task<ICollection<Studio>> Search(string query)
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Studio> Get(int id)
{
return await _database.Studios.FirstOrDefaultAsync(x => x.ID == id);
}
public async Task<Studio> Get(string slug)
{
return await _database.Studios.FirstOrDefaultAsync(x => x.Slug == slug);
}
public async Task<ICollection<Studio>> Search(string query)
{ {
return await _database.Studios return await _database.Studios
.Where(x => EF.Functions.Like(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
public async Task<ICollection<Studio>> GetAll(Expression<Func<Studio, bool>> where = null, public override async Task<Studio> Create(Studio obj)
Sort<Studio> sort = default,
Pagination limit = default)
{
return await _database.Studios.ToListAsync();
}
public async Task<Studio> Create(Studio obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -68,64 +42,19 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists).");
throw; throw;
} }
return obj; return obj;
} }
public async Task<Studio> CreateIfNotExists(Studio obj) protected override Task Validate(Studio ressource)
{ {
if (obj == null) return Task.CompletedTask;
throw new ArgumentNullException(nameof(obj));
Studio old = await Get(obj.Slug);
if (old != null)
return old;
try
{
return await Create(obj);
}
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old;
}
}
public async Task<Studio> Edit(Studio edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
Studio old = await Get(edited.Name);
if (old == null)
throw new ItemNotFound($"No studio found with the name {edited.Name}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await _database.SaveChangesAsync();
return old;
}
public async Task Delete(int id)
{
Studio obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
Studio obj = await Get(slug);
await Delete(obj);
} }
public async Task Delete(Studio obj) public override async Task Delete(Studio obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -137,23 +66,5 @@ namespace Kyoo.Controllers
show.StudioID = null; show.StudioID = null;
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task DeleteRange(IEnumerable<Studio> objs)
{
foreach (Studio obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
@ -8,34 +9,39 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public class TrackRepository : ITrackRepository public class TrackRepository : LocalRepository<Track>, ITrackRepository
{ {
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
protected override Expression<Func<Track, object>> DefaultSort => x => x.ID;
public TrackRepository(DatabaseContext database) public TrackRepository(DatabaseContext database) : base(database)
{ {
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync() public override Task<Track> Get(string slug)
{ {
return _database.DisposeAsync(); Match match = Regex.Match(slug,
} @"(?<show>.*)-s(?<season>\d*)-e(?<episode>\d*).(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
public async Task<Track> Get(int id) if (!match.Success)
{ {
return await _database.Tracks.FirstOrDefaultAsync(x => x.ID == id); if (int.TryParse(slug, out int id))
} return Get(id);
throw new ArgumentException("Invalid track slug. Format: {episodeSlug}.{language}[-forced][.{extension}]");
public Task<Track> Get(string slug) }
{
throw new InvalidOperationException("Tracks do not support the get by slug method."); string showSlug = match.Groups["show"].Value;
int seasonNumber = int.Parse(match.Groups["season"].Value);
int episodeNumber = int.Parse(match.Groups["episode"].Value);
string language = match.Groups["language"].Value;
bool forced = match.Groups["forced"].Success;
return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug
&& x.Episode.SeasonNumber == seasonNumber
&& x.Episode.EpisodeNumber == episodeNumber
&& x.Language == language
&& x.IsForced == forced);
} }
public Task<Track> Get(int episodeID, string languageTag, bool isForced) public Task<Track> Get(int episodeID, string languageTag, bool isForced)
@ -45,19 +51,12 @@ namespace Kyoo.Controllers
&& x.IsForced == isForced); && x.IsForced == isForced);
} }
public Task<ICollection<Track>> Search(string query) public override Task<ICollection<Track>> Search(string query)
{ {
throw new InvalidOperationException("Tracks do not support the search method."); throw new InvalidOperationException("Tracks do not support the search method.");
} }
public async Task<ICollection<Track>> GetAll(Expression<Func<Track, bool>> where = null, public override async Task<Track> Create(Track obj)
Sort<Track> sort = default,
Pagination limit = default)
{
return await _database.Tracks.ToListAsync();
}
public async Task<Track> Create(Track obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -74,48 +73,19 @@ namespace Kyoo.Controllers
catch (DbUpdateException ex) catch (DbUpdateException ex)
{ {
_database.DiscardChanges(); _database.DiscardChanges();
if (Helper.IsDuplicateException(ex)) if (IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists).");
throw; throw;
} }
return obj; return obj;
} }
public Task<Track> CreateIfNotExists(Track obj) protected override Task Validate(Track ressource)
{ {
return Create(obj); return Task.CompletedTask;
}
public async Task<Track> Edit(Track edited, bool resetOld)
{
if (edited == null)
throw new ArgumentNullException(nameof(edited));
Track old = await Get(edited.ID);
if (old == null)
throw new ItemNotFound($"No track found with the ID {edited.ID}.");
if (resetOld)
Utility.Nullify(old);
Utility.Merge(old, edited);
await _database.SaveChangesAsync();
return old;
}
public async Task Delete(int id)
{
Track obj = await Get(id);
await Delete(obj);
}
public async Task Delete(string slug)
{
Track obj = await Get(slug);
await Delete(obj);
} }
public async Task Delete(Track obj) public override async Task Delete(Track obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
@ -123,23 +93,5 @@ namespace Kyoo.Controllers
_database.Entry(obj).State = EntityState.Deleted; _database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
public async Task DeleteRange(IEnumerable<Track> objs)
{
foreach (Track obj in objs)
await Delete(obj);
}
public async Task DeleteRange(IEnumerable<int> ids)
{
foreach (int id in ids)
await Delete(id);
}
public async Task DeleteRange(IEnumerable<string> slugs)
{
foreach (string slug in slugs)
await Delete(slug);
}
} }
} }