Starting to rework related properties loading. (Removing lazy loading)

This commit is contained in:
Zoe Roux 2021-02-14 00:05:55 +01:00
parent f9e0c23e62
commit 3fe7c83415
16 changed files with 170 additions and 86 deletions

View File

@ -60,6 +60,10 @@ namespace Kyoo.Controllers
Task<Genre> GetGenre(Expression<Func<Genre, bool>> where);
Task<Studio> GetStudio(Expression<Func<Studio, bool>> where);
Task<People> GetPerson(Expression<Func<People, bool>> where);
Task Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member)
where T : class, IResource
where T2 : class;
// Library Items relations
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,

View File

@ -33,12 +33,8 @@ namespace Kyoo.Controllers
Key = ExpressionRewrite.Rewrite<Func<T, object>>(key);
Descendant = descendant;
if (Key == null ||
Key.Body is MemberExpression ||
Key.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)Key.Body).Operand is MemberExpression)
return;
throw new ArgumentException("The given sort key is not valid.");
if (!Utility.IsPropertyExpression(Key))
throw new ArgumentException("The given sort key is not valid.");
}
public Sort(string sortBy)

View File

@ -6,7 +6,7 @@ using Kyoo.Models;
namespace Kyoo.Controllers
{
public class LibraryManager : ILibraryManager
public abstract class ALibraryManager : ILibraryManager
{
public ILibraryRepository LibraryRepository { get; }
public ILibraryItemRepository LibraryItemRepository { get; }
@ -20,7 +20,7 @@ namespace Kyoo.Controllers
public IPeopleRepository PeopleRepository { get; }
public IProviderRepository ProviderRepository { get; }
public LibraryManager(ILibraryRepository libraryRepository,
public ALibraryManager(ILibraryRepository libraryRepository,
ILibraryItemRepository libraryItemRepository,
ICollectionRepository collectionRepository,
IShowRepository showRepository,
@ -235,6 +235,10 @@ namespace Kyoo.Controllers
return PeopleRepository.Get(where);
}
public abstract Task Load<T, T2>(T obj, Expression<Func<T, T2>> member)
where T : class, IResource
where T2 : class;
public Task<ICollection<Library>> GetLibraries(Expression<Func<Library, bool>> where = null,
Sort<Library> sort = default,
Pagination page = default)

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" />
</ItemGroup>

View File

@ -0,0 +1,7 @@
using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class ComposedSlug : Attribute { }
}

View File

@ -4,6 +4,7 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
[ComposedSlug]
public class Episode : IResource, IOnMerge
{
public int ID { get; set; }
@ -28,7 +29,7 @@ namespace Kyoo.Models
[JsonIgnore] public virtual IEnumerable<Track> Tracks { get; set; }
public string ShowTitle => Show.Title;
public string Slug => GetSlug(Show.Slug, SeasonNumber, EpisodeNumber);
public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString();
public string Thumb
{
get

View File

@ -3,6 +3,7 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
[ComposedSlug]
public class Season : IResource
{
[JsonIgnore] public int ID { get; set; }
@ -10,7 +11,7 @@ namespace Kyoo.Models
public int SeasonNumber { get; set; } = -1;
public string Slug => $"{Show.Slug}-s{SeasonNumber}";
public string Slug => Show != null ? $"{Show.Slug}-s{SeasonNumber}" : ID.ToString();
public string Title { get; set; }
public string Overview { get; set; }
public int? Year { get; set; }

View File

@ -17,6 +17,13 @@ namespace Kyoo
{
public static class Utility
{
public static bool IsPropertyExpression<T, T2>(Expression<Func<T, T2>> ex)
{
return ex == null ||
ex.Body is MemberExpression ||
ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression;
}
public static string ToSlug(string str)
{
if (str == null)

View File

@ -12,10 +12,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
<PackageReference Include="Npgsql" Version="4.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Npgsql" Version="5.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -153,6 +153,7 @@ namespace Kyoo.Controllers
if (edited == null)
throw new ArgumentNullException(nameof(edited));
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
Database.ChangeTracker.LazyLoadingEnabled = false;
try
{
@ -193,7 +194,7 @@ namespace Kyoo.Controllers
}
finally
{
Database.ChangeTracker.LazyLoadingEnabled = true;
Database.ChangeTracker.LazyLoadingEnabled = lazyLoading;
}
}
@ -206,7 +207,7 @@ namespace Kyoo.Controllers
{
if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _))
if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute<ComposedSlug>() == null)
{
try
{
@ -352,7 +353,7 @@ namespace Kyoo.Controllers
throw new ArgumentNullException(nameof(edited));
if (edited is TInternal intern)
return Edit(intern, resetOld).Cast<T>();
TInternal obj = new TInternal();
TInternal obj = new();
Utility.Assign(obj, edited);
return base.Edit(obj, resetOld).Cast<T>();
}
@ -365,7 +366,7 @@ namespace Kyoo.Controllers
throw new ArgumentNullException(nameof(obj));
if (obj is TInternal intern)
return Delete(intern);
TInternal item = new TInternal();
TInternal item = new();
Utility.Assign(item, obj);
return Delete(item);
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Models;
namespace Kyoo.Controllers
{
public class LibaryManager : ALibraryManager
{
private readonly DatabaseContext _database;
public LibaryManager(ILibraryRepository libraryRepository,
ILibraryItemRepository libraryItemRepository,
ICollectionRepository collectionRepository,
IShowRepository showRepository,
ISeasonRepository seasonRepository,
IEpisodeRepository episodeRepository,
ITrackRepository trackRepository,
IGenreRepository genreRepository,
IStudioRepository studioRepository,
IProviderRepository providerRepository,
IPeopleRepository peopleRepository,
DatabaseContext database)
: base(libraryRepository,
libraryItemRepository,
collectionRepository,
showRepository,
seasonRepository,
episodeRepository,
trackRepository,
genreRepository,
studioRepository,
providerRepository,
peopleRepository)
{
_database = database;
}
public override Task Load<T, T2>(T obj, Expression<Func<T, T2>> member)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (!Utility.IsPropertyExpression(member) || member == null)
throw new ArgumentException($"{nameof(member)} is not a property.");
if (typeof(IEnumerable).IsAssignableFrom(typeof(T2)))
return _database.Entry(obj).Collection(member).LoadAsync();
return _database.Entry(obj).Reference(member).LoadAsync();
}
}
}

View File

@ -20,30 +20,30 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="3.1.2" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="3.1.2" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="3.1.2" />
<PackageReference Include="IdentityServer4.EntityFramework.Storage" Version="3.1.2" />
<PackageReference Include="IdentityServer4.Storage" Version="3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.6.7" />
<PackageReference Include="IdentityServer4" Version="4.1.1" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.1" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="4.1.1" />
<PackageReference Include="IdentityServer4.EntityFramework.Storage" Version="4.1.1" />
<PackageReference Include="IdentityServer4.Storage" Version="4.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.9" />
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.12" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" />
</ItemGroup>

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -10,6 +11,8 @@ using Npgsql;
namespace Kyoo
{
//TODO disable lazy loading a provide a LoadAsync method in the library manager.
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
@ -41,10 +44,10 @@ namespace Kyoo
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
}
private readonly ValueComparer<IEnumerable<string>> _stringArrayComparer =
new ValueComparer<IEnumerable<string>>(
(l1, l2) => l1.SequenceEqual(l2),
arr => arr.Aggregate(0, (i, s) => s.GetHashCode()));
private readonly ValueComparer<IEnumerable<string>> _stringArrayComparer = new(
(l1, l2) => l1.SequenceEqual(l2),
arr => arr.Aggregate(0, (i, s) => s.GetHashCode())
);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@ -266,7 +269,7 @@ namespace Kyoo
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = new CancellationToken())
CancellationToken cancellationToken = new())
{
try
{
@ -281,7 +284,7 @@ namespace Kyoo
}
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
{
try
{
@ -297,7 +300,7 @@ namespace Kyoo
}
public async Task<int> SaveChangesAsync(string duplicateMessage,
CancellationToken cancellationToken = new CancellationToken())
CancellationToken cancellationToken = new())
{
try
{
@ -312,7 +315,7 @@ namespace Kyoo
}
}
public async Task<int> SaveIfNoDuplicates(CancellationToken cancellationToken = new CancellationToken())
public async Task<int> SaveIfNoDuplicates(CancellationToken cancellationToken = new())
{
try
{
@ -324,13 +327,12 @@ namespace Kyoo
}
}
public static bool IsDuplicateException(DbUpdateException ex)
private static bool IsDuplicateException(Exception ex)
{
return ex.InnerException is PostgresException inner
&& inner.SqlState == PostgresErrorCodes.UniqueViolation;
return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation};
}
public void DiscardChanges()
private void DiscardChanges()
{
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged
&& x.State != EntityState.Detached))

View File

@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using IdentityServer4.Models;
namespace Kyoo
{
public class IdentityContext
public static class IdentityContext
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
@ -19,7 +20,7 @@ namespace Kyoo
{
return new List<Client>
{
new Client
new()
{
ClientId = "kyoo.webapp",
@ -38,6 +39,38 @@ namespace Kyoo
}
};
}
public static IEnumerable<ApiScope> GetScopes()
{
return new[]
{
new ApiScope
{
Name = "kyoo.read",
DisplayName = "Read only access to the API.",
},
new ApiScope
{
Name = "kyoo.write",
DisplayName = "Read and write access to the public API"
},
new ApiScope
{
Name = "kyoo.play",
DisplayName = "Allow playback of movies and episodes."
},
new ApiScope
{
Name = "kyoo.download",
DisplayName = "Allow downloading of episodes and movies from kyoo."
},
new ApiScope
{
Name = "kyoo.admin",
DisplayName = "Full access to the admin's API and the public API."
}
};
}
public static IEnumerable<ApiResource> GetApis()
{
@ -46,34 +79,7 @@ namespace Kyoo
new ApiResource
{
Name = "Kyoo",
Scopes =
{
new Scope
{
Name = "kyoo.read",
DisplayName = "Read only access to the API.",
},
new Scope
{
Name = "kyoo.write",
DisplayName = "Read and write access to the public API"
},
new Scope
{
Name = "kyoo.play",
DisplayName = "Allow playback of movies and episodes."
},
new Scope
{
Name = "kyoo.download",
DisplayName = "Allow downloading of episodes and movies from kyoo."
},
new Scope
{
Name = "kyoo.admin",
DisplayName = "Full access to the admin's API and the public API."
}
}
Scopes = GetScopes().Select(x => x.Name).ToArray()
}
};
}

View File

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Reflection;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using Kyoo.Api;
using Kyoo.Controllers;
@ -47,8 +48,7 @@ namespace Kyoo
services.AddDbContext<DatabaseContext>(options =>
{
options.UseLazyLoadingProxies()
.UseNpgsql(_configuration.GetConnectionString("Database"));
options.UseNpgsql(_configuration.GetConnectionString("Database"));
// .EnableSensitiveDataLogging()
// .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
}, ServiceLifetime.Transient);
@ -72,7 +72,6 @@ namespace Kyoo
services.AddIdentityServer(options =>
{
options.IssuerUri = publicUrl;
options.PublicOrigin = publicUrl;
options.UserInteraction.LoginUrl = publicUrl + "login";
options.UserInteraction.ErrorUrl = publicUrl + "error";
options.UserInteraction.LogoutUrl = publicUrl + "logout";
@ -92,6 +91,7 @@ namespace Kyoo
options.EnableTokenCleanup = true;
})
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
.AddInMemoryApiScopes(IdentityContext.GetScopes())
.AddInMemoryApiResources(IdentityContext.GetApis())
.AddProfileService<AccountController>()
.AddSigninKeys(_configuration);
@ -157,7 +157,7 @@ namespace Kyoo
services.AddHostedService(provider => (TaskManager)provider.GetService<ITaskManager>());
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
@ -186,6 +186,11 @@ namespace Kyoo
MinimumSameSitePolicy = SameSiteMode.Strict
});
app.UseAuthentication();
app.Use((ctx, next) =>
{
ctx.SetIdentityServerOrigin(_configuration.GetValue<string>("public_url"));
return next();
});
app.UseIdentityServer();
app.UseAuthorization();

View File

@ -98,10 +98,9 @@ namespace Kyoo.Tasks
await _thumbnails!.Validate(episode, true);
if (subs)
{
// TODO this doesn't work.
IEnumerable<Track> tracks = (await _transcoder!.ExtractInfos(episode.Path))
// TODO handle external subtites.
episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path))
.Where(x => x.Type != StreamType.Font);
episode.Tracks = tracks;
await _library.EditEpisode(episode, false);
}
}