diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 3a8e3b60..692e951c 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using JetBrains.Annotations; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -14,9 +15,36 @@ namespace Kyoo.Models { /// public int ID { get; set; } - + /// - public string Slug => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber, AbsoluteNumber); + public string Slug + { + get => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber, AbsoluteNumber); + set + { + Match match = Regex.Match(value, @"(?.*)-s(?\d*)e(?\d*)"); + + if (match.Success) + { + ShowSlug = match.Groups["show"].Value; + SeasonNumber = int.Parse(match.Groups["season"].Value); + EpisodeNumber = int.Parse(match.Groups["episode"].Value); + } + else + { + match = Regex.Match(value, @"(?.*)-(?\d*)"); + if (match.Success) + { + ShowSlug = match.Groups["Show"].Value; + AbsoluteNumber = int.Parse(match.Groups["absolute"].Value); + } + else + ShowSlug = value; + SeasonNumber = -1; + EpisodeNumber = -1; + } + } + } /// /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 3c8f21a9..35bb6ffe 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -12,9 +13,21 @@ namespace Kyoo.Models { /// public int ID { get; set; } - + /// - public string Slug => $"{ShowSlug}-s{SeasonNumber}"; + public string Slug + { + get => $"{ShowSlug}-s{SeasonNumber}"; + set + { + Match match = Regex.Match(value, @"(?.*)-s(?\d*)"); + + if (!match.Success) + throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); + ShowSlug = match.Groups["show"].Value; + SeasonNumber = int.Parse(match.Groups["season"].Value); + } + } /// /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed. diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 290ecd3f..17736e07 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -1,5 +1,7 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -44,10 +46,36 @@ namespace Kyoo.Models "subrip" => ".srt", {} x => $".{x}" }; - return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}"; + return $"{EpisodeSlug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}"; + } + set + { + Match match = Regex.Match(value, @"(?.*)-s(?\d+)e(?\d+)" + + @"(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..\w)?"); + + if (!match.Success) + { + match = Regex.Match(value, @"(?.*)\.(?.{0,3})(?-forced)?(\..\w)?"); + if (!match.Success) + throw new ArgumentException("Invalid track slug. " + + "Format: {episodeSlug}.{language}[-forced][.{extension}]"); + } + + EpisodeSlug = Episode.GetSlug(match.Groups["show"].Value, + match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : -1, + match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1); + Language = match.Groups["language"].Value; + IsForced = match.Groups["forced"].Success; + if (match.Groups["type"].Success) + Type = Enum.Parse(match.Groups["type"].Value, true); } } + /// + /// The slug of the episode that contain this track. If this is not set, this track is ill-formed. + /// + [SerializeIgnore] public string EpisodeSlug { private get; set; } + /// /// The title of the stream. /// diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index b106a2fb..f3d7f867 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -260,7 +260,7 @@ namespace Kyoo modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); diff --git a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj index 3181be71..f6add6b4 100644 --- a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj +++ b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Kyoo.Postgresql/Kyoo.Postgresql.csproj b/Kyoo.Postgresql/Kyoo.Postgresql.csproj index 05b767ab..1add3667 100644 --- a/Kyoo.Postgresql/Kyoo.Postgresql.csproj +++ b/Kyoo.Postgresql/Kyoo.Postgresql.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Kyoo.SqLite/Kyoo.SqLite.csproj b/Kyoo.SqLite/Kyoo.SqLite.csproj index 0d5ab21c..ba87fa11 100644 --- a/Kyoo.SqLite/Kyoo.SqLite.csproj +++ b/Kyoo.SqLite/Kyoo.SqLite.csproj @@ -19,11 +19,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 1a2a392a..81b1626e 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -82,10 +82,6 @@ namespace Kyoo.SqLite /// The database's model builder. protected override void OnModelCreating(ModelBuilder modelBuilder) { - // modelBuilder.HasPostgresEnum(); - // modelBuilder.HasPostgresEnum(); - // modelBuilder.HasPostgresEnum(); - ValueConverter arrayConvertor = new( x => string.Join(";", x), x => x.Split(';', StringSplitOptions.None)); @@ -112,7 +108,6 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.ExtraData) .HasConversion(jsonConvertor); - base.OnModelCreating(modelBuilder); } @@ -127,7 +122,7 @@ namespace Kyoo.SqLite public override Expression> Like(Expression> query, string format) { MethodInfo iLike = MethodOfUtils.MethodOf(EF.Functions.Like); - MethodCallExpression call = Expression.Call(iLike, query.Body, Expression.Constant(format)); + MethodCallExpression call = Expression.Call(iLike, Expression.Constant(EF.Functions), query.Body, Expression.Constant(format)); return Expression.Lambda>(call, query.Parameters); } diff --git a/Kyoo.Tests/Library/RepositoryActivator.cs b/Kyoo.Tests/Library/RepositoryActivator.cs index 7352a781..df0be918 100644 --- a/Kyoo.Tests/Library/RepositoryActivator.cs +++ b/Kyoo.Tests/Library/RepositoryActivator.cs @@ -25,13 +25,13 @@ namespace Kyoo.Tests PeopleRepository people = new(_database, provider, new Lazy(() => LibraryManager.ShowRepository)); ShowRepository show = new(_database, studio, people, genre, provider); - SeasonRepository season = new(_database, provider, show); + SeasonRepository season = new(_database, provider); LibraryItemRepository libraryItem = new(_database, new Lazy(() => LibraryManager.LibraryRepository), new Lazy(() => LibraryManager.ShowRepository), new Lazy(() => LibraryManager.CollectionRepository)); TrackRepository track = new(_database); - EpisodeRepository episode = new(_database, provider, show, track); + EpisodeRepository episode = new(_database, provider, track); LibraryManager = new LibraryManager(new IBaseRepository[] { provider, diff --git a/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs new file mode 100644 index 00000000..a61436e9 --- /dev/null +++ b/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs @@ -0,0 +1,15 @@ +using Kyoo.Controllers; +using Kyoo.Models; + +namespace Kyoo.Tests.SpecificTests +{ + public class SeasonTests : RepositoryTests + { + private readonly ISeasonRepository _repository; + + public SeasonTests() + { + _repository = Repositories.LibraryManager.SeasonRepository; + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Library/SpecificTests/ShowTests.cs b/Kyoo.Tests/Library/SpecificTests/ShowTests.cs index 183780aa..9c0b5138 100644 --- a/Kyoo.Tests/Library/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/ShowTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; @@ -233,5 +234,23 @@ namespace Kyoo.Tests.SpecificTests Show reference = TestSample.Get(); Assert.Equal(reference.Slug, await _repository.GetSlug(reference.ID)); } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Show value = new() + { + Slug = "super-test", + Title = "This is a test title²" + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Library/TestSample.cs b/Kyoo.Tests/Library/TestSample.cs index 7abf1ed1..6f122c89 100644 --- a/Kyoo.Tests/Library/TestSample.cs +++ b/Kyoo.Tests/Library/TestSample.cs @@ -45,6 +45,7 @@ namespace Kyoo.Tests SeasonNumber = 1, Title = "Season 1", Overview = "The first season", + Show = Get(), StartDate = new DateTime(2020, 06, 05), EndDate = new DateTime(2020, 07, 05), Poster = "poster" diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index e6765c13..5a01bd44 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -16,7 +15,7 @@ namespace Kyoo.Controllers public class EpisodeRepository : LocalRepository, IEpisodeRepository { /// - /// The databse handle + /// The database handle /// private readonly DatabaseContext _database; /// @@ -24,10 +23,6 @@ namespace Kyoo.Controllers /// private readonly IProviderRepository _providers; /// - /// A show repository to get show's slug from their ID and keep the slug in each episode. - /// - private readonly IShowRepository _shows; - /// /// A track repository to handle creation and deletion of tracks related to the current episode. /// private readonly ITrackRepository _tracks; @@ -41,66 +36,31 @@ namespace Kyoo.Controllers /// /// The database handle to use. /// A provider repository - /// A show repository /// A track repository public EpisodeRepository(DatabaseContext database, IProviderRepository providers, - IShowRepository shows, ITrackRepository tracks) : base(database) { _database = database; _providers = providers; - _shows = shows; _tracks = tracks; } - /// - public override async Task GetOrDefault(int id) + public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { - Episode ret = await base.GetOrDefault(id); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); - return ret; + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); } /// - public override async Task GetOrDefault(string slug) + public Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { - Match match = Regex.Match(slug, @"(?.*)-s(?\d*)e(?\d*)"); - - if (match.Success) - { - return await GetOrDefault(match.Groups["show"].Value, - int.Parse(match.Groups["season"].Value), - int.Parse(match.Groups["episode"].Value)); - } - - Episode episode = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); - if (episode != null) - episode.ShowSlug = slug; - return episode; - } - - /// - public override async Task GetOrDefault(Expression> where) - { - Episode ret = await base.GetOrDefault(where); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); - return ret; - } - - /// - public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) - { - Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); - if (ret != null) - ret.ShowSlug = showSlug; - return ret; + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); } /// @@ -122,61 +82,30 @@ namespace Kyoo.Controllers } /// - public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) + public Task GetAbsolute(int showID, int absoluteNumber) { - Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(showID); - return ret; + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.AbsoluteNumber == absoluteNumber); } /// - public async Task GetAbsolute(int showID, int absoluteNumber) + public Task GetAbsolute(string showSlug, int absoluteNumber) { - Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.AbsoluteNumber == absoluteNumber); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(showID); - return ret; - } - - /// - public async Task GetAbsolute(string showSlug, int absoluteNumber) - { - Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.AbsoluteNumber == absoluteNumber); - if (ret != null) - ret.ShowSlug = showSlug; - return ret; + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.AbsoluteNumber == absoluteNumber); } /// public override async Task> Search(string query) { - List episodes = await _database.Episodes + return await _database.Episodes .Where(x => x.EpisodeNumber != -1) .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); - foreach (Episode episode in episodes) - episode.ShowSlug = await _shows.GetSlug(episode.ShowID); - return episodes; } - /// - public override async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - ICollection episodes = await base.GetAll(where, sort, limit); - foreach (Episode episode in episodes) - episode.ShowSlug = await _shows.GetSlug(episode.ShowID); - return episodes; - } - /// public override async Task Create(Episode obj) { diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index ad3d5064..d4529606 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -23,66 +22,30 @@ namespace Kyoo.Controllers /// A provider repository to handle externalID creation and deletion /// private readonly IProviderRepository _providers; - /// - /// A show repository to get show's slug from their ID and keep the slug in each episode. - /// - private readonly IShowRepository _shows; /// protected override Expression> DefaultSort => x => x.SeasonNumber; /// - /// Create a new using the provided handle, a provider & a show repository and - /// a service provider to lazilly request an episode repository. + /// Create a new . /// /// The database handle that will be used /// A provider repository - /// A show repository public SeasonRepository(DatabaseContext database, - IProviderRepository providers, - IShowRepository shows) + IProviderRepository providers) : base(database) { _database = database; _providers = providers; - _shows = shows; - } - - - /// - public override async Task Get(int id) - { - Season ret = await base.Get(id); - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); - return ret; } - /// - public override async Task Get(Expression> where) - { - Season ret = await base.Get(where); - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); - return ret; - } - - /// - public override Task Get(string slug) - { - Match match = Regex.Match(slug, @"(?.*)-s(?\d*)"); - - if (!match.Success) - throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); - return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value)); - } - /// public async Task Get(int showID, int seasonNumber) { Season ret = await GetOrDefault(showID, seasonNumber); if (ret == null) throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showID}"); - ret.ShowSlug = await _shows.GetSlug(showID); return ret; } @@ -92,7 +55,6 @@ namespace Kyoo.Controllers Season ret = await GetOrDefault(showSlug, seasonNumber); if (ret == null) throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showSlug}"); - ret.ShowSlug = showSlug; return ret; } @@ -113,27 +75,13 @@ namespace Kyoo.Controllers /// public override async Task> Search(string query) { - List seasons = await _database.Seasons + return await _database.Seasons .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); - foreach (Season season in seasons) - season.ShowSlug = await _shows.GetSlug(season.ShowID); - return seasons; } - - /// - public override async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - ICollection seasons = await base.GetAll(where, sort, limit); - foreach (Season season in seasons) - season.ShowSlug = await _shows.GetSlug(season.ShowID); - return seasons; - } - + /// public override async Task Create(Season obj) { diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 4b27a5e4..b33f48a4 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -16,7 +16,7 @@ namespace Kyoo.Controllers public class TrackRepository : LocalRepository, ITrackRepository { /// - /// The databse handle + /// The database handle /// private readonly DatabaseContext _database; @@ -27,7 +27,7 @@ namespace Kyoo.Controllers /// /// Create a new . /// - /// The datatabse handle + /// The database handle public TrackRepository(DatabaseContext database) : base(database) { diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 5a369ea9..1fc38b98 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -35,9 +35,9 @@ - - - + + +