From 0c4cab48d7bdd74f073cc98fc605f0e6047b6e21 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 14 Jul 2021 00:04:55 +0200 Subject: [PATCH] Adding thetvdb, using autofac --- Kyoo.Common/Controllers/IMetadataProvider.cs | 51 ++++- Kyoo.Common/Controllers/IPlugin.cs | 28 ++- Kyoo.Common/Controllers/IPluginManager.cs | 9 +- Kyoo.Common/Controllers/IProviderManager.cs | 17 -- Kyoo.Common/Kyoo.Common.csproj | 1 + Kyoo.Common/Module.cs | 57 +++--- Kyoo.TheTvdb/Kyoo.TheTvdb.csproj | 33 +++ Kyoo.TheTvdb/PluginTVDB.cs | 41 ++++ Kyoo.TheTvdb/ProviderTVDB.cs | 203 +++++++++++++++++++ Kyoo.sln | 6 + Kyoo/Controllers/PluginManager.cs | 7 + Kyoo/Controllers/ProviderComposite.cs | 182 +++++++++++++++++ Kyoo/Controllers/ProviderManager.cs | 166 --------------- Kyoo/CoreModule.cs | 68 ++++--- Kyoo/Kyoo.csproj | 2 + Kyoo/Program.cs | 45 ++-- Kyoo/Startup.cs | 13 +- Kyoo/Tasks/Crawler.cs | 15 +- 18 files changed, 664 insertions(+), 280 deletions(-) delete mode 100644 Kyoo.Common/Controllers/IProviderManager.cs create mode 100644 Kyoo.TheTvdb/Kyoo.TheTvdb.csproj create mode 100644 Kyoo.TheTvdb/PluginTVDB.cs create mode 100644 Kyoo.TheTvdb/ProviderTVDB.cs create mode 100644 Kyoo/Controllers/ProviderComposite.cs delete mode 100644 Kyoo/Controllers/ProviderManager.cs diff --git a/Kyoo.Common/Controllers/IMetadataProvider.cs b/Kyoo.Common/Controllers/IMetadataProvider.cs index a0c30cbb..9c260030 100644 --- a/Kyoo.Common/Controllers/IMetadataProvider.cs +++ b/Kyoo.Common/Controllers/IMetadataProvider.cs @@ -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 { + /// + /// An interface to automatically retrieve metadata from external providers. + /// public interface IMetadataProvider { + /// + /// The corresponding to this provider. + /// This allow to map metadata to a provider, keep metadata links and + /// know witch is used for a specific . + /// Provider Provider { get; } - Task GetCollectionFromName(string name); + /// + /// Return a new item with metadata from your provider. + /// + /// + /// 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. + /// + /// + /// You must not use metadata from the given . + /// Merging metadata is the job of Kyoo, a complex is given + /// to make a precise search and give you every available properties, not to discard properties. + /// + /// + /// If this metadata provider does not support . + /// + /// A new containing metadata from your provider + [ItemNotNull] + Task Get([NotNull] T item) + where T : class, IResource; - Task GetShowByID(Show show); - Task> SearchShows(string showName, bool isMovie); + /// + /// Search for a specific type of items with a given query. + /// + /// The search query to use. + /// + /// If this metadata provider does not support . + /// + /// The list of items that could be found on this specific provider. + [ItemNotNull] + Task> Search(string query) + where T : class, IResource; + Task> GetPeople(Show show); - - Task GetSeason(Show show, int seasonNumber); - - Task GetEpisode(Show show, int? seasonNumber, int? episodeNumber, int? absoluteNumber); } } diff --git a/Kyoo.Common/Controllers/IPlugin.cs b/Kyoo.Common/Controllers/IPlugin.cs index 3201df83..83cc70a4 100644 --- a/Kyoo.Common/Controllers/IPlugin.cs +++ b/Kyoo.Common/Controllers/IPlugin.cs @@ -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 /// ICollection Requires { get; } + /// + /// A configure method that will be run on plugin's startup. + /// + /// The autofac service container to register services. + void Configure(ContainerBuilder builder) + { + // Skipped + } + /// /// A configure method that will be run on plugin's startup. /// @@ -64,21 +74,31 @@ namespace Kyoo.Controllers /// or > /// You can't simply check on the service collection because some dependencies might be registered after your plugin. /// - void Configure(IServiceCollection services, ICollection availableTypes); + void Configure(IServiceCollection services, ICollection availableTypes) + { + // Skipped + } + /// /// 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. /// /// The Asp.Net application builder. On most case it is not needed but you can use it to add asp net functionalities. - void ConfigureAspNet(IApplicationBuilder app) {} - + void ConfigureAspNet(IApplicationBuilder app) + { + // Skipped + } + /// /// An optional function to execute and initialize your plugin. /// It can be used to initialize a database connection, fill initial data or anything. /// /// A service provider to request services - void Initialize(IServiceProvider provider) {} + void Initialize(IServiceProvider provider) + { + // Skipped + } } /// diff --git a/Kyoo.Common/Controllers/IPluginManager.cs b/Kyoo.Common/Controllers/IPluginManager.cs index bd4ef513..04d308f3 100644 --- a/Kyoo.Common/Controllers/IPluginManager.cs +++ b/Kyoo.Common/Controllers/IPluginManager.cs @@ -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 plugins); /// - /// Configure services adding or removing services as the plugins wants. + /// Configure container adding or removing services as the plugins wants. + /// + /// The container to populate + void ConfigureContainer(ContainerBuilder builder); + + /// + /// Configure services via the microsoft way. This allow libraries to add their services. /// /// The service collection to populate public void ConfigureServices(IServiceCollection services); diff --git a/Kyoo.Common/Controllers/IProviderManager.cs b/Kyoo.Common/Controllers/IProviderManager.cs deleted file mode 100644 index dd83a283..00000000 --- a/Kyoo.Common/Controllers/IProviderManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Kyoo.Models; - -namespace Kyoo.Controllers -{ - public interface IProviderManager - { - Task GetCollectionFromName(string name, Library library); - Task CompleteShow(Show show, Library library); - Task SearchShow(string showName, bool isMovie, Library library); - Task> SearchShows(string showName, bool isMovie, Library library); - Task GetSeason(Show show, int seasonNumber, Library library); - Task GetEpisode(Show show, string episodePath, int? seasonNumber, int? episodeNumber, int? absoluteNumber, Library library); - Task> GetPeople(Show show, Library library); - } -} \ No newline at end of file diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 349ef6a0..ada2ed60 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -21,6 +21,7 @@ + diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index a8a81b88..d4442d98 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -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 /// /// Register a new task to the container. /// - /// The container + /// The container /// The type of the task - /// The initial container. - public static IServiceCollection AddTask(this IServiceCollection services) + /// The registration builder of this new task. That can be used to edit the registration. + public static IRegistrationBuilder + RegisterTask(this ContainerBuilder builder) where T : class, ITask { - services.AddSingleton(); - return services; + return builder.RegisterType().As().SingleInstance(); + } + + /// + /// Register a new metadata provider to the container. + /// + /// The container + /// The type of the task + /// The registration builder of this new provider. That can be used to edit the registration. + public static IRegistrationBuilder + RegisterProvider(this ContainerBuilder builder) + where T : class, IMetadataProvider + { + return builder.RegisterType().As().InstancePerLifetimeScope(); } /// /// Register a new repository to the container. /// - /// The container - /// The lifetime of the repository. The default is scoped. + /// The container /// The type of the repository. /// - /// If your repository implements a special interface, please use + /// If your repository implements a special interface, please use /// /// The initial container. - public static IServiceCollection AddRepository(this IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Scoped) + public static IRegistrationBuilder + RegisterRepository(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() + .As() + .As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>))) + .InstancePerLifetimeScope(); } /// /// Register a new repository with a custom mapping to the container. /// - /// - /// The lifetime of the repository. The default is scoped. + /// The container /// The custom mapping you have for your repository. /// The type of the repository. /// - /// If your repository does not implements a special interface, please use + /// If your repository does not implements a special interface, please use /// /// The initial container. - public static IServiceCollection AddRepository(this IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Scoped) + public static IRegistrationBuilder + RegisterRepository(this ContainerBuilder builder) where T2 : IBaseRepository, T { - services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime)); - return services.AddRepository(lifetime); + return builder.RegisterRepository().As(); } /// diff --git a/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj b/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj new file mode 100644 index 00000000..1b6aee8f --- /dev/null +++ b/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj @@ -0,0 +1,33 @@ + + + net5.0 + + SDG + Zoe Roux + https://github.com/AnonymusRaccoon/Kyoo + default + Kyoo.TheTvdb + + + + ../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-tvdb + false + false + false + false + true + + + + + + + + + + all + false + runtime + + + diff --git a/Kyoo.TheTvdb/PluginTVDB.cs b/Kyoo.TheTvdb/PluginTVDB.cs new file mode 100644 index 00000000..6484b51d --- /dev/null +++ b/Kyoo.TheTvdb/PluginTVDB.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Kyoo.Controllers; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.TheTvdb +{ + /// + /// A plugin that add a for The TVDB. + /// + public class PluginTvdb : IPlugin + { + /// + public string Slug => "the-tvdb"; + + /// + public string Name => "The TVDB Provider"; + + /// + public string Description => "A metadata provider for The TVDB."; + + /// + public ICollection Provides => new [] + { + typeof(IMetadataProvider) + }; + + /// + public ICollection ConditionalProvides => ArraySegment.Empty; + + /// + public ICollection Requires => ArraySegment.Empty; + + + /// + public void Configure(IServiceCollection services, ICollection availableTypes) + { + // services.AddProvider(); + } + } +} \ No newline at end of file diff --git a/Kyoo.TheTvdb/ProviderTVDB.cs b/Kyoo.TheTvdb/ProviderTVDB.cs new file mode 100644 index 00000000..eeb065c9 --- /dev/null +++ b/Kyoo.TheTvdb/ProviderTVDB.cs @@ -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 +{ + /// + /// A metadata provider for The TVDB. + /// + public class ProviderTvdb : IMetadataProvider + { + public Provider Provider { get; } + public Task Get(T item) where T : class, IResource + { + throw new NotImplementedException(); + } + + public Task> Search(string query) where T : class, IResource + { + throw new NotImplementedException(); + } + + public Task> 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 GetCollectionFromName(string name) + // { + // return Task.FromResult(null); + // } + // + // public async Task> SearchShows(string showName, bool isMovie) + // { + // await Authentificate(); + // + // if (isMovie) + // return null; //There is no movie search API for now on TheTVDB. + // TvDbResponse shows = await _client.Search.SearchSeriesAsync(showName, SearchParameter.Name); + // return shows.Data.Select(x => x.ToShow(Provider)).ToArray(); + // } + // + // public async Task GetShowByID(Show show) + // { + // if (!int.TryParse(show?.GetID(Provider.Name), out int id)) + // return await Task.FromResult(null); + // await Authentificate(); + // TvDbResponse serie = await _client.Series.GetAsync(id); + // return serie.Data.ToShow(Provider); + // } + // + // public async Task> GetPeople(Show show) + // { + // if (!int.TryParse(show?.GetID(Provider.Name), out int id)) + // return null; + // await Authentificate(); + // TvDbResponse people = await _client.Series.GetActorsAsync(id); + // return people.Data.Select(x => x.ToPeopleRole(Provider)).ToArray(); + // } + // + // public Task GetSeason(Show show, int seasonNumber) + // { + // return Task.FromResult(null); + // } + // + // public async Task GetEpisode(Show show, int seasonNumber, int episodeNumber, int absoluteNumber) + // { + // if (!int.TryParse(show?.GetID(Provider.Name), out int id)) + // return null; + // await Authentificate(); + // TvDbResponse 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}") + // }); + // } + // } +} \ No newline at end of file diff --git a/Kyoo.sln b/Kyoo.sln index 60998b55..55aeb44c 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -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 diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs index 7d0c399b..cd0a38f6 100644 --- a/Kyoo/Controllers/PluginManager.cs +++ b/Kyoo/Controllers/PluginManager.cs @@ -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; @@ -126,6 +127,12 @@ namespace Kyoo.Controllers else _logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name)); } + + public void ConfigureContainer(ContainerBuilder builder) + { + foreach (IPlugin plugin in _plugins) + plugin.Configure(builder); + } /// public void ConfigureServices(IServiceCollection services) diff --git a/Kyoo/Controllers/ProviderComposite.cs b/Kyoo/Controllers/ProviderComposite.cs new file mode 100644 index 00000000..9b7e498b --- /dev/null +++ b/Kyoo/Controllers/ProviderComposite.cs @@ -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 _providers; + + + public ProviderComposite(IEnumerable providers) + { + _providers = providers; + } + + public Provider Provider { get; } + public Task Get(T item) where T : class, IResource + { + throw new NotImplementedException(); + } + + public Task> Search(string query) where T : class, IResource + { + throw new NotImplementedException(); + } + + public Task> GetPeople(Show show) + { + throw new NotImplementedException(); + } + + // private async Task GetMetadata(Func> providerCall, Library library, string what) + // where T : new() + // { + // T ret = new(); + // + // IEnumerable 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> GetMetadata( + // Func>> providerCall, + // Library library, + // string what) + // { + // List ret = new(); + // + // IEnumerable 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()); + // } 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 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 CompleteShow(Show show, Library library) + // { + // return await GetMetadata(provider => provider.GetShowByID(show), library, $"the show {show.Title}"); + // } + // + // public async Task 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> SearchShows(string showName, bool isMovie, Library library) + // { + // IEnumerable 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 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 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> GetPeople(Show show, Library library) + // { + // List 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(); + // } + } +} diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs deleted file mode 100644 index 6ed5f796..00000000 --- a/Kyoo/Controllers/ProviderManager.cs +++ /dev/null @@ -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 _providers; - - public ProviderManager(IPluginManager pluginManager) - { - _providers = pluginManager.GetPlugins(); - } - - private async Task GetMetadata(Func> providerCall, Library library, string what) - where T : new() - { - T ret = new(); - - IEnumerable 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> GetMetadata( - Func>> providerCall, - Library library, - string what) - { - List ret = new(); - - IEnumerable 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()); - } 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 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 CompleteShow(Show show, Library library) - { - return await GetMetadata(provider => provider.GetShowByID(show), library, $"the show {show.Title}"); - } - - public async Task 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> SearchShows(string showName, bool isMovie, Library library) - { - IEnumerable 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 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 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> GetPeople(Show show, Library library) - { - List 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(); - } - } -} diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 34e24e75..a0dc0729 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -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; } + /// + public void Configure(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterComposite().InstancePerLifetimeScope(); + + builder.RegisterTask(); + + static bool DatabaseIsPresent(IComponentRegistryBuilder x) + => x.IsRegistered(new TypedService(typeof(DatabaseContext))); + + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + builder.RegisterRepository().OnlyIf(DatabaseIsPresent); + + builder.RegisterType().As() + .IfNotRegistered(typeof(IPermissionValidator)); + } + /// public void Configure(IServiceCollection services, ICollection availableTypes) { @@ -109,36 +144,7 @@ namespace Kyoo x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); }); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); services.AddHostedService(x => x.GetService() as TaskManager); - - services.AddScoped(); - - if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes)) - { - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - services.AddRepository(); - } - - services.AddTask(); - - if (services.All(x => x.ServiceType != typeof(IPermissionValidator))) - services.AddSingleton(); } /// diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 438f3e9b..b1bad964 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -34,6 +34,8 @@ + + diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 12227266..44b80849 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -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 { /// @@ -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 /// /// Command line parameters that can be handled by kestrel /// A new web host instance - 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("basics:url")) - .UseStartup(); + .ConfigureWebHost(x => x + .UseKestrel(options => { options.AddServerHeader = false; }) + .UseIIS() + .UseIISIntegration() + .UseUrls(configuration.GetValue("basics:url")) + .UseStartup() + ); } } } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 4d5995ef..5557e872 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Autofac; using Kyoo.Authentication; using Kyoo.Controllers; using Kyoo.Models; @@ -71,12 +72,16 @@ namespace Kyoo services.AddHttpClient(); - services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>)); - - services.AddSingleton(_plugins); - services.AddTask(); + // services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>)); _plugins.ConfigureServices(services); } + + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterInstance(_plugins).As().ExternallyOwned(); + builder.RegisterTask(); + _plugins.ConfigureContainer(builder); + } /// /// Configure the asp net host. diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 9f1185dd..e9d99010 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -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(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) {