From edc5def19ac990ec62c584b5139d9df9bb5c2061 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 19 Aug 2019 01:34:01 +0200 Subject: [PATCH] Adding thumbnails management (downloading, checking local versions...) Finishing the browse/library tab of the web app. --- .../src/app/browse/browse.component.html | 6 +- .../src/app/browse/browse.component.scss | 40 +++++++--- .../src/app/browse/browse.component.ts | 8 +- .../show-details/show-details.component.html | 3 +- Kyoo/Controllers/ThumbnailController.cs | 25 ++++++ Kyoo/InternalAPI/Crawler/Crawler.cs | 5 +- .../MetadataProvider/IMetadataProvider.cs | 2 +- .../TheTvDB/ProviderTheTvDB.cs | 2 +- .../MetadataProvider/ProviderManager.cs | 78 +++++++++++++++---- .../ThumbnailsManager/IThumbnailsManager.cs | 10 +++ .../ThumbnailsManager/ThumbnailsManager.cs | 40 ++++++++++ Kyoo/Startup.cs | 6 ++ 12 files changed, 191 insertions(+), 34 deletions(-) create mode 100644 Kyoo/Controllers/ThumbnailController.cs create mode 100644 Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs create mode 100644 Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.html b/Kyoo/ClientApp/src/app/browse/browse.component.html index f05a7a86..a6381383 100644 --- a/Kyoo/ClientApp/src/app/browse/browse.component.html +++ b/Kyoo/ClientApp/src/app/browse/browse.component.html @@ -1,7 +1,7 @@ -
+
- -

{{show.title}}

+ +

{{show.title}}

{{show.startYear}} - {{show.endYear}}

{{show.startYear}}

diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.scss b/Kyoo/ClientApp/src/app/browse/browse.component.scss index ab3912b0..896b5959 100644 --- a/Kyoo/ClientApp/src/app/browse/browse.component.scss +++ b/Kyoo/ClientApp/src/app/browse/browse.component.scss @@ -2,14 +2,20 @@ @import "~bootstrap/scss/variables"; @import "~bootstrap/scss//mixins/breakpoints"; +.container +{ + display: flex; + flex-wrap: wrap; +} + .show { width: 33%; list-style: none; padding: 1em; - display: inline-block; text-decoration: none; color: inherit; + outline: none; @include media-breakpoint-up(md) { @@ -26,10 +32,26 @@ width: 18%; } + &:focus, &:hover + { + > img + { + outline: solid var(--accentColor); + } + + > .title + { + text-decoration: underline; + } + } + > img { - max-width: 100%; - max-height: 100%; + width: 100%; + height: 0; + padding-top: 147.0588%; + background-size: cover; + background-color: #333333; } > p @@ -39,6 +61,12 @@ text-overflow: ellipsis; text-align: center; margin-bottom: 0px; + + &.date + { + opacity: 0.8; + font-size: 0.8em; + } } &:hover @@ -46,9 +74,3 @@ cursor: pointer; } } - -.date -{ - opacity: 0.8; - font-size: 0.8em; -} diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.ts b/Kyoo/ClientApp/src/app/browse/browse.component.ts index ec67ea62..d51dc9b7 100644 --- a/Kyoo/ClientApp/src/app/browse/browse.component.ts +++ b/Kyoo/ClientApp/src/app/browse/browse.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { HttpClient } from '@angular/common/http'; +import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; @Component({ selector: 'app-browse', @@ -13,7 +14,7 @@ export class BrowseComponent implements OnInit private watch: any; - constructor(private http: HttpClient, private route: ActivatedRoute) { } + constructor(private http: HttpClient, private route: ActivatedRoute, private sanitizer: DomSanitizer) {} ngOnInit() { @@ -42,4 +43,9 @@ export class BrowseComponent implements OnInit { this.watch.unsubscribe(); } + + getThumb(slug: string) + { + return this.sanitizer.bypassSecurityTrustStyle("url(/thumb/" + slug + ")"); + } } diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.html b/Kyoo/ClientApp/src/app/show-details/show-details.component.html index 7dd1e78c..bb864159 100644 --- a/Kyoo/ClientApp/src/app/show-details/show-details.component.html +++ b/Kyoo/ClientApp/src/app/show-details/show-details.component.html @@ -1 +1,2 @@ -

Should display show details of: {{this.show.title}}

+

Should display show details of: {{this.show.title}}

+Loading diff --git a/Kyoo/Controllers/ThumbnailController.cs b/Kyoo/Controllers/ThumbnailController.cs new file mode 100644 index 00000000..90d0e89f --- /dev/null +++ b/Kyoo/Controllers/ThumbnailController.cs @@ -0,0 +1,25 @@ +using Kyoo.InternalAPI; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.FileProviders; + +// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace Kyoo.Controllers +{ + public class ThumbnailController : Controller + { + private ILibraryManager libraryManager; + + public ThumbnailController(ILibraryManager libraryManager) + { + this.libraryManager = libraryManager; + } + + [HttpGet("thumb/{showSlug}")] + public IActionResult GetShowThumb(string showSlug) + { + string thumbPath = libraryManager.GetShowBySlug(showSlug).ImgPrimary; + return new PhysicalFileResult(thumbPath, "image/jpg"); + } + } +} diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index dd9aaa56..961daa01 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -42,7 +42,7 @@ namespace Kyoo.InternalAPI public async void Scan(string folderPath) { - string[] files = Directory.GetFiles(folderPath); + string[] files = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories); foreach (string file in files) { @@ -133,10 +133,9 @@ namespace Kyoo.InternalAPI seasonID = libraryManager.RegisterSeason(season); } - Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber); + Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber, path); episode.ShowID = showID; episode.SeasonID = seasonID; - episode.Path = path; libraryManager.RegisterEpisode(episode); } } diff --git a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs index 26e0cb37..dcdb6325 100644 --- a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs +++ b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs @@ -15,6 +15,6 @@ namespace Kyoo.InternalAPI Task GetSeasonImage(string showName, long seasonNumber); //For the episodes - Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber); + Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath); } } diff --git a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs index 16d0bfb2..03076474 100644 --- a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs +++ b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs @@ -269,7 +269,7 @@ namespace Kyoo.InternalAPI.MetadataProvider return null; } - public async Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber) + public async Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath) { string id = GetID(externalIDs); diff --git a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs index fe43254c..f8fb365b 100644 --- a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs +++ b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs @@ -1,9 +1,11 @@ using Kyoo.InternalAPI.MetadataProvider; +using Kyoo.InternalAPI.ThumbnailsManager; using Kyoo.Models; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -12,10 +14,12 @@ namespace Kyoo.InternalAPI public class ProviderManager : IMetadataProvider { private readonly List providers = new List(); + private readonly IThumbnailsManager thumbnailsManager; private readonly IConfiguration config; - public ProviderManager(IConfiguration configuration) + public ProviderManager(IThumbnailsManager thumbnailsManager, IConfiguration configuration) { + this.thumbnailsManager = thumbnailsManager; config = configuration; LoadProviders(); } @@ -58,12 +62,21 @@ namespace Kyoo.InternalAPI } } - //public Show MergeShows(Show baseShow, Show newShow) - //{ + public Show Merge(IEnumerable shows) + { + return shows.FirstOrDefault(); + } - //} + public Season Merge(IEnumerable seasons) + { + return seasons.FirstOrDefault(); + } + + public Episode Merge(IEnumerable episodes) + { + return episodes.FirstOrDefault(); + } - //For all the following methods, it should use all providers and merge the data. public Task GetImages(Show show) @@ -71,34 +84,69 @@ namespace Kyoo.InternalAPI return providers[0].GetImages(show); } - public Task GetSeason(string showName, int seasonNumber) + public async Task GetSeason(string showName, int seasonNumber) { - return providers[0].GetSeason(showName, seasonNumber); + List datas = new List(); + for (int i = 0; i < providers.Count; i++) + { + datas.Add(await providers[i].GetSeason(showName, seasonNumber)); + } + + return Merge(datas); } - public Task GetShowByID(string id) + public async Task GetShowByID(string id) { - return providers[0].GetShowByID(id); + List datas = new List(); + for (int i = 0; i < providers.Count; i++) + { + datas.Add(await providers[i].GetShowByID(id)); + } + + return Merge(datas); } - public Task GetShowFromName(string showName, string showPath) + public async Task GetShowFromName(string showName, string showPath) { - return providers[0].GetShowFromName(showName, showPath); + List datas = new List(); + for (int i = 0; i < providers.Count; i++) + { + datas.Add(await providers[i].GetShowFromName(showName, showPath)); + } + + Show show = Merge(datas); + return thumbnailsManager.Validate(show); } - public Task GetSeason(string showName, long seasonNumber) + public async Task GetSeason(string showName, long seasonNumber) { - return providers[0].GetSeason(showName, seasonNumber); + List datas = new List(); + for (int i = 0; i < providers.Count; i++) + { + datas.Add(await providers[i].GetSeason(showName, seasonNumber)); + } + + return Merge(datas); } public Task GetSeasonImage(string showName, long seasonNumber) { + //Should select the best provider for this show. + return providers[0].GetSeasonImage(showName, seasonNumber); } - public Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber) + public async Task GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath) { - return providers[0].GetEpisode(externalIDs, seasonNumber, episodeNumber); + List datas = new List(); + for (int i = 0; i < providers.Count; i++) + { + datas.Add(await providers[i].GetEpisode(externalIDs, seasonNumber, episodeNumber, episodePath)); + } + + Episode episode = Merge(datas); + episode.Path = episodePath; + return thumbnailsManager.Validate(episode); } } } diff --git a/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs b/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs new file mode 100644 index 00000000..0933994f --- /dev/null +++ b/Kyoo/InternalAPI/ThumbnailsManager/IThumbnailsManager.cs @@ -0,0 +1,10 @@ +using Kyoo.Models; + +namespace Kyoo.InternalAPI.ThumbnailsManager +{ + public interface IThumbnailsManager + { + Show Validate(Show show); + Episode Validate(Episode episode); + } +} diff --git a/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs new file mode 100644 index 00000000..4600c91e --- /dev/null +++ b/Kyoo/InternalAPI/ThumbnailsManager/ThumbnailsManager.cs @@ -0,0 +1,40 @@ +using Kyoo.Models; +using System; +using System.IO; +using System.Net; + +namespace Kyoo.InternalAPI.ThumbnailsManager +{ + public class ThumbnailsManager : IThumbnailsManager + { + public Show Validate(Show show) + { + string localThumb = Path.Combine(show.Path, "poster.jpg"); + if (!File.Exists(localThumb)) + { + using (WebClient client = new WebClient()) + { + client.DownloadFileAsync(new Uri(show.ImgPrimary), localThumb); + } + } + + show.ImgPrimary = localThumb; + return show; + } + + public Episode Validate(Episode episode) + { + string localThumb = Path.ChangeExtension(episode.Path, "jpg"); + if (!File.Exists(localThumb)) + { + using (WebClient client = new WebClient()) + { + client.DownloadFileAsync(new Uri(episode.ImgPrimary), localThumb); + } + } + + episode.ImgPrimary = localThumb; + return episode; + } + } +} diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index a84a9ea7..cbbeb1b8 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,5 +1,6 @@ using Kyoo.InternalAPI; using Kyoo.InternalAPI.MetadataProvider; +using Kyoo.InternalAPI.ThumbnailsManager; using Kyoo.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -7,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SpaServices.AngularCli; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using System.Diagnostics; using System.Web.Http; @@ -32,8 +34,12 @@ namespace Kyoo configuration.RootPath = "ClientApp/dist"; }); + //Services needed in the private and in the public API services.AddSingleton(); + + //Services used to get informations about files and register them services.AddHostedService(); + services.AddSingleton(); services.AddSingleton(); }