Fix repositories create/edit/delete events

This commit is contained in:
Zoe Roux 2023-10-31 01:27:04 +01:00
parent d7dee62e97
commit 5b7bfa79f9
16 changed files with 97 additions and 78 deletions

View File

@ -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

View File

@ -37,7 +37,8 @@ namespace Kyoo.Abstractions.Controllers
/// The event handler type for all events of this repository.
/// </summary>
/// <param name="resource">The resource created/modified/deleted</param>
public delegate void ResourceEventHandler(T resource);
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public delegate Task ResourceEventHandler(T resource);
/// <summary>
/// Get a resource from it's ID.
@ -146,7 +147,15 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Called when a resource has been created.
/// </summary>
event ResourceEventHandler OnCreated;
static event ResourceEventHandler OnCreated;
/// <summary>
/// Callback that should be called after a resource has been created.
/// </summary>
/// <param name="obj">The resource newly created.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceCreated(T obj)
=> OnCreated?.Invoke(obj) ?? Task.CompletedTask;
/// <summary>
/// Edit a resource and replace every property
@ -171,7 +180,15 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Called when a resource has been edited.
/// </summary>
event ResourceEventHandler OnEdited;
static event ResourceEventHandler OnEdited;
/// <summary>
/// Callback that should be called after a resource has been edited.
/// </summary>
/// <param name="obj">The resource newly edited.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceEdited(T obj)
=> OnEdited?.Invoke(obj) ?? Task.CompletedTask;
/// <summary>
/// Delete a resource by it's ID
@ -207,7 +224,15 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Called when a resource has been edited.
/// </summary>
event ResourceEventHandler OnDeleted;
static event ResourceEventHandler OnDeleted;
/// <summary>
/// Callback that should be called after a resource has been deleted.
/// </summary>
/// <param name="obj">The resource newly deleted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected static Task OnResourceDeleted(T obj)
=> OnDeleted?.Invoke(obj) ?? Task.CompletedTask;
}
/// <summary>

View File

@ -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<Collection>.OnResourceCreated(obj);
return obj;
}

View File

@ -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<Episode>.By(x => x.EpisodeNumber)
);
static EpisodeRepository()
{
// Edit episode slugs when the show's slug changes.
IRepository<Show>.OnEdited += async (show) =>
{
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
DatabaseContext database = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
List<Episode> 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<Episode>.OnResourceEdited(ep);
}
};
}
/// <summary>
/// Create a new <see cref="EpisodeRepository"/>.
/// </summary>
@ -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<Episode> episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList();
foreach (Episode ep in episodes)
{
ep.ShowSlug = show.Slug;
_database.SaveChanges();
OnResourceEdited(ep);
}
};
}
/// <inheritdoc />
@ -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<Episode>.OnResourceCreated(obj);
return obj;
}

View File

@ -71,15 +71,6 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public Type RepositoryType => typeof(T);
/// <inheritdoc/>
public event IRepository<T>.ResourceEventHandler OnCreated;
/// <inheritdoc/>
public event IRepository<T>.ResourceEventHandler OnEdited;
/// <inheritdoc/>
public event IRepository<T>.ResourceEventHandler OnDeleted;
/// <summary>
/// Sort the given query.
/// </summary>
@ -434,24 +425,6 @@ namespace Kyoo.Core.Controllers
return obj;
}
/// <summary>
/// Callback that should be called after a resource has been created.
/// </summary>
/// <param name="obj">The resource newly created.</param>
protected void OnResourceCreated(T obj)
{
OnCreated?.Invoke(obj);
}
/// <summary>
/// Callback that should be called after a resource has been edited.
/// </summary>
/// <param name="obj">The resource newly edited.</param>
protected void OnResourceEdited(T obj)
{
OnEdited?.Invoke(obj);
}
/// <inheritdoc/>
public virtual async Task<T> CreateIfNotExists(T obj)
{
@ -481,7 +454,7 @@ namespace Kyoo.Core.Controllers
Merger.Complete(old, edited, x => x.GetCustomAttribute<LoadableRelationAttribute>() == null);
await EditRelations(old, edited);
await Database.SaveChangesAsync();
OnEdited?.Invoke(old);
await IRepository<T>.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<T>.OnResourceEdited(resource);
return resource;
}
finally
@ -586,7 +559,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public virtual Task Delete(T obj)
{
OnDeleted?.Invoke(obj);
IRepository<T>.OnResourceDeleted(obj);
return Task.CompletedTask;
}

View File

@ -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<Movie>.OnResourceCreated(obj);
return obj;
}

View File

@ -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<People>.OnResourceCreated(obj);
return obj;
}

View File

@ -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
/// <inheritdoc/>
protected override Sort<Season> DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
static SeasonRepository()
{
// Edit seasons slugs when the show's slug changes.
IRepository<Show>.OnEdited += async (show) =>
{
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
DatabaseContext database = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
List<Season> 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<Season>.OnResourceEdited(season);
}
};
}
/// <summary>
/// Create a new <see cref="SeasonRepository"/>.
/// </summary>
/// <param name="database">The database handle that will be used</param>
/// <param name="shows">A shows repository</param>
/// <param name="thumbs">The thumbnail manager used to store images.</param>
public SeasonRepository(DatabaseContext database,
IRepository<Show> shows,
IThumbnailsManager thumbs)
: base(database, thumbs)
{
_database = database;
// Edit seasons slugs when the show's slug changes.
shows.OnEdited += (show) =>
{
List<Season> seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList();
foreach (Season season in seasons)
{
season.ShowSlug = show.Slug;
_database.SaveChanges();
OnResourceEdited(season);
}
};
}
/// <inheritdoc/>
@ -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<Season>.OnResourceCreated(obj);
return obj;
}

View File

@ -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<Show>.OnResourceCreated(obj);
return obj;
}

View File

@ -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<Studio>.OnResourceCreated(obj);
return obj;
}

View File

@ -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<User>.OnResourceCreated(obj);
return obj;
}

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using AspNetCore.Proxy;
@ -41,6 +42,12 @@ namespace Kyoo.Core
/// </summary>
public class CoreModule : IPlugin
{
/// <summary>
/// A service provider to access services in static context (in events for example).
/// </summary>
/// <remarks>Don't forget to create a scope.</remarks>
public static IServiceProvider Services { get; set; }
/// <inheritdoc />
public string Name => "Core";

View File

@ -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);

View File

@ -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<string?>("MEILI_MASTER_KEY")
)).SingleInstance();
builder.RegisterType<SearchManager>().SingleInstance();
builder.RegisterType<SearchManager>().As<ISearchManager>().SingleInstance().AutoActivate();
}
}
}

View File

@ -64,9 +64,10 @@ public class SearchManager : ISearchManager
if (kind != null)
{
dynamic expando = new ExpandoObject();
var dictionary = (IDictionary<string, object?>)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 });

View File

@ -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);