diff --git a/back/.editorconfig b/back/.editorconfig index 5f7f01a5..9882c05d 100644 --- a/back/.editorconfig +++ b/back/.editorconfig @@ -44,9 +44,9 @@ dotnet_style_prefer_conditional_expression_over_return = true # Disable strange throw. csharp_style_throw_expression = false:suggestion # Forbid "var" everywhere -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_when_type_is_apparent = false:warning -csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:suggestion +csharp_style_var_elsewhere = false:suggestion # Prefer method-like constructs to have a block body csharp_style_expression_bodied_methods = false:none csharp_style_expression_bodied_constructors = false:none diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index c9fab9bf..e52afafd 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -37,7 +37,8 @@ namespace Kyoo.Abstractions.Controllers /// The event handler type for all events of this repository. /// /// The resource created/modified/deleted - public delegate void ResourceEventHandler(T resource); + /// A representing the asynchronous operation. + public delegate Task ResourceEventHandler(T resource); /// /// Get a resource from it's ID. @@ -146,7 +147,15 @@ namespace Kyoo.Abstractions.Controllers /// /// Called when a resource has been created. /// - event ResourceEventHandler OnCreated; + static event ResourceEventHandler OnCreated; + + /// + /// Callback that should be called after a resource has been created. + /// + /// The resource newly created. + /// A representing the asynchronous operation. + protected static Task OnResourceCreated(T obj) + => OnCreated?.Invoke(obj) ?? Task.CompletedTask; /// /// Edit a resource and replace every property @@ -171,7 +180,15 @@ namespace Kyoo.Abstractions.Controllers /// /// Called when a resource has been edited. /// - event ResourceEventHandler OnEdited; + static event ResourceEventHandler OnEdited; + + /// + /// Callback that should be called after a resource has been edited. + /// + /// The resource newly edited. + /// A representing the asynchronous operation. + protected static Task OnResourceEdited(T obj) + => OnEdited?.Invoke(obj) ?? Task.CompletedTask; /// /// Delete a resource by it's ID @@ -207,7 +224,15 @@ namespace Kyoo.Abstractions.Controllers /// /// Called when a resource has been edited. /// - event ResourceEventHandler OnDeleted; + static event ResourceEventHandler OnDeleted; + + /// + /// Callback that should be called after a resource has been deleted. + /// + /// The resource newly deleted. + /// A representing the asynchronous operation. + protected static Task OnResourceDeleted(T obj) + => OnDeleted?.Invoke(obj) ?? Task.CompletedTask; } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index d054bb53..a8fe9241 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -68,7 +68,7 @@ namespace Kyoo.Core.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index 58c4d3a6..b52f8c3e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -25,6 +25,7 @@ using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Utils; using Kyoo.Postgresql; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Core.Controllers { @@ -48,6 +49,25 @@ namespace Kyoo.Core.Controllers new Sort.By(x => x.EpisodeNumber) ); + static EpisodeRepository() + { + // Edit episode slugs when the show's slug changes. + IRepository.OnEdited += async (show) => + { + await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope(); + DatabaseContext database = scope.ServiceProvider.GetRequiredService(); + List episodes = await database.Episodes.AsTracking() + .Where(x => x.ShowId == show.Id) + .ToListAsync(); + foreach (Episode ep in episodes) + { + ep.ShowSlug = show.Slug; + await database.SaveChangesAsync(); + await IRepository.OnResourceEdited(ep); + } + }; + } + /// /// Create a new . /// @@ -61,18 +81,6 @@ namespace Kyoo.Core.Controllers { _database = database; _shows = shows; - - // Edit episode slugs when the show's slug changes. - shows.OnEdited += (show) => - { - List episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList(); - foreach (Episode ep in episodes) - { - ep.ShowSlug = show.Slug; - _database.SaveChanges(); - OnResourceEdited(ep); - } - }; } /// @@ -96,7 +104,7 @@ namespace Kyoo.Core.Controllers obj is { SeasonNumber: not null, EpisodeNumber: not null } ? Get(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber && x.EpisodeNumber == obj.EpisodeNumber) : Get(x => x.ShowId == obj.ShowId && x.AbsoluteNumber == obj.AbsoluteNumber)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 525b4003..0796dca3 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -71,15 +71,6 @@ namespace Kyoo.Core.Controllers /// public Type RepositoryType => typeof(T); - /// - public event IRepository.ResourceEventHandler OnCreated; - - /// - public event IRepository.ResourceEventHandler OnEdited; - - /// - public event IRepository.ResourceEventHandler OnDeleted; - /// /// Sort the given query. /// @@ -434,24 +425,6 @@ namespace Kyoo.Core.Controllers return obj; } - /// - /// Callback that should be called after a resource has been created. - /// - /// The resource newly created. - protected void OnResourceCreated(T obj) - { - OnCreated?.Invoke(obj); - } - - /// - /// Callback that should be called after a resource has been edited. - /// - /// The resource newly edited. - protected void OnResourceEdited(T obj) - { - OnEdited?.Invoke(obj); - } - /// public virtual async Task CreateIfNotExists(T obj) { @@ -481,7 +454,7 @@ namespace Kyoo.Core.Controllers Merger.Complete(old, edited, x => x.GetCustomAttribute() == null); await EditRelations(old, edited); await Database.SaveChangesAsync(); - OnEdited?.Invoke(old); + await IRepository.OnResourceEdited(old); return old; } finally @@ -504,7 +477,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentException("Could not patch resource"); await Database.SaveChangesAsync(); - OnEdited?.Invoke(resource); + await IRepository.OnResourceEdited(resource); return resource; } finally @@ -586,7 +559,7 @@ namespace Kyoo.Core.Controllers /// public virtual Task Delete(T obj) { - OnDeleted?.Invoke(obj); + IRepository.OnResourceDeleted(obj); return Task.CompletedTask; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs index f1fa3e26..db1c26ac 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs @@ -85,7 +85,7 @@ namespace Kyoo.Core.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index ecadd8e0..1a004ff9 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -80,7 +79,7 @@ namespace Kyoo.Core.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index cb1ed67b..2b285b76 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -22,11 +22,10 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Utils; using Kyoo.Postgresql; -using Kyoo.Utils; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Core.Controllers { @@ -43,30 +42,35 @@ namespace Kyoo.Core.Controllers /// protected override Sort DefaultSort => new Sort.By(x => x.SeasonNumber); + static SeasonRepository() + { + // Edit seasons slugs when the show's slug changes. + IRepository.OnEdited += async (show) => + { + await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope(); + DatabaseContext database = scope.ServiceProvider.GetRequiredService(); + List seasons = await database.Seasons.AsTracking() + .Where(x => x.ShowId == show.Id) + .ToListAsync(); + foreach (Season season in seasons) + { + season.ShowSlug = show.Slug; + await database.SaveChangesAsync(); + await IRepository.OnResourceEdited(season); + } + }; + } + /// /// Create a new . /// /// The database handle that will be used - /// A shows repository /// The thumbnail manager used to store images. public SeasonRepository(DatabaseContext database, - IRepository shows, IThumbnailsManager thumbs) : base(database, thumbs) { _database = database; - - // Edit seasons slugs when the show's slug changes. - shows.OnEdited += (show) => - { - List seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList(); - foreach (Season season in seasons) - { - season.ShowSlug = show.Slug; - _database.SaveChanges(); - OnResourceEdited(season); - } - }; } /// @@ -87,7 +91,7 @@ namespace Kyoo.Core.Controllers obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug; _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 5f00c076..4d234454 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -86,7 +86,7 @@ namespace Kyoo.Core.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index 6ca571aa..31a8d920 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -69,7 +69,7 @@ namespace Kyoo.Core.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs index d07753ec..daf38738 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -70,7 +70,7 @@ namespace Kyoo.Core.Controllers if (obj.Logo != null) _database.Entry(obj).Reference(x => x.Logo).TargetEntry!.State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); + await IRepository.OnResourceCreated(obj); return obj; } diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index 72c05456..fc9bda22 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . +using System; using System.Collections.Generic; using System.Linq; using AspNetCore.Proxy; @@ -41,6 +42,12 @@ namespace Kyoo.Core /// public class CoreModule : IPlugin { + /// + /// A service provider to access services in static context (in events for example). + /// + /// Don't forget to create a scope. + public static IServiceProvider Services { get; set; } + /// public string Name => "Core"; diff --git a/back/src/Kyoo.Host/Application.cs b/back/src/Kyoo.Host/Application.cs index f9d15f3f..b406cd1e 100644 --- a/back/src/Kyoo.Host/Application.cs +++ b/back/src/Kyoo.Host/Application.cs @@ -23,6 +23,7 @@ using System.Threading; using System.Threading.Tasks; using Autofac; using Autofac.Extensions.DependencyInjection; +using Kyoo.Core; using Kyoo.Meiliseach; using Kyoo.Postgresql; using Microsoft.AspNetCore.Hosting; @@ -117,6 +118,7 @@ namespace Kyoo.Host { try { + CoreModule.Services = host.Services; _logger.Information("Version: {Version}", Assembly.GetExecutingAssembly().GetName().Version.ToString(3)); _logger.Information("Data directory: {DataDirectory}", Environment.CurrentDirectory); await host.RunAsync(cancellationToken); diff --git a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs index affd9cc7..d32a7f5c 100644 --- a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs +++ b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs @@ -63,12 +63,12 @@ namespace Kyoo.Meiliseach nameof(LibraryItem.Status), nameof(LibraryItem.AirDate), nameof(Movie.StudioID), + nameof(LibraryItem.Kind), }, SortableAttributes = new[] { nameof(LibraryItem.AirDate), nameof(LibraryItem.AddedDate), - nameof(LibraryItem.Kind), }, DisplayedAttributes = new[] { @@ -94,7 +94,7 @@ namespace Kyoo.Meiliseach _configuration.GetValue("MEILI_HOST", "http://meilisearch:7700"), _configuration.GetValue("MEILI_MASTER_KEY") )).SingleInstance(); - builder.RegisterType().SingleInstance(); + builder.RegisterType().As().SingleInstance().AutoActivate(); } } } diff --git a/back/src/Kyoo.Meilisearch/SearchManager.cs b/back/src/Kyoo.Meilisearch/SearchManager.cs index ce2195d5..a3072c7c 100644 --- a/back/src/Kyoo.Meilisearch/SearchManager.cs +++ b/back/src/Kyoo.Meilisearch/SearchManager.cs @@ -64,9 +64,10 @@ public class SearchManager : ISearchManager if (kind != null) { dynamic expando = new ExpandoObject(); + var dictionary = (IDictionary)expando; foreach (PropertyInfo property in item.GetType().GetProperties()) - expando.TryAdd(property.Name, property.GetValue(item)); + dictionary.Add(property.Name, property.GetValue(item)); expando.Ref = $"{kind}/{item.Id}"; expando.Kind = kind; return _client.Index(index).AddDocumentsAsync(new[] { item }); diff --git a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs index f2aebc4d..c68cf414 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -50,7 +50,7 @@ namespace Kyoo.Tests.Database thumbs.Object); MovieRepository movies = new(_NewContext(), studio, people, thumbs.Object); ShowRepository show = new(_NewContext(), studio, people, thumbs.Object); - SeasonRepository season = new(_NewContext(), show, thumbs.Object); + SeasonRepository season = new(_NewContext(), thumbs.Object); LibraryItemRepository libraryItem = new(_NewContext(), thumbs.Object); EpisodeRepository episode = new(_NewContext(), show, thumbs.Object); UserRepository user = new(_NewContext(), thumbs.Object);