Adding thetvdb, using autofac

This commit is contained in:
Zoe Roux 2021-07-14 00:04:55 +02:00
parent 8255c2f800
commit 0c4cab48d7
18 changed files with 664 additions and 280 deletions

View File

@ -1,21 +1,56 @@
using Kyoo.Models;
using System;
using Kyoo.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Kyoo.Controllers
{
/// <summary>
/// An interface to automatically retrieve metadata from external providers.
/// </summary>
public interface IMetadataProvider
{
/// <summary>
/// The <see cref="Provider"/> corresponding to this provider.
/// This allow to map metadata to a provider, keep metadata links and
/// know witch <see cref="IMetadataProvider"/> is used for a specific <see cref="Library"/>.
/// </summary>
Provider Provider { get; }
Task<Collection> GetCollectionFromName(string name);
/// <summary>
/// Return a new item with metadata from your provider.
/// </summary>
/// <param name="item">
/// The item to retrieve metadata from. Most of the time, only the name will be available but other
/// properties may be filed by other providers before a call to this method. This can allow you to identify
/// the collection on your provider.
/// </param>
/// <remarks>
/// You must not use metadata from the given <paramref name="item"/>.
/// Merging metadata is the job of Kyoo, a complex <typeparamref name="T"/> is given
/// to make a precise search and give you every available properties, not to discard properties.
/// </remarks>
/// <exception cref="NotSupportedException">
/// If this metadata provider does not support <typeparamref name="T"/>.
/// </exception>
/// <returns>A new <typeparamref name="T"/> containing metadata from your provider</returns>
[ItemNotNull]
Task<T> Get<T>([NotNull] T item)
where T : class, IResource;
/// <summary>
/// Search for a specific type of items with a given query.
/// </summary>
/// <param name="query">The search query to use.</param>
/// <exception cref="NotSupportedException">
/// If this metadata provider does not support <typeparamref name="T"/>.
/// </exception>
/// <returns>The list of items that could be found on this specific provider.</returns>
[ItemNotNull]
Task<ICollection<T>> Search<T>(string query)
where T : class, IResource;
Task<Show> GetShowByID(Show show);
Task<ICollection<Show>> SearchShows(string showName, bool isMovie);
Task<ICollection<PeopleRole>> GetPeople(Show show);
Task<Season> GetSeason(Show show, int seasonNumber);
Task<Episode> GetEpisode(Show show, int? seasonNumber, int? episodeNumber, int? absoluteNumber);
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@ -55,6 +56,15 @@ namespace Kyoo.Controllers
/// </remarks>
ICollection<Type> Requires { get; }
/// <summary>
/// A configure method that will be run on plugin's startup.
/// </summary>
/// <param name="builder">The autofac service container to register services.</param>
void Configure(ContainerBuilder builder)
{
// Skipped
}
/// <summary>
/// A configure method that will be run on plugin's startup.
/// </summary>
@ -64,21 +74,31 @@ namespace Kyoo.Controllers
/// or <see cref="ProviderCondition.Has(System.Collections.Generic.ICollection{System.Type},System.Collections.Generic.ICollection{System.Type})"/>>
/// You can't simply check on the service collection because some dependencies might be registered after your plugin.
/// </param>
void Configure(IServiceCollection services, ICollection<Type> availableTypes);
void Configure(IServiceCollection services, ICollection<Type> availableTypes)
{
// Skipped
}
/// <summary>
/// An optional configuration step to allow a plugin to change asp net configurations.
/// WARNING: This is only called on Kyoo's startup so you must restart the app to apply this changes.
/// </summary>
/// <param name="app">The Asp.Net application builder. On most case it is not needed but you can use it to add asp net functionalities.</param>
void ConfigureAspNet(IApplicationBuilder app) {}
void ConfigureAspNet(IApplicationBuilder app)
{
// Skipped
}
/// <summary>
/// An optional function to execute and initialize your plugin.
/// It can be used to initialize a database connection, fill initial data or anything.
/// </summary>
/// <param name="provider">A service provider to request services</param>
void Initialize(IServiceProvider provider) {}
void Initialize(IServiceProvider provider)
{
// Skipped
}
}
/// <summary>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Autofac;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@ -42,7 +43,13 @@ namespace Kyoo.Controllers
public void LoadPlugins(ICollection<IPlugin> plugins);
/// <summary>
/// Configure services adding or removing services as the plugins wants.
/// Configure container adding or removing services as the plugins wants.
/// </summary>
/// <param name="builder">The container to populate</param>
void ConfigureContainer(ContainerBuilder builder);
/// <summary>
/// Configure services via the microsoft way. This allow libraries to add their services.
/// </summary>
/// <param name="services">The service collection to populate</param>
public void ConfigureServices(IServiceCollection services);

View File

@ -1,17 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Models;
namespace Kyoo.Controllers
{
public interface IProviderManager
{
Task<Collection> GetCollectionFromName(string name, Library library);
Task<Show> CompleteShow(Show show, Library library);
Task<Show> SearchShow(string showName, bool isMovie, Library library);
Task<IEnumerable<Show>> SearchShows(string showName, bool isMovie, Library library);
Task<Season> GetSeason(Show show, int seasonNumber, Library library);
Task<Episode> GetEpisode(Show show, string episodePath, int? seasonNumber, int? episodeNumber, int? absoluteNumber, Library library);
Task<ICollection<PeopleRole>> GetPeople(Show show, Library library);
}
}

View File

@ -21,6 +21,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.2.0" />
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />

View File

@ -1,5 +1,6 @@
using System;
using System.Linq;
using Autofac;
using Autofac.Builder;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.Extensions.Configuration;
@ -15,55 +16,63 @@ namespace Kyoo
/// <summary>
/// Register a new task to the container.
/// </summary>
/// <param name="services">The container</param>
/// <param name="builder">The container</param>
/// <typeparam name="T">The type of the task</typeparam>
/// <returns>The initial container.</returns>
public static IServiceCollection AddTask<T>(this IServiceCollection services)
/// <returns>The registration builder of this new task. That can be used to edit the registration.</returns>
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterTask<T>(this ContainerBuilder builder)
where T : class, ITask
{
services.AddSingleton<ITask, T>();
return services;
return builder.RegisterType<T>().As<ITask>().SingleInstance();
}
/// <summary>
/// Register a new metadata provider to the container.
/// </summary>
/// <param name="builder">The container</param>
/// <typeparam name="T">The type of the task</typeparam>
/// <returns>The registration builder of this new provider. That can be used to edit the registration.</returns>
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterProvider<T>(this ContainerBuilder builder)
where T : class, IMetadataProvider
{
return builder.RegisterType<T>().As<IMetadataProvider>().InstancePerLifetimeScope();
}
/// <summary>
/// Register a new repository to the container.
/// </summary>
/// <param name="services">The container</param>
/// <param name="lifetime">The lifetime of the repository. The default is scoped.</param>
/// <param name="builder">The container</param>
/// <typeparam name="T">The type of the repository.</typeparam>
/// <remarks>
/// If your repository implements a special interface, please use <see cref="AddRepository{T,T2}"/>
/// If your repository implements a special interface, please use <see cref="RegisterRepository{T,T2}"/>
/// </remarks>
/// <returns>The initial container.</returns>
public static IServiceCollection AddRepository<T>(this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterRepository<T>(this ContainerBuilder builder)
where T : IBaseRepository
{
Type repository = Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>));
if (repository != null)
services.Add(ServiceDescriptor.Describe(repository, typeof(T), lifetime));
services.Add(ServiceDescriptor.Describe(typeof(IBaseRepository), typeof(T), lifetime));
return services;
return builder.RegisterType<T>()
.As<IBaseRepository>()
.As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>)))
.InstancePerLifetimeScope();
}
/// <summary>
/// Register a new repository with a custom mapping to the container.
/// </summary>
/// <param name="services"></param>
/// <param name="lifetime">The lifetime of the repository. The default is scoped.</param>
/// <param name="builder">The container</param>
/// <typeparam name="T">The custom mapping you have for your repository.</typeparam>
/// <typeparam name="T2">The type of the repository.</typeparam>
/// <remarks>
/// If your repository does not implements a special interface, please use <see cref="AddRepository{T}"/>
/// If your repository does not implements a special interface, please use <see cref="RegisterRepository{T}"/>
/// </remarks>
/// <returns>The initial container.</returns>
public static IServiceCollection AddRepository<T, T2>(this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
public static IRegistrationBuilder<T2, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterRepository<T, T2>(this ContainerBuilder builder)
where T2 : IBaseRepository, T
{
services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime));
return services.AddRepository<T2>(lifetime);
return builder.RegisterRepository<T2>().As<T>();
}
/// <summary>

View File

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Company>SDG</Company>
<Authors>Zoe Roux</Authors>
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
<LangVersion>default</LangVersion>
<RootNamespace>Kyoo.TheTvdb</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-tvdb</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<GenerateDependencyFile>false</GenerateDependencyFile>
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="TvDbSharper" Version="3.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
<PrivateAssets>all</PrivateAssets>
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using Kyoo.Controllers;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.TheTvdb
{
/// <summary>
/// A plugin that add a <see cref="IMetadataProvider"/> for The TVDB.
/// </summary>
public class PluginTvdb : IPlugin
{
/// <inheritdoc />
public string Slug => "the-tvdb";
/// <inheritdoc />
public string Name => "The TVDB Provider";
/// <inheritdoc />
public string Description => "A metadata provider for The TVDB.";
/// <inheritdoc />
public ICollection<Type> Provides => new []
{
typeof(IMetadataProvider)
};
/// <inheritdoc />
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
/// <inheritdoc />
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
{
// services.AddProvider<ProviderTvdb>();
}
}
}

View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models;
using TvDbSharper;
using TvDbSharper.Dto;
namespace Kyoo.TheTvdb
{
/// <summary>
/// A metadata provider for The TVDB.
/// </summary>
public class ProviderTvdb : IMetadataProvider
{
public Provider Provider { get; }
public Task<T> Get<T>(T item) where T : class, IResource
{
throw new NotImplementedException();
}
public Task<ICollection<T>> Search<T>(string query) where T : class, IResource
{
throw new NotImplementedException();
}
public Task<ICollection<PeopleRole>> GetPeople(Show show)
{
throw new NotImplementedException();
}
}
// public class Old
// {
// private static readonly ProviderID _provider = new()
// {
// Slug = "the-tvdb",
// Name = "TheTVDB",
// LogoExtension = "png",
// Logo = "https://www.thetvdb.com/images/logo.png"
// };
// public ProviderID Provider => _provider;
//
//
// private readonly TvDbClient _client = new();
//
// private Task Authentificate()
// {
// if (_client.Authentication.Token == null)
// return _client.Authentication.AuthenticateAsync(APIKey);
// return _client.Authentication.RefreshTokenAsync();
// }
//
// public Task<Collection> GetCollectionFromName(string name)
// {
// return Task.FromResult<Collection>(null);
// }
//
// public async Task<ICollection<Show>> SearchShows(string showName, bool isMovie)
// {
// await Authentificate();
//
// if (isMovie)
// return null; //There is no movie search API for now on TheTVDB.
// TvDbResponse<SeriesSearchResult[]> shows = await _client.Search.SearchSeriesAsync(showName, SearchParameter.Name);
// return shows.Data.Select(x => x.ToShow(Provider)).ToArray();
// }
//
// public async Task<Show> GetShowByID(Show show)
// {
// if (!int.TryParse(show?.GetID(Provider.Name), out int id))
// return await Task.FromResult<Show>(null);
// await Authentificate();
// TvDbResponse<Series> serie = await _client.Series.GetAsync(id);
// return serie.Data.ToShow(Provider);
// }
//
// public async Task<ICollection<PeopleRole>> GetPeople(Show show)
// {
// if (!int.TryParse(show?.GetID(Provider.Name), out int id))
// return null;
// await Authentificate();
// TvDbResponse<Actor[]> people = await _client.Series.GetActorsAsync(id);
// return people.Data.Select(x => x.ToPeopleRole(Provider)).ToArray();
// }
//
// public Task<Season> GetSeason(Show show, int seasonNumber)
// {
// return Task.FromResult<Season>(null);
// }
//
// public async Task<Episode> GetEpisode(Show show, int seasonNumber, int episodeNumber, int absoluteNumber)
// {
// if (!int.TryParse(show?.GetID(Provider.Name), out int id))
// return null;
// await Authentificate();
// TvDbResponse<EpisodeRecord[]> episodes = absoluteNumber != -1
// ? await _client.Series.GetEpisodesAsync(id, 0, new EpisodeQuery {AbsoluteNumber = absoluteNumber})
// : await _client.Series.GetEpisodesAsync(id, 0, new EpisodeQuery {AiredSeason = seasonNumber, AiredEpisode = episodeNumber});
// EpisodeRecord x = episodes.Data[0];
//
// if (absoluteNumber == -1)
// absoluteNumber = x.AbsoluteNumber ?? -1;
// else
// {
// seasonNumber = x.AiredSeason ?? -1;
// episodeNumber = x.AiredEpisodeNumber ?? -1;
// }
//
// return new Episode(seasonNumber,
// episodeNumber,
// absoluteNumber,
// x.EpisodeName,
// x.Overview,
// DateTime.ParseExact(x.FirstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture),
// -1,
// x.Filename != null ? "https://www.thetvdb.com/banners/" + x.Filename : null,
// new []
// {
// new MetadataID(Provider, x.Id.ToString(), $"https://www.thetvdb.com/series/{id}/episodes/{x.Id}")
// });
// }
// }
// public static class Convertors
// {
// private static int? GetYear(string firstAired)
// {
// if (firstAired?.Length >= 4 && int.TryParse(firstAired.Substring(0, 4), out int year))
// return year;
// return null;
// }
//
// private static Status? GetStatus(string status)
// {
// return status switch
// {
// "Ended" => Status.Finished,
// "Continuing" => Status.Airing,
// _ => null
// };
// }
//
// public static Show ToShow(this SeriesSearchResult x, ProviderID provider)
// {
// Show ret = new(x.Slug,
// x.SeriesName,
// x.Aliases,
// null,
// x.Overview,
// null,
// null,
// GetStatus(x.Status),
// GetYear(x.FirstAired),
// null,
// new[]
// {
// new MetadataID(provider, x.Id.ToString(), $"https://www.thetvdb.com/series/{x.Slug}")
// });
// if (x.Poster != null)
// Utility.SetImage(ret, $"https://www.thetvdb.com{x.Poster}", ImageType.Poster);
// return ret;
// }
//
// public static Show ToShow(this Series x, ProviderID provider)
// {
// return new(x.Slug,
// x.SeriesName,
// x.Aliases,
// null,
// x.Overview,
// null,
// x.Genre.Select(y => new Genre(Utility.ToSlug(y), y)),
// GetStatus(x.Status),
// GetYear(x.FirstAired),
// null,
// new[]
// {
// new MetadataID(provider, x.Id.ToString(),$"https://www.thetvdb.com/series/{x.Slug}")
// })
// {
// Poster = x.Poster != null ? $"https://www.thetvdb.com/banners/{x.Poster}" : null,
// Backdrop = x.FanArt != null ? $"https://www.thetvdb.com/banners/{x.FanArt}" : null
// };
// }
//
// public static PeopleRole ToPeopleRole(this Actor x, ProviderID provider)
// {
// return new (Utility.ToSlug(x.Name),
// x.Name,
// x.Role,
// null,
// x.Image != null ? $"https://www.thetvdb.com/banners/{x.Image}" : null,
// new[]
// {
// new MetadataID(provider, x.Id.ToString(), $"https://www.thetvdb.com/people/{x.Id}")
// });
// }
// }
}

View File

@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.SqLite", "Kyoo.SqLite\Kyoo.SqLite.csproj", "{6515380E-1E57-42DA-B6E3-E1C8A848818A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -47,5 +49,9 @@ Global
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6515380E-1E57-42DA-B6E3-E1C8A848818A}.Release|Any CPU.Build.0 = Release|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Autofac;
using Kyoo.Models.Options;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@ -127,6 +128,12 @@ namespace Kyoo.Controllers
_logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name));
}
public void ConfigureContainer(ContainerBuilder builder)
{
foreach (IPlugin plugin in _plugins)
plugin.Configure(builder);
}
/// <inheritdoc />
public void ConfigureServices(IServiceCollection services)
{

View File

@ -0,0 +1,182 @@
using System;
using Kyoo.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Kyoo.Controllers
{
public class ProviderComposite : IMetadataProvider
{
private readonly IEnumerable<IMetadataProvider> _providers;
public ProviderComposite(IEnumerable<IMetadataProvider> providers)
{
_providers = providers;
}
public Provider Provider { get; }
public Task<T> Get<T>(T item) where T : class, IResource
{
throw new NotImplementedException();
}
public Task<ICollection<T>> Search<T>(string query) where T : class, IResource
{
throw new NotImplementedException();
}
public Task<ICollection<PeopleRole>> GetPeople(Show show)
{
throw new NotImplementedException();
}
// private async Task<T> GetMetadata<T>(Func<IMetadataProvider, Task<T>> providerCall, Library library, string what)
// where T : new()
// {
// T ret = new();
//
// IEnumerable<IMetadataProvider> providers = library?.Providers
// .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug))
// .Where(x => x != null)
// ?? _providers;
//
// foreach (IMetadataProvider provider in providers)
// {
// try
// {
// ret = Merger.Merge(ret, await providerCall(provider));
// } catch (Exception ex)
// {
// await Console.Error.WriteLineAsync(
// $"The provider {provider.Provider.Name} could not work for {what}. Exception: {ex.Message}");
// }
// }
// return ret;
// }
//
// private async Task<List<T>> GetMetadata<T>(
// Func<IMetadataProvider, Task<ICollection<T>>> providerCall,
// Library library,
// string what)
// {
// List<T> ret = new();
//
// IEnumerable<IMetadataProvider> providers = library?.Providers
// .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug))
// .Where(x => x != null)
// ?? _providers;
//
// foreach (IMetadataProvider provider in providers)
// {
// try
// {
// ret.AddRange(await providerCall(provider) ?? new List<T>());
// } catch (Exception ex)
// {
// await Console.Error.WriteLineAsync(
// $"The provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
// }
// }
// return ret;
// }
//
// public async Task<Collection> GetCollectionFromName(string name, Library library)
// {
// Collection collection = await GetMetadata(
// provider => provider.GetCollectionFromName(name),
// library,
// $"the collection {name}");
// collection.Name ??= name;
// collection.Slug ??= Utility.ToSlug(name);
// return collection;
// }
//
// public async Task<Show> CompleteShow(Show show, Library library)
// {
// return await GetMetadata(provider => provider.GetShowByID(show), library, $"the show {show.Title}");
// }
//
// public async Task<Show> SearchShow(string showName, bool isMovie, Library library)
// {
// Show show = await GetMetadata(async provider =>
// {
// Show searchResult = (await provider.SearchShows(showName, isMovie))?.FirstOrDefault();
// if (searchResult == null)
// return null;
// return await provider.GetShowByID(searchResult);
// }, library, $"the show {showName}");
// show.Slug = Utility.ToSlug(showName);
// show.Title ??= showName;
// show.IsMovie = isMovie;
// show.Genres = show.Genres?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
// show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
// return show;
// }
//
// public async Task<IEnumerable<Show>> SearchShows(string showName, bool isMovie, Library library)
// {
// IEnumerable<Show> shows = await GetMetadata(
// provider => provider.SearchShows(showName, isMovie),
// library,
// $"the show {showName}");
// return shows.Select(show =>
// {
// show.Slug = Utility.ToSlug(showName);
// show.Title ??= showName;
// show.IsMovie = isMovie;
// return show;
// });
// }
//
// public async Task<Season> GetSeason(Show show, int seasonNumber, Library library)
// {
// Season season = await GetMetadata(
// provider => provider.GetSeason(show, seasonNumber),
// library,
// $"the season {seasonNumber} of {show.Title}");
// season.Show = show;
// season.ShowID = show.ID;
// season.ShowSlug = show.Slug;
// season.Title ??= $"Season {season.SeasonNumber}";
// return season;
// }
//
// public async Task<Episode> GetEpisode(Show show,
// string episodePath,
// int? seasonNumber,
// int? episodeNumber,
// int? absoluteNumber,
// Library library)
// {
// Episode episode = await GetMetadata(
// provider => provider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber),
// library,
// "an episode");
// episode.Show = show;
// episode.ShowID = show.ID;
// episode.ShowSlug = show.Slug;
// episode.Path = episodePath;
// episode.SeasonNumber ??= seasonNumber;
// episode.EpisodeNumber ??= episodeNumber;
// episode.AbsoluteNumber ??= absoluteNumber;
// return episode;
// }
//
// public async Task<ICollection<PeopleRole>> GetPeople(Show show, Library library)
// {
// List<PeopleRole> people = await GetMetadata(
// provider => provider.GetPeople(show),
// library,
// $"a cast member of {show.Title}");
// return people?.GroupBy(x => x.Slug)
// .Select(x => x.First())
// .Select(x =>
// {
// x.Show = show;
// x.ShowID = show.ID;
// return x;
// }).ToList();
// }
}
}

View File

@ -1,166 +0,0 @@
using System;
using Kyoo.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Kyoo.Controllers
{
public class ProviderManager : IProviderManager
{
private readonly IEnumerable<IMetadataProvider> _providers;
public ProviderManager(IPluginManager pluginManager)
{
_providers = pluginManager.GetPlugins<IMetadataProvider>();
}
private async Task<T> GetMetadata<T>(Func<IMetadataProvider, Task<T>> providerCall, Library library, string what)
where T : new()
{
T ret = new();
IEnumerable<IMetadataProvider> providers = library?.Providers
.Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug))
.Where(x => x != null)
?? _providers;
foreach (IMetadataProvider provider in providers)
{
try
{
ret = Merger.Merge(ret, await providerCall(provider));
} catch (Exception ex)
{
await Console.Error.WriteLineAsync(
$"The provider {provider.Provider.Name} could not work for {what}. Exception: {ex.Message}");
}
}
return ret;
}
private async Task<List<T>> GetMetadata<T>(
Func<IMetadataProvider, Task<ICollection<T>>> providerCall,
Library library,
string what)
{
List<T> ret = new();
IEnumerable<IMetadataProvider> providers = library?.Providers
.Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug))
.Where(x => x != null)
?? _providers;
foreach (IMetadataProvider provider in providers)
{
try
{
ret.AddRange(await providerCall(provider) ?? new List<T>());
} catch (Exception ex)
{
await Console.Error.WriteLineAsync(
$"The provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
}
}
return ret;
}
public async Task<Collection> GetCollectionFromName(string name, Library library)
{
Collection collection = await GetMetadata(
provider => provider.GetCollectionFromName(name),
library,
$"the collection {name}");
collection.Name ??= name;
collection.Slug ??= Utility.ToSlug(name);
return collection;
}
public async Task<Show> CompleteShow(Show show, Library library)
{
return await GetMetadata(provider => provider.GetShowByID(show), library, $"the show {show.Title}");
}
public async Task<Show> SearchShow(string showName, bool isMovie, Library library)
{
Show show = await GetMetadata(async provider =>
{
Show searchResult = (await provider.SearchShows(showName, isMovie))?.FirstOrDefault();
if (searchResult == null)
return null;
return await provider.GetShowByID(searchResult);
}, library, $"the show {showName}");
show.Slug = Utility.ToSlug(showName);
show.Title ??= showName;
show.IsMovie = isMovie;
show.Genres = show.Genres?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList();
return show;
}
public async Task<IEnumerable<Show>> SearchShows(string showName, bool isMovie, Library library)
{
IEnumerable<Show> shows = await GetMetadata(
provider => provider.SearchShows(showName, isMovie),
library,
$"the show {showName}");
return shows.Select(show =>
{
show.Slug = Utility.ToSlug(showName);
show.Title ??= showName;
show.IsMovie = isMovie;
return show;
});
}
public async Task<Season> GetSeason(Show show, int seasonNumber, Library library)
{
Season season = await GetMetadata(
provider => provider.GetSeason(show, seasonNumber),
library,
$"the season {seasonNumber} of {show.Title}");
season.Show = show;
season.ShowID = show.ID;
season.ShowSlug = show.Slug;
season.Title ??= $"Season {season.SeasonNumber}";
return season;
}
public async Task<Episode> GetEpisode(Show show,
string episodePath,
int? seasonNumber,
int? episodeNumber,
int? absoluteNumber,
Library library)
{
Episode episode = await GetMetadata(
provider => provider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber),
library,
"an episode");
episode.Show = show;
episode.ShowID = show.ID;
episode.ShowSlug = show.Slug;
episode.Path = episodePath;
episode.SeasonNumber ??= seasonNumber;
episode.EpisodeNumber ??= episodeNumber;
episode.AbsoluteNumber ??= absoluteNumber;
return episode;
}
public async Task<ICollection<PeopleRole>> GetPeople(Show show, Library library)
{
List<PeopleRole> people = await GetMetadata(
provider => provider.GetPeople(show),
library,
$"a cast member of {show.Title}");
return people?.GroupBy(x => x.Slug)
.Select(x => x.First())
.Select(x =>
{
x.Show = show;
x.ShowID = show.ID;
return x;
}).ToList();
}
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Autofac;
using Autofac.Core;
using Autofac.Core.Registration;
using Kyoo.Controllers;
using Kyoo.Models.Options;
using Kyoo.Models.Permissions;
@ -34,7 +36,7 @@ namespace Kyoo
typeof(IFileManager),
typeof(ITranscoder),
typeof(IThumbnailsManager),
typeof(IProviderManager),
typeof(IMetadataProvider),
typeof(ITaskManager),
typeof(ILibraryManager)
};
@ -88,6 +90,39 @@ namespace Kyoo
_configuration = configuration;
}
/// <inheritdoc />
public void Configure(ContainerBuilder builder)
{
builder.RegisterType<ConfigurationManager>().As<IConfigurationManager>().SingleInstance();
builder.RegisterType<FileManager>().As<IFileManager>().SingleInstance();
builder.RegisterType<Transcoder>().As<ITranscoder>().SingleInstance();
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().SingleInstance();
builder.RegisterType<TaskManager>().As<ITaskManager>().SingleInstance();
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
builder.RegisterComposite<ProviderComposite, IMetadataProvider>().InstancePerLifetimeScope();
builder.RegisterTask<Crawler>();
static bool DatabaseIsPresent(IComponentRegistryBuilder x)
=> x.IsRegistered(new TypedService(typeof(DatabaseContext)));
builder.RegisterRepository<ILibraryRepository, LibraryRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<ICollectionRepository, CollectionRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IShowRepository, ShowRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<ISeasonRepository, SeasonRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IEpisodeRepository, EpisodeRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<ITrackRepository, TrackRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IPeopleRepository, PeopleRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IStudioRepository, StudioRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IGenreRepository, GenreRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IProviderRepository, ProviderRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterRepository<IUserRepository, UserRepository>().OnlyIf(DatabaseIsPresent);
builder.RegisterType<PassthroughPermissionValidator>().As<IPermissionValidator>()
.IfNotRegistered(typeof(IPermissionValidator));
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
{
@ -109,36 +144,7 @@ namespace Kyoo
x.SerializerSettings.Converters.Add(new PeopleRoleConverter());
});
services.AddSingleton<IConfigurationManager, ConfigurationManager>();
services.AddSingleton<IFileManager, FileManager>();
services.AddSingleton<ITranscoder, Transcoder>();
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
services.AddSingleton<IProviderManager, ProviderManager>();
services.AddSingleton<ITaskManager, TaskManager>();
services.AddHostedService(x => x.GetService<ITaskManager>() as TaskManager);
services.AddScoped<ILibraryManager, LibraryManager>();
if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes))
{
services.AddRepository<ILibraryRepository, LibraryRepository>();
services.AddRepository<ILibraryItemRepository, LibraryItemRepository>();
services.AddRepository<ICollectionRepository, CollectionRepository>();
services.AddRepository<IShowRepository, ShowRepository>();
services.AddRepository<ISeasonRepository, SeasonRepository>();
services.AddRepository<IEpisodeRepository, EpisodeRepository>();
services.AddRepository<ITrackRepository, TrackRepository>();
services.AddRepository<IPeopleRepository, PeopleRepository>();
services.AddRepository<IStudioRepository, StudioRepository>();
services.AddRepository<IGenreRepository, GenreRepository>();
services.AddRepository<IProviderRepository, ProviderRepository>();
services.AddRepository<IUserRepository, UserRepository>();
}
services.AddTask<Crawler>();
if (services.All(x => x.ServiceType != typeof(IPermissionValidator)))
services.AddSingleton<IPermissionValidator, PassthroughPermissionValidator>();
}
/// <inheritdoc />

View File

@ -34,6 +34,8 @@
<ItemGroup>
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" />
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" />
<PackageReference Include="Autofac" Version="6.2.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.17" />

View File

@ -2,12 +2,13 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Kyoo
{
/// <summary>
@ -30,6 +31,8 @@ namespace Kyoo
if (!File.Exists("./settings.json"))
File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "settings.json"), "settings.json");
IHostBuilder builder = CreateWebHostBuilder(args);
bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch
{
"d" => true,
@ -43,18 +46,21 @@ namespace Kyoo
};
if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null)
Console.WriteLine($"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring...");
{
Console.WriteLine(
$"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring...");
}
#if DEBUG
debug ??= true;
#endif
Console.WriteLine($"Running as {Environment.UserName}.");
IWebHostBuilder builder = CreateWebHostBuilder(args);
if (debug != null)
builder = builder.UseEnvironment(debug == true ? "Development" : "Production");
try
{
Console.WriteLine($"Running as {Environment.UserName}.");
await builder.Build().RunAsync();
}
catch (Exception ex)
@ -81,13 +87,14 @@ namespace Kyoo
/// </summary>
/// <param name="args">Command line parameters that can be handled by kestrel</param>
/// <returns>A new web host instance</returns>
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
private static IHostBuilder CreateWebHostBuilder(string[] args)
{
IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build();
return new WebHostBuilder()
return new HostBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.UseConfiguration(configuration)
.ConfigureAppConfiguration(x => SetupConfig(x, args))
.ConfigureLogging((context, builder) =>
{
@ -99,18 +106,20 @@ namespace Kyoo
.AddDebug()
.AddEventSourceLogger();
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
if (context.HostingEnvironment.IsDevelopment())
StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration);
})
// .UseDefaultServiceProvider((context, options) =>
// {
// options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
// if (context.HostingEnvironment.IsDevelopment())
// StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration);
// })
.ConfigureServices(x => x.AddRouting())
.UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS()
.UseIISIntegration()
.UseUrls(configuration.GetValue<string>("basics:url"))
.UseStartup<Startup>();
.ConfigureWebHost(x => x
.UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS()
.UseIISIntegration()
.UseUrls(configuration.GetValue<string>("basics:url"))
.UseStartup<Startup>()
);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using Autofac;
using Kyoo.Authentication;
using Kyoo.Controllers;
using Kyoo.Models;
@ -71,13 +72,17 @@ namespace Kyoo
services.AddHttpClient();
services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>));
services.AddSingleton(_plugins);
services.AddTask<PluginInitializer>();
// services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>));
_plugins.ConfigureServices(services);
}
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterInstance(_plugins).As<IPluginManager>().ExternallyOwned();
builder.RegisterTask<PluginInitializer>();
_plugins.ConfigureContainer(builder);
}
/// <summary>
/// Configure the asp net host.
/// </summary>

View File

@ -25,7 +25,7 @@ namespace Kyoo.Tasks
[Injected] public IServiceProvider ServiceProvider { private get; set; }
[Injected] public IThumbnailsManager ThumbnailsManager { private get; set; }
[Injected] public IProviderManager MetadataProvider { private get; set; }
[Injected] public IMetadataProvider MetadataProvider { private get; set; }
[Injected] public ITranscoder Transcoder { private get; set; }
[Injected] public IConfiguration Config { private get; set; }
@ -258,7 +258,7 @@ namespace Kyoo.Tasks
Collection collection = await libraryManager.GetOrDefault<Collection>(Utility.ToSlug(collectionName));
if (collection != null)
return collection;
collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
// collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
try
{
@ -283,9 +283,10 @@ namespace Kyoo.Tasks
await libraryManager.Load(old, x => x.ExternalIDs);
return old;
}
Show show = await MetadataProvider.SearchShow(showTitle, isMovie, library);
Show show = new();//await MetadataProvider.SearchShow(showTitle, isMovie, library);
show.Path = showPath;
show.People = await MetadataProvider.GetPeople(show, library);
// show.People = await MetadataProvider.GetPeople(show, library);
try
{
@ -325,7 +326,7 @@ namespace Kyoo.Tasks
}
catch (ItemNotFoundException)
{
Season season = await MetadataProvider.GetSeason(show, seasonNumber, library);
Season season = new();//await MetadataProvider.GetSeason(show, seasonNumber, library);
try
{
await libraryManager.Create(season);
@ -348,12 +349,12 @@ namespace Kyoo.Tasks
string episodePath,
Library library)
{
Episode episode = await MetadataProvider.GetEpisode(show,
Episode episode = new();/*await MetadataProvider.GetEpisode(show,
episodePath,
season?.SeasonNumber,
episodeNumber,
absoluteNumber,
library);
library);*/
if (episode.SeasonNumber != null)
{