From 5940ccfc9d4f7bed235f08e75f2e3ae5ae03cefc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Oct 2019 00:13:26 +0200 Subject: [PATCH] Adding collection support, only need to make the component for the visual interface. --- .../src/app/browse/browse.component.html | 2 +- .../src/app/browse/browse.component.ts | 8 ++ Kyoo/ClientApp/src/models/show.ts | 2 +- Kyoo/InternalAPI/Crawler/Crawler.cs | 24 +++- .../LibraryManager/ILibraryManager.cs | 3 + .../LibraryManager/LibraryManager.cs | 53 +++++++-- .../MetadataProvider/IMetadataProvider.cs | 3 + .../Implementations/TheTvDB/HelperTvDB.cs | 2 - .../TheTvDB/ProviderTheTvDB.cs | 108 ++++-------------- .../MetadataProvider/ProviderHelper.cs | 31 +---- .../MetadataProvider/ProviderManager.cs | 5 + Kyoo/InternalAPI/Utility/Slugifier.cs | 34 ++++++ Kyoo/Models/Collection.cs | 5 + Kyoo/Models/Show.cs | 22 +++- Kyoo/appsettings.json | 2 +- 15 files changed, 170 insertions(+), 134 deletions(-) create mode 100644 Kyoo/InternalAPI/Utility/Slugifier.cs diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.html b/Kyoo/ClientApp/src/app/browse/browse.component.html index 4b75ca34..9463805f 100644 --- a/Kyoo/ClientApp/src/app/browse/browse.component.html +++ b/Kyoo/ClientApp/src/app/browse/browse.component.html @@ -21,7 +21,7 @@
- +

{{show.title}}

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

diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.ts b/Kyoo/ClientApp/src/app/browse/browse.component.ts index 3399b521..dc88b664 100644 --- a/Kyoo/ClientApp/src/app/browse/browse.component.ts +++ b/Kyoo/ClientApp/src/app/browse/browse.component.ts @@ -28,6 +28,14 @@ export class BrowseComponent implements OnInit return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")"); } + getLink(show: Show) + { + if (show.isCollection) + return "/collection/" + show.slug; + else + return "/show/" + show.slug; + } + sort(type: string, order: boolean) { this.sortType = type; diff --git a/Kyoo/ClientApp/src/models/show.ts b/Kyoo/ClientApp/src/models/show.ts index be30cdb5..65748657 100644 --- a/Kyoo/ClientApp/src/models/show.ts +++ b/Kyoo/ClientApp/src/models/show.ts @@ -16,10 +16,10 @@ export interface Show people: People[]; seasons: Season[]; trailerUrl: string; + isCollection: boolean; startYear: number; endYear : number; externalIDs: string; - } diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index 673aadf9..d6b035f3 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -1,4 +1,5 @@ -using Kyoo.Models; +using Kyoo.InternalAPI.Utility; +using Kyoo.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System.Collections.Generic; @@ -15,17 +16,17 @@ namespace Kyoo.InternalAPI { private readonly CancellationTokenSource cancellation; - private readonly IConfiguration config; private readonly ILibraryManager libraryManager; private readonly IMetadataProvider metadataProvider; private readonly ITranscoder transcoder; + private readonly IConfiguration config; - public Crawler(IConfiguration configuration, ILibraryManager libraryManager, IMetadataProvider metadataProvider, ITranscoder transcoder) + public Crawler(ILibraryManager libraryManager, IMetadataProvider metadataProvider, ITranscoder transcoder, IConfiguration configuration) { - config = configuration; this.libraryManager = libraryManager; this.metadataProvider = metadataProvider; this.transcoder = transcoder; + config = configuration; cancellation = new CancellationTokenSource(); } @@ -129,12 +130,13 @@ namespace Kyoo.InternalAPI Match match = regex.Match(path); string showPath = Path.GetDirectoryName(path); + string collectionName = match.Groups["Collection"]?.Value; string showName = match.Groups["ShowTitle"].Value; bool seasonSuccess = long.TryParse(match.Groups["Season"].Value, out long seasonNumber); bool episodeSucess = long.TryParse(match.Groups["Episode"].Value, out long episodeNumber); long absoluteNumber = -1; - if(!seasonSuccess || !episodeSucess) + if (!seasonSuccess || !episodeSucess) { //Considering that the episode is using absolute path. seasonNumber = -1; @@ -160,6 +162,16 @@ namespace Kyoo.InternalAPI showProviderIDs = show.ExternalIDs; showID = libraryManager.RegisterShow(show); + if (collectionName != null) + { + if (!libraryManager.IsCollectionRegistered(Slugifier.ToSlug(collectionName), out long collectionID)) + { + Collection collection = await metadataProvider.GetCollectionFromName(collectionName); + collectionID = libraryManager.RegisterCollection(collection); + } + libraryManager.AddShowToCollection(showID, collectionID); + } + List actors = await metadataProvider.GetPeople(show.ExternalIDs); libraryManager.RegisterShowPeople(showID, actors); } @@ -180,7 +192,7 @@ namespace Kyoo.InternalAPI Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber, absoluteNumber, path); episode.ShowID = showID; - if(seasonID == -1) + if (seasonID == -1) { if (!libraryManager.IsSeasonRegistered(showID, episode.seasonNumber, out seasonID)) { diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs index 27957073..fe7b2045 100644 --- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs @@ -35,6 +35,8 @@ namespace Kyoo.InternalAPI Collection GetCollection(string slug); //Check if value exists + bool IsCollectionRegistered(string collectionSlug); + bool IsCollectionRegistered(string collectionSlug, out long collectionID); bool IsShowRegistered(string showPath); bool IsShowRegistered(string showPath, out long showID); bool IsSeasonRegistered(long showID, long seasonNumber); @@ -52,6 +54,7 @@ namespace Kyoo.InternalAPI long GetOrCreateStudio(Studio studio); void RegisterShowPeople(long showID, List actors); + void AddShowToCollection(long showID, long collectionID); void ClearSubtitles(long episodeID); } diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index 43176301..04fbf86b 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -102,6 +102,8 @@ namespace Kyoo.InternalAPI slug TEXT UNIQUE, name TEXT, overview TEXT, + starYear INTEGER, + endYear INTEGER, imgPrimary TEXT ); CREATE TABLE collectionsLinks( @@ -253,19 +255,17 @@ namespace Kyoo.InternalAPI public IEnumerable QueryShows(string selection) { - string query = "SELECT * FROM shows ORDER BY title;"; + List shows = new List(); + SQLiteDataReader reader; + string query = "SELECT slug, title, startYear, endYear, '0' FROM shows LEFT JOIN collectionsLinks l ON l.showID = shows.id WHERE l.showID IS NULL UNION SELECT slug, name, startYear, endYear, '1' FROM collections ORDER BY title;"; using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) { - SQLiteDataReader reader = cmd.ExecuteReader(); - - List shows = new List(); - + reader = cmd.ExecuteReader(); while (reader.Read()) - shows.Add(Show.FromReader(reader)); - - return shows; + shows.Add(Show.FromQueryReader(reader)); } + return shows; } public Show GetShowBySlug(string slug) @@ -564,6 +564,31 @@ namespace Kyoo.InternalAPI #endregion #region Check if items exists + public bool IsCollectionRegistered(string collectionSlug) + { + string query = "SELECT (id) FROM collections WHERE slug = $slug;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$slug", collectionSlug); + + return cmd.ExecuteScalar() != null; + } + } + + public bool IsCollectionRegistered(string collectionSlug, out long collectionID) + { + string query = "SELECT (id) FROM collections WHERE slug = $slug;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$slug", collectionSlug); + collectionID = cmd.ExecuteScalar() as long? ?? -1; + + return collectionID != -1; + } + } + public bool IsShowRegistered(string showPath) { string query = "SELECT (id) FROM shows WHERE path = $path;"; @@ -831,6 +856,18 @@ namespace Kyoo.InternalAPI } } + public void AddShowToCollection(long showID, long collectionID) + { + string linkQuery = "INSERT INTO collectionsLinks (collectionID, showID) VALUES($collectionID, $showID);"; + + using (SQLiteCommand cmd = new SQLiteCommand(linkQuery, sqlConnection)) + { + cmd.Parameters.AddWithValue("$collectionID", collectionID); + cmd.Parameters.AddWithValue("$showID", showID); + cmd.ExecuteNonQuery(); + } + } + public void ClearSubtitles(long episodeID) { string query = "DELETE FROM tracks WHERE episodeID = $episodeID;"; diff --git a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs index aaffff23..1a53a4b5 100644 --- a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs +++ b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs @@ -6,6 +6,9 @@ namespace Kyoo.InternalAPI { public interface IMetadataProvider { + //For the collection + Task GetCollectionFromName(string name); + //For the show Task GetShowByID(string id); Task GetShowFromName(string showName, string showPath); diff --git a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/HelperTvDB.cs b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/HelperTvDB.cs index ec9e568b..b825dd95 100644 --- a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/HelperTvDB.cs +++ b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/HelperTvDB.cs @@ -1,10 +1,8 @@ using Kyoo.Models; using Newtonsoft.Json; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; diff --git a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs index aca09150..8539065c 100644 --- a/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs +++ b/Kyoo/InternalAPI/MetadataProvider/Implementations/TheTvDB/ProviderTheTvDB.cs @@ -1,6 +1,8 @@ using Kyoo.InternalAPI.MetadataProvider.TheTvDB; +using Kyoo.InternalAPI.Utility; using Kyoo.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; @@ -16,74 +18,13 @@ namespace Kyoo.InternalAPI.MetadataProvider [MetaProvider] public class ProviderTheTvDB : HelperTvDB, IMetadataProvider { - private struct SearchTbDB +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + public async Task GetCollectionFromName(string name) +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { - public string seriesName; - public string overview; - public string slug; - public string network; - public string status; - public int id; - public string firstAired; - public string banner; - public string[] aliases; + return new Collection(-1, Slugifier.ToSlug(name), name, null, null); } - private struct DataTvDb - { - public string seriesName; - public string overview; - public string slug; - public string network; - public string status; - - public int id; - public string seriesId; - public string imdbId; - public string zap2itId; - - public string firstAired; - public string banner; - public string[] aliases; - public string[] genre; - - public string added; - public string airsDayOfWeek; - public string airsTime; - public string lastUpdated; - public string runtime; - - public string networkId; - public string rating; - public float siteRating; - public int siteRatingCount; - } - - private struct RatingInfo - { - public float average; - public int count; - } - private struct ImageTvDb - { - public string fileName; - public int id; - public string keyType; - public int languageId; - public RatingInfo ratingsInfo; - public string resolution; - public string subKey; - public string thumbnail; - } - - private struct ErrorsTvDB - { - public string[] invalidFilters; - public string invalidLanguage; - public string[] invalidQueryParams; - } - - public async Task GetShowFromName(string showName, string showPath) { string token = await Authentificate(); @@ -109,21 +50,21 @@ namespace Kyoo.InternalAPI.MetadataProvider stream.Close(); response.Close(); - var model = new { data = new SearchTbDB[0] }; - SearchTbDB data = JsonConvert.DeserializeAnonymousType(content, model).data[0]; + dynamic obj = JsonConvert.DeserializeObject(content); + dynamic data = obj.data[0]; Show show = new Show(-1, ToSlug(showName), - data.seriesName, - data.aliases, + (string)data.seriesName, + ((JArray)data.aliases).ToObject>(), showPath, - data.overview, + (string)data.overview, null, //trailer null, //genres (no info with this request) - GetStatus(data.status), - GetYear(data.firstAired), + GetStatus((string)data.status), + GetYear((string)data.firstAired), null, //endYear - string.Format("{0}={1}|", Provider, data.id)); + string.Format("{0}={1}|", Provider, (string)data.id)); return (await GetShowByID(GetID(show.ExternalIDs))).Set(show.Slug, show.Path) ?? show; } } @@ -168,19 +109,19 @@ namespace Kyoo.InternalAPI.MetadataProvider stream.Close(); response.Close(); - var model = new { data = new DataTvDb(), errors = new ErrorsTvDB() }; - DataTvDb data = JsonConvert.DeserializeAnonymousType(content, model).data; + dynamic model = JsonConvert.DeserializeObject(content); + dynamic data = model.data; Show show = new Show(-1, null, //Slug - data.seriesName, - data.aliases, + (string)data.seriesName, + ((JArray)data.aliases).ToObject>(), null, //Path - data.overview, + (string)data.overview, null, //Trailer - GetGenres(data.genre), - GetStatus(data.status), - GetYear(data.firstAired), + GetGenres(((JArray)data.genre).ToObject()), + GetStatus((string)data.status), + GetYear((string)data.firstAired), null, //endYear string.Format("TvDB={0}|", id)); await GetImages(show); @@ -237,10 +178,9 @@ namespace Kyoo.InternalAPI.MetadataProvider stream.Close(); response.Close(); - var model = new { data = new ImageTvDb[0], error = new ErrorsTvDB() }; + dynamic model = JsonConvert.DeserializeObject(content); //Should implement language selection here - ImageTvDb data = JsonConvert.DeserializeAnonymousType(content, model).data.OrderByDescending(x => x.ratingsInfo.average).ThenByDescending(x => x.ratingsInfo.count).FirstOrDefault(); - IEnumerable datas = JsonConvert.DeserializeAnonymousType(content, model).data.OrderByDescending(x => x.ratingsInfo.average).ThenByDescending(x => x.ratingsInfo.count); + dynamic data = ((IEnumerable)model.data).OrderByDescending(x => x.ratingsInfo.average).ThenByDescending(x => x.ratingsInfo.count).FirstOrDefault(); SetImage(show, "https://www.thetvdb.com/banners/" + data.fileName, type.Key); } } diff --git a/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs b/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs index 48f47f38..82e5220e 100644 --- a/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs +++ b/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs @@ -1,14 +1,13 @@ -using Kyoo.Models; +using Kyoo.InternalAPI.Utility; +using Kyoo.Models; using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; namespace Kyoo.InternalAPI.MetadataProvider { public abstract class ProviderHelper { public abstract string Provider { get; } - + public string GetID(string externalIDs) { if (externalIDs?.Contains(Provider) == true) @@ -22,29 +21,7 @@ namespace Kyoo.InternalAPI.MetadataProvider public string ToSlug(string showTitle) { - if (showTitle == null) - return null; - - //First to lower case - showTitle = showTitle.ToLowerInvariant(); - - //Remove all accents - //var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(showTitle); - //showTitle = Encoding.ASCII.GetString(bytes); - - //Replace spaces - showTitle = Regex.Replace(showTitle, @"\s", "-", RegexOptions.Compiled); - - //Remove invalid chars - showTitle = Regex.Replace(showTitle, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled); - - //Trim dashes from end - showTitle = showTitle.Trim('-', '_'); - - //Replace double occurences of - or \_ - showTitle = Regex.Replace(showTitle, @"([-_]){2,}", "$1", RegexOptions.Compiled); - - return showTitle; + return Slugifier.ToSlug(showTitle); } public enum ImageType { Poster, Background, Thumbnail, Logo } diff --git a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs index 4c2d324c..cbb053a2 100644 --- a/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs +++ b/Kyoo/InternalAPI/MetadataProvider/ProviderManager.cs @@ -79,6 +79,11 @@ namespace Kyoo.InternalAPI //For all the following methods, it should use all providers and merge the data. + public Task GetCollectionFromName(string name) + { + return providers[0].GetCollectionFromName(name); + } + public Task GetImages(Show show) { return providers[0].GetImages(show); diff --git a/Kyoo/InternalAPI/Utility/Slugifier.cs b/Kyoo/InternalAPI/Utility/Slugifier.cs new file mode 100644 index 00000000..395c4d77 --- /dev/null +++ b/Kyoo/InternalAPI/Utility/Slugifier.cs @@ -0,0 +1,34 @@ +using System.Text.RegularExpressions; + +namespace Kyoo.InternalAPI.Utility +{ + public class Slugifier + { + public static string ToSlug(string showTitle) + { + if (showTitle == null) + return null; + + //First to lower case + showTitle = showTitle.ToLowerInvariant(); + + //Remove all accents + //var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(showTitle); + //showTitle = Encoding.ASCII.GetString(bytes); + + //Replace spaces + showTitle = Regex.Replace(showTitle, @"\s", "-", RegexOptions.Compiled); + + //Remove invalid chars + showTitle = Regex.Replace(showTitle, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled); + + //Trim dashes from end + showTitle = showTitle.Trim('-', '_'); + + //Replace double occurences of - or \_ + showTitle = Regex.Replace(showTitle, @"([-_]){2,}", "$1", RegexOptions.Compiled); + + return showTitle; + } + } +} diff --git a/Kyoo/Models/Collection.cs b/Kyoo/Models/Collection.cs index 4c35649e..8ae27c71 100644 --- a/Kyoo/Models/Collection.cs +++ b/Kyoo/Models/Collection.cs @@ -33,6 +33,11 @@ namespace Kyoo.Models reader["imgPrimary"] as string); } + public Show AsShow() + { + return new Show(-1, Slug, Name, null, null, Overview, null, null, null, null, null, null); + } + public Collection SetShows(ILibraryManager libraryManager) { Shows = libraryManager.GetShowsInCollection(id); diff --git a/Kyoo/Models/Show.cs b/Kyoo/Models/Show.cs index 2475022c..eb4b6639 100644 --- a/Kyoo/Models/Show.cs +++ b/Kyoo/Models/Show.cs @@ -1,7 +1,6 @@ using Kyoo.InternalAPI; using Newtonsoft.Json; using System.Collections.Generic; -using System.Linq; namespace Kyoo.Models { @@ -29,10 +28,11 @@ namespace Kyoo.Models public string ExternalIDs; //Used in the rest API excusively. - public Studio studio; + public Studio studio; public IEnumerable directors; public IEnumerable people; public IEnumerable seasons; + public bool IsCollection; public string GetAliases() @@ -68,6 +68,7 @@ namespace Kyoo.Models StartYear = startYear; EndYear = endYear; ExternalIDs = externalIDs; + IsCollection = false; } public Show(long id, string slug, string title, IEnumerable aliases, string path, string overview, string trailerUrl, Status? status, long? startYear, long? endYear, string imgPrimary, string imgThumb, string imgLogo, string imgBackdrop, string externalIDs) @@ -87,6 +88,19 @@ namespace Kyoo.Models ImgLogo = imgLogo; ImgBackdrop = imgBackdrop; ExternalIDs = externalIDs; + IsCollection = false; + } + + public static Show FromQueryReader(System.Data.SQLite.SQLiteDataReader reader) + { + return new Show() + { + Slug = reader["slug"] as string, + Title = reader["title"] as string, + StartYear = reader["startYear"] as long?, + EndYear = reader["endYear"] as long?, + IsCollection = reader[4] as string == "1" + }; } public static Show FromReader(System.Data.SQLite.SQLiteDataReader reader) @@ -99,9 +113,9 @@ namespace Kyoo.Models reader["overview"] as string, reader["trailerUrl"] as string, reader["status"] as Status?, - reader["startYear"] as long?, + reader["startYear"] as long?, reader["endYear"] as long?, - reader["imgPrimary"] as string, + reader["imgPrimary"] as string, reader["imgThumb"] as string, reader["imgLogo"] as string, reader["imgBackdrop"] as string, diff --git a/Kyoo/appsettings.json b/Kyoo/appsettings.json index a21739eb..1ff0ec3c 100644 --- a/Kyoo/appsettings.json +++ b/Kyoo/appsettings.json @@ -16,6 +16,6 @@ "libraryPaths": [ "\\\\sdg\\video\\Anime" ], - "regex": ".*\\\\(?.+?) S(?\\d+)E(?\\d+)", + "regex": ".*\\\\(?.+?)?\\\\.*\\\\(?.+?) S(?\\d+)E(?\\d+)", "absoluteRegex": ".*\\\\(?.+?) (?\\d+)" }