From 19f5427dbf82214d956d93c4ec95051e1950278a Mon Sep 17 00:00:00 2001 From: Keivan Beigi Date: Tue, 30 Dec 2014 14:08:08 -0800 Subject: [PATCH] Fixed: Replaced trakt with tvdb as data source --- .../MetadataSourceTests/TraktProxyFixture.cs | 4 +- .../TvdbDataProxyFixture.cs | 138 +++++++ .../NzbDrone.Core.Test.csproj | 1 + .../TvTests/RefreshEpisodeServiceFixture.cs | 2 +- .../MetadataSource/TraktProxy.cs | 2 +- src/NzbDrone.Core/MetadataSource/TvDbProxy.cs | 342 ++++++++++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 5 + src/NzbDrone.Core/Tv/SeriesService.cs | 5 + src/NzbDrone.sln | 11 +- src/TVDBSharp/App.config | 6 + src/TVDBSharp/Models/Builder.cs | 279 ++++++++++++++ src/TVDBSharp/Models/DAO/DataProvider.cs | 43 +++ src/TVDBSharp/Models/DAO/IDataProvider.cs | 45 +++ src/TVDBSharp/Models/Enums/ContentRating.cs | 43 +++ src/TVDBSharp/Models/Enums/Frequency.cs | 14 + src/TVDBSharp/Models/Enums/Interval.cs | 32 ++ src/TVDBSharp/Models/Enums/Status.cs | 23 ++ src/TVDBSharp/Models/Episode.cs | 111 ++++++ src/TVDBSharp/Models/Show.cs | 122 +++++++ src/TVDBSharp/Models/Updates.cs | 49 +++ src/TVDBSharp/Properties/AssemblyInfo.cs | 39 ++ src/TVDBSharp/TVDB.cs | 72 ++++ src/TVDBSharp/TVDBSharp.csproj | 74 ++++ src/TVDBSharp/Utilities/Extensions.cs | 58 +++ src/TVDBSharp/Utilities/Utils.cs | 73 ++++ 25 files changed, 1589 insertions(+), 4 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MetadataSourceTests/TvdbDataProxyFixture.cs create mode 100644 src/NzbDrone.Core/MetadataSource/TvDbProxy.cs create mode 100644 src/TVDBSharp/App.config create mode 100644 src/TVDBSharp/Models/Builder.cs create mode 100644 src/TVDBSharp/Models/DAO/DataProvider.cs create mode 100644 src/TVDBSharp/Models/DAO/IDataProvider.cs create mode 100644 src/TVDBSharp/Models/Enums/ContentRating.cs create mode 100644 src/TVDBSharp/Models/Enums/Frequency.cs create mode 100644 src/TVDBSharp/Models/Enums/Interval.cs create mode 100644 src/TVDBSharp/Models/Enums/Status.cs create mode 100644 src/TVDBSharp/Models/Episode.cs create mode 100644 src/TVDBSharp/Models/Show.cs create mode 100644 src/TVDBSharp/Models/Updates.cs create mode 100644 src/TVDBSharp/Properties/AssemblyInfo.cs create mode 100644 src/TVDBSharp/TVDB.cs create mode 100644 src/TVDBSharp/TVDBSharp.csproj create mode 100644 src/TVDBSharp/Utilities/Extensions.cs create mode 100644 src/TVDBSharp/Utilities/Utils.cs diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index 2e5710e46..307e23928 100644 --- a/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; @@ -134,3 +135,4 @@ namespace NzbDrone.Core.Test.MetadataSourceTests } } } +*/ diff --git a/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbDataProxyFixture.cs b/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbDataProxyFixture.cs new file mode 100644 index 000000000..8ade9b6ca --- /dev/null +++ b/src/NzbDrone.Core.Test/MetadataSourceTests/TvdbDataProxyFixture.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; +using NzbDrone.Test.Common.Categories; + +namespace NzbDrone.Core.Test.MetadataSourceTests +{ + [TestFixture] + [IntegrationTest] + public class TvdbDataProxyFixture : CoreTest + { + [SetUp] + public void Setup() + { + UseRealHttp(); + } + + [TestCase("The Simpsons", "The Simpsons")] + [TestCase("South Park", "South Park")] + [TestCase("Franklin & Bash", "Franklin & Bash")] + [TestCase("House", "House")] + [TestCase("Mr. D", "Mr. D")] + [TestCase("Rob & Big", "Rob & Big")] + [TestCase("M*A*S*H", "M*A*S*H")] + //[TestCase("imdb:tt0436992", "Doctor Who (2005)")] + //[TestCase("tvdb:78804", "Doctor Who (2005)")] + public void successful_search(string title, string expected) + { + var result = Subject.SearchForNewSeries(title); + + result.Should().NotBeEmpty(); + + result[0].Title.Should().Be(expected); + } + + [Test] + public void no_search_result() + { + var result = Subject.SearchForNewSeries(Guid.NewGuid().ToString()); + result.Should().BeEmpty(); + } + + [TestCase(75978, "Family Guy")] + [TestCase(83462, "Castle (2009)")] + [TestCase(266189, "The Blacklist")] + public void should_be_able_to_get_series_detail(Int32 tvdbId, String title) + { + var details = Subject.GetSeriesInfo(tvdbId); + + ValidateSeries(details.Item1); + ValidateEpisodes(details.Item2); + + details.Item1.Title.Should().Be(title); + } + + [Test] + public void getting_details_of_invalid_series() + { + Assert.Throws(() => Subject.GetSeriesInfo(Int32.MaxValue)); + + //ExceptionVerification.ExpectedWarns(1); + } + + [Test] + public void should_not_have_period_at_start_of_title_slug() + { + var details = Subject.GetSeriesInfo(79099); + + details.Item1.TitleSlug.Should().Be("dothack"); + } + + private void ValidateSeries(Series series) + { + series.Should().NotBeNull(); + series.Title.Should().NotBeNullOrWhiteSpace(); + series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title)); + series.SortTitle.Should().Be(SeriesTitleNormalizer.Normalize(series.Title, series.TvdbId)); + series.Overview.Should().NotBeNullOrWhiteSpace(); + series.AirTime.Should().NotBeNullOrWhiteSpace(); + series.FirstAired.Should().HaveValue(); + series.FirstAired.Value.Kind.Should().Be(DateTimeKind.Utc); + series.Images.Should().NotBeEmpty(); + series.ImdbId.Should().NotBeNullOrWhiteSpace(); + series.Network.Should().NotBeNullOrWhiteSpace(); + series.Runtime.Should().BeGreaterThan(0); + series.TitleSlug.Should().NotBeNullOrWhiteSpace(); + //series.TvRageId.Should().BeGreaterThan(0); + series.TvdbId.Should().BeGreaterThan(0); + } + + private void ValidateEpisodes(List episodes) + { + episodes.Should().NotBeEmpty(); + + var episodeGroup = episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")); + episodeGroup.Should().OnlyContain(c => c.Count() == 1); + + episodes.Should().Contain(c => c.SeasonNumber > 0); + episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview)); + + foreach (var episode in episodes) + { + ValidateEpisode(episode); + + //if atleast one episdoe has title it means parse it working. + episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Title)); + } + } + + private void ValidateEpisode(Episode episode) + { + episode.Should().NotBeNull(); + + //TODO: Is there a better way to validate that episode number or season number is greater than zero? + (episode.EpisodeNumber + episode.SeasonNumber).Should().NotBe(0); + + episode.Should().NotBeNull(); + + if (episode.AirDateUtc.HasValue) + { + episode.AirDateUtc.Value.Kind.Should().Be(DateTimeKind.Utc); + } + + episode.Images.Any(i => i.CoverType == MediaCoverTypes.Screenshot && i.Url.Contains("-940.")) + .Should() + .BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index cfb166421..c74ade20b 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -227,6 +227,7 @@ + diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index 1d1374bfe..2a64711dc 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Test.TvTests { UseRealHttp(); - _gameOfThrones = Mocker.Resolve().GetSeriesInfo(121361);//Game of thrones + _gameOfThrones = Mocker.Resolve().GetSeriesInfo(121361);//Game of thrones // Remove specials. _gameOfThrones.Item2.RemoveAll(v => v.SeasonNumber == 0); diff --git a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs index aea2e0914..e4f84f4cb 100644 --- a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -13,7 +13,7 @@ using Episode = NzbDrone.Core.Tv.Episode; namespace NzbDrone.Core.MetadataSource { - public class TraktProxy : ISearchForNewSeries, IProvideSeriesInfo + public class TraktProxy //: ISearchForNewSeries, IProvideSeriesInfo { private readonly Logger _logger; private readonly IHttpClient _httpClient; diff --git a/src/NzbDrone.Core/MetadataSource/TvDbProxy.cs b/src/NzbDrone.Core/MetadataSource/TvDbProxy.cs new file mode 100644 index 000000000..0632df2e8 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/TvDbProxy.cs @@ -0,0 +1,342 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource.Trakt; +using NzbDrone.Core.Tv; +using TVDBSharp.Models.Enums; + +namespace NzbDrone.Core.MetadataSource +{ + public class TvDbProxy : ISearchForNewSeries, IProvideSeriesInfo + { + private readonly Logger _logger; + private readonly IHttpClient _httpClient; + private static readonly Regex CollapseSpaceRegex = new Regex(@"\s+", RegexOptions.Compiled); + private static readonly Regex InvalidSearchCharRegex = new Regex(@"(?:\*|\(|\)|'|!|@|\+)", RegexOptions.Compiled); + private static readonly Regex ExpandCamelCaseRegEx = new Regex(@"(? SearchTrakt(string title) + { +/* Common.Http.HttpRequest request; + + var lowerTitle = title.ToLowerInvariant(); + + if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:") /*|| lowerTitle.StartsWith("slug:")#1#) + { + var slug = lowerTitle.Split(':')[1].Trim(); + + if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace)) + { + return Enumerable.Empty(); + } + + request = _requestBuilder.Build("/{slug}/extended"); + + request.AddSegment("path", "show"); + request.AddSegment("resource", "summary"); + request.AddSegment("slug", GetSearchTerm(slug)); + + return new List { _httpClient.Get(request).Resource }; + } + + if (lowerTitle.StartsWith("imdb:") || lowerTitle.StartsWith("imdbid:")) + { + var slug = lowerTitle.Split(':')[1].TrimStart('t').Trim(); + + if (slug.IsNullOrWhiteSpace() || !slug.All(char.IsDigit) || slug.Length < 7) + { + return Enumerable.Empty(); + } + + title = "tt" + slug; + }*/ + + + return tvdb.Search(GetSearchTerm(title)); + + } + + public List SearchForNewSeries(string title) + { + try + { + var tvdbSeries = SearchTrakt(title.Trim()); + + var series = tvdbSeries.Select(MapSeries).ToList(); + + series.Sort(new TraktSearchSeriesComparer(title)); + + return series; + } + catch (Common.Http.HttpException) + { + throw new TraktException("Search for '{0}' failed. Unable to communicate with Trakt.", title); + } + catch (Exception ex) + { + _logger.WarnException(ex.Message, ex); + throw new TraktException("Search for '{0}' failed. Invalid response received from Trakt.", title); + } + } + + public Tuple> GetSeriesInfo(int tvdbSeriesId) + { + + var request = _requestBuilder.Build("/{tvdbId}/extended"); + + request.AddSegment("path", "show"); + request.AddSegment("resource", "summary"); + request.AddSegment("tvdbId", tvdbSeriesId.ToString()); + + var tvdbSeries = tvdb.GetShow(tvdbSeriesId); + + var episodes = tvdbSeries.Episodes.Select(MapEpisode); + + episodes = RemoveDuplicates(episodes); + + var series = MapSeries(tvdbSeries); + + return new Tuple>(series, episodes.ToList()); + } + + private static IEnumerable RemoveDuplicates(IEnumerable episodes) + { + //http://support.trakt.tv/forums/188762-general/suggestions/4430690-anger-management-duplicate-episode + var episodeGroup = episodes.GroupBy(e => e.SeasonNumber.ToString("0000") + e.EpisodeNumber.ToString("0000")); + return episodeGroup.Select(g => g.First()); + } + + private static Series MapSeries(TVDBSharp.Models.Show show) + { + var series = new Series(); + series.TvdbId = show.Id; + //series.TvRageId = show.tvrage_id; + series.ImdbId = show.ImdbId; + series.Title = show.Name; + series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.Name); + series.SortTitle = SeriesTitleNormalizer.Normalize(show.Name, show.Id); + + if (show.FirstAired != null) + { + series.Year = show.FirstAired.Value.Year; + series.FirstAired = show.FirstAired.Value.ToUniversalTime(); + } + + series.Overview = show.Description; + + if (show.Runtime != null) + { + series.Runtime = show.Runtime.Value; + } + + series.Network = show.Network; + + if (show.AirTime != null) + { + series.AirTime = show.AirTime.Value.ToString(); + } + + series.TitleSlug = GenerateSlug(show.Name); + series.Status = GetSeriesStatus(show.Status); + series.Ratings = GetRatings(show.RatingCount, show.Rating); + series.Genres = show.Genres; + series.Certification = show.ContentRating.ToString().ToUpper(); + series.Actors = new List(); + series.Seasons = GetSeasons(show); + + series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Banner, Url = show.Banner.ToString() }); + series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Poster, Url = show.Poster.ToString() }); + series.Images.Add(new MediaCover.MediaCover { CoverType = MediaCoverTypes.Fanart, Url = show.Fanart.ToString() }); + + return series; + } + + private static Tv.Episode MapEpisode(TVDBSharp.Models.Episode traktEpisode) + { + var episode = new Tv.Episode(); + episode.Overview = traktEpisode.Description; + episode.SeasonNumber = traktEpisode.SeasonNumber; + episode.EpisodeNumber = traktEpisode.EpisodeNumber; + episode.Title = traktEpisode.Title; + + if (traktEpisode.FirstAired != null) + { + episode.AirDate = traktEpisode.FirstAired.Value.ToString("yyyy-MM-dd"); + episode.AirDateUtc = traktEpisode.FirstAired.Value.ToUniversalTime(); + } + + episode.Ratings = GetRatings(traktEpisode.RatingCount, traktEpisode.Rating); + + //Don't include series fanart images as episode screenshot + episode.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Screenshot, traktEpisode.EpisodeImage.ToString())); + + return episode; + } + + private static string GetPosterThumbnailUrl(string posterUrl) + { + if (posterUrl.Contains("poster-small.jpg") || posterUrl.Contains("poster-dark.jpg")) return posterUrl; + + var extension = Path.GetExtension(posterUrl); + var withoutExtension = posterUrl.Substring(0, posterUrl.Length - extension.Length); + return withoutExtension + "-300" + extension; + } + + private static SeriesStatusType GetSeriesStatus(Status status) + { + if (status == Status.Ended) + { + return SeriesStatusType.Ended; + } + + return SeriesStatusType.Continuing; + } + + private static DateTime? FromIso(string iso) + { + DateTime result; + + if (!DateTime.TryParse(iso, out result)) + return null; + + return result.ToUniversalTime(); + } + + private static string FromIsoToString(string iso) + { + if (String.IsNullOrWhiteSpace(iso)) return null; + + var match = Regex.Match(iso, @"^\d{4}\W\d{2}\W\d{2}"); + + if (!match.Success) return null; + + return match.Captures[0].Value; + } + + private static string GetSearchTerm(string phrase) + { + phrase = phrase.RemoveAccent(); + phrase = InvalidSearchCharRegex.Replace(phrase, ""); + + // if (!phrase.Any(char.IsWhiteSpace) && phrase.Any(char.IsUpper) && phrase.Any(char.IsLower) && phrase.Length > 4) + // { + // phrase = ExpandCamelCaseRegEx.Replace(phrase, " "); + // } + + phrase = CollapseSpaceRegex.Replace(phrase, " ").Trim(); + phrase = phrase.Trim('-'); + + phrase = HttpUtility.UrlEncode(phrase.ToLower()); + + return phrase; + } + + private static int GetYear(int year, int firstAired) + { + if (year > 1969) return year; + + if (firstAired == 0) return DateTime.Today.Year; + + return year; + } + + private static Tv.Ratings GetRatings(int ratingCount, double? rating) + { + + var result = new Tv.Ratings { Votes = ratingCount }; + + if (rating != null) + { + result.Percentage = (int)(rating.Value * 100); + } + + return result; + } + + private static List GetActors(People people) + { + if (people == null) + { + return new List(); + } + + return GetActors(people.actors).ToList(); + } + + private static IEnumerable GetActors(IEnumerable trakcActors) + { + foreach (var traktActor in trakcActors) + { + var actor = new Tv.Actor + { + Name = traktActor.name, + Character = traktActor.character, + }; + + actor.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Headshot, traktActor.images.headshot)); + + yield return actor; + } + } + + private static List GetSeasons(TVDBSharp.Models.Show show) + { + var seasons = new List(); + + foreach (var seasonNumber in show.Episodes.Select(c => c.SeasonNumber).Distinct().OrderByDescending(c => c)) + { + var season = new Tv.Season + { + SeasonNumber = seasonNumber + }; + + /* if (season.images != null) + { + season.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Poster, season.images.poster)); + }*/ + + seasons.Add(season); + } + + return seasons; + } + + + private static readonly Regex RemoveRegex = new Regex(@"[^\w-]", RegexOptions.Compiled); + + public static string GenerateSlug(string showTitle) + { + if (showTitle.StartsWith(".")) + { + showTitle = "dot" + showTitle.Substring(1); + } + showTitle = showTitle.Replace(" ", "-"); + showTitle = showTitle.Replace("&", "and"); + showTitle = RemoveRegex.Replace(showTitle, String.Empty); + showTitle = showTitle.RemoveAccent(); + showTitle = showTitle.ToLowerInvariant(); + + return showTitle; + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index b08657bcf..16c5df788 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -595,6 +595,7 @@ + @@ -899,6 +900,10 @@ {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} NzbDrone.Common + + {0cc493d7-0a9f-4199-9615-0a977945d716} + TVDBSharp + diff --git a/src/NzbDrone.Core/Tv/SeriesService.cs b/src/NzbDrone.Core/Tv/SeriesService.cs index 624d2b1e7..3224f3443 100644 --- a/src/NzbDrone.Core/Tv/SeriesService.cs +++ b/src/NzbDrone.Core/Tv/SeriesService.cs @@ -176,6 +176,11 @@ namespace NzbDrone.Core.Tv } } + if (series.TvRageId == 0) + { + series.TvRageId = storedSeries.TvRageId; + } + var updatedSeries = _seriesRepository.Update(series); _eventAggregator.PublishEvent(new SeriesEditedEvent(updatedSeries, storedSeries)); diff --git a/src/NzbDrone.sln b/src/NzbDrone.sln index efeeb72db..c7c6fea0b 100644 --- a/src/NzbDrone.sln +++ b/src/NzbDrone.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{57A04B72-8088-4F75-A582-1158CF8291F7}" EndProject @@ -82,6 +82,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogentriesCore", "Logentrie EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogentriesNLog", "LogentriesNLog\LogentriesNLog.csproj", "{9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TVDBSharp", "TVDBSharp\TVDBSharp.csproj", "{0CC493D7-0A9F-4199-9615-0A977945D716}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -262,6 +264,12 @@ Global {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Mono|x86.Build.0 = Release|x86 {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.ActiveCfg = Release|x86 {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB}.Release|x86.Build.0 = Release|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Debug|x86.ActiveCfg = Debug|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Debug|x86.Build.0 = Debug|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Mono|x86.ActiveCfg = Release|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Mono|x86.Build.0 = Release|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Release|x86.ActiveCfg = Release|x86 + {0CC493D7-0A9F-4199-9615-0A977945D716}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -294,6 +302,7 @@ Global {411A9E0E-FDC6-4E25-828A-0C2CD1CD96F8} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {90D6E9FC-7B88-4E1B-B018-8FA742274558} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} {9DC31DE3-79FF-47A8-96B4-6BA18F6BB1CB} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} + {0CC493D7-0A9F-4199-9615-0A977945D716} = {F6E3A728-AE77-4D02-BAC8-82FBC1402DDA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.0\lib\NET35;packages\Unity.2.1.505.2\lib\NET35 diff --git a/src/TVDBSharp/App.config b/src/TVDBSharp/App.config new file mode 100644 index 000000000..b04d01078 --- /dev/null +++ b/src/TVDBSharp/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/TVDBSharp/Models/Builder.cs b/src/TVDBSharp/Models/Builder.cs new file mode 100644 index 000000000..72f307a9e --- /dev/null +++ b/src/TVDBSharp/Models/Builder.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Xml.Linq; +using TVDBSharp.Models.DAO; +using TVDBSharp.Models.Enums; +using TVDBSharp.Utilities; + +namespace TVDBSharp.Models +{ + /// + /// Provides builder classes for complex entities. + /// + public class Builder + { + private const string UriPrefix = "http://thetvdb.com/banners/"; + private readonly IDataProvider _dataProvider; + + /// + /// Initializes a new Builder object with the given . + /// + /// The DataProvider used to retrieve XML responses. + public Builder(IDataProvider dataProvider) + { + _dataProvider = dataProvider; + } + + /// + /// Builds a show object from the given show ID. + /// + /// ID of the show to serialize into a object. + /// Returns the Show object. + public Show BuildShow(int showID) + { + var builder = new ShowBuilder(_dataProvider.GetShow(showID)); + return builder.GetResult(); + } + + public Episode BuildEpisode(int episodeId, string lang) + { + var builder = new EpisodeBuilder(_dataProvider.GetEpisode(episodeId, lang).Descendants("Episode").First()); + return builder.GetResult(); + } + + public Updates BuildUpdates(Interval interval) + { + var builder = new UpdatesBuilder(_dataProvider.GetUpdates(interval)); + return builder.GetResult(); + } + + /// + /// Returns a list of objects that match the given query. + /// + /// Query the search is performed with. + /// Maximal amount of shows the resultset should return. + /// Returns a list of show objects. + public List Search(string query, int results) + { + var shows = new List(); + var doc = _dataProvider.Search(query); + + foreach (var element in doc.Descendants("Series").Take(results)) + { + var id = int.Parse(element.GetXmlData("seriesid")); + + try + { + var response = _dataProvider.GetShow(id); + shows.Add(new ShowBuilder(response).GetResult()); + } + catch (WebException ex) + { + + } + + } + + return shows; + } + + private static Uri GetBannerUri(string uriSuffix) + { + return new Uri(UriPrefix + uriSuffix, UriKind.Absolute); + } + + private class ShowBuilder + { + private readonly Show _show; + + public ShowBuilder(XDocument doc) + { + _show = new Show(); + _show.Id = int.Parse(doc.GetSeriesData("id")); + _show.ImdbId = doc.GetSeriesData("IMDB_ID"); + _show.Name = doc.GetSeriesData("SeriesName"); + _show.Language = doc.GetSeriesData("Language"); + _show.Network = doc.GetSeriesData("Network"); + _show.Description = doc.GetSeriesData("Overview"); + _show.Rating = string.IsNullOrWhiteSpace(doc.GetSeriesData("Rating")) + ? (double?) null + : Convert.ToDouble(doc.GetSeriesData("Rating"), + System.Globalization.CultureInfo.InvariantCulture); + _show.RatingCount = string.IsNullOrWhiteSpace(doc.GetSeriesData("RatingCount")) + ? 0 + : Convert.ToInt32(doc.GetSeriesData("RatingCount")); + _show.Runtime = string.IsNullOrWhiteSpace(doc.GetSeriesData("Runtime")) + ? (int?) null + : Convert.ToInt32(doc.GetSeriesData("Runtime")); + _show.Banner = GetBannerUri(doc.GetSeriesData("banner")); + _show.Fanart = GetBannerUri(doc.GetSeriesData("fanart")); + _show.LastUpdated = string.IsNullOrWhiteSpace(doc.GetSeriesData("lastupdated")) + ? (long?) null + : Convert.ToInt64(doc.GetSeriesData("lastupdated")); + _show.Poster = GetBannerUri(doc.GetSeriesData("poster")); + _show.Zap2ItID = doc.GetSeriesData("zap2it_id"); + _show.FirstAired = string.IsNullOrWhiteSpace(doc.GetSeriesData("FirstAired")) + ? (DateTime?) null + : Utils.ParseDate(doc.GetSeriesData("FirstAired")); + _show.AirTime = string.IsNullOrWhiteSpace(doc.GetSeriesData("Airs_Time")) + ? (TimeSpan?) null + : Utils.ParseTime(doc.GetSeriesData("Airs_Time")); + _show.AirDay = string.IsNullOrWhiteSpace(doc.GetSeriesData("Airs_DayOfWeek")) + ? (Frequency?) null + : (Frequency) Enum.Parse(typeof (Frequency), doc.GetSeriesData("Airs_DayOfWeek")); + _show.Status = string.IsNullOrWhiteSpace(doc.GetSeriesData("Status")) + ? Status.Unknown + : (Status) Enum.Parse(typeof (Status), doc.GetSeriesData("Status")); + _show.ContentRating = Utils.GetContentRating(doc.GetSeriesData("ContentRating")); + _show.Genres = + new List(doc.GetSeriesData("Genre") + .Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)); + _show.Actors = + new List(doc.GetSeriesData("Actors") + .Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)); + _show.Episodes = new EpisodesBuilder(doc).BuildEpisodes(); + } + + public Show GetResult() + { + return _show; + } + } + + public class EpisodeBuilder + { + private readonly Episode _episode; + + public EpisodeBuilder(XElement episodeNode) + { + _episode = new Episode + { + Id = int.Parse(episodeNode.GetXmlData("id")), + Title = episodeNode.GetXmlData("EpisodeName"), + Description = episodeNode.GetXmlData("Overview"), + EpisodeNumber = int.Parse(episodeNode.GetXmlData("EpisodeNumber")), + Director = episodeNode.GetXmlData("Director"), + EpisodeImage = GetBannerUri(episodeNode.GetXmlData("filename")), + FirstAired = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("FirstAired")) + ? (DateTime?) null + : Utils.ParseDate(episodeNode.GetXmlData("FirstAired")), + GuestStars = + new List(episodeNode.GetXmlData("GuestStars") + .Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)), + ImdbId = episodeNode.GetXmlData("IMDB_ID"), + Language = episodeNode.GetXmlData("Language"), + LastUpdated = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("lastupdated")) + ? 0L + : Convert.ToInt64(episodeNode.GetXmlData("lastupdated")), + Rating = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("Rating")) + ? (double?) null + : Convert.ToDouble(episodeNode.GetXmlData("Rating"), + System.Globalization.CultureInfo.InvariantCulture), + RatingCount = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("RatingCount")) + ? 0 + : Convert.ToInt32(episodeNode.GetXmlData("RatingCount")), + SeasonId = int.Parse(episodeNode.GetXmlData("seasonid")), + SeasonNumber = int.Parse(episodeNode.GetXmlData("SeasonNumber")), + SeriesId = int.Parse(episodeNode.GetXmlData("seriesid")), + ThumbHeight = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("thumb_height")) + ? (int?) null + : Convert.ToInt32(episodeNode.GetXmlData("thumb_height")), + ThumbWidth = + string.IsNullOrWhiteSpace(episodeNode.GetXmlData("thumb_width")) + ? (int?) null + : Convert.ToInt32(episodeNode.GetXmlData("thumb_width")), + TmsExport = episodeNode.GetXmlData("tms_export"), + Writers = + new List(episodeNode.GetXmlData("Writer") + .Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)) + }; + } + + public Episode GetResult() + { + return _episode; + } + } + + private class EpisodesBuilder + { + private readonly XDocument _doc; + + public EpisodesBuilder(XDocument doc) + { + _doc = doc; + } + + public List BuildEpisodes() + { + var result = new List(); + + foreach (var episodeNode in _doc.Descendants("Episode")) + { + var episode = new EpisodeBuilder(episodeNode).GetResult(); + result.Add(episode); + } + + return result; + } + } + + public class UpdatesBuilder + { + private readonly Updates _updates; + + public UpdatesBuilder(XDocument doc) + { + if (doc.Root != null) + { + _updates = new Updates + { + Time = int.Parse(doc.Root.Attribute("time").Value), + UpdatedSeries = doc.Root.Elements("Series") + .Select(elt => new UpdatedSerie + { + Id = int.Parse(elt.Element("id").Value), + Time = int.Parse(elt.Element("time").Value) + }) + .ToList(), + UpdatedEpisodes = doc.Root.Elements("Episode") + .Select(elt => new UpdatedEpisode + { + Id = int.Parse(elt.Element("id").Value), + SerieId = int.Parse(elt.Element("Series").Value), + Time = int.Parse(elt.Element("time").Value) + }) + .ToList(), + UpdatedBanners = doc.Root.Elements("Banner") + .Select(elt => new UpdatedBanner + { + SerieId = int.Parse(elt.Element("Series").Value), + Format = elt.Element("format").Value, + Language = + elt.Elements("language").Select(n => n.Value).FirstOrDefault() ?? string.Empty, + Path = elt.Element("path").Value, + Type = elt.Element("type").Value, + SeasonNumber = elt.Elements("SeasonNumber").Any() + ? int.Parse(elt.Element("SeasonNumber").Value) + : (int?) null, + Time = int.Parse(elt.Element("time").Value) + }) + .ToList() + }; + } + } + + public Updates GetResult() + { + return _updates; + } + } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/DAO/DataProvider.cs b/src/TVDBSharp/Models/DAO/DataProvider.cs new file mode 100644 index 000000000..e79a05f10 --- /dev/null +++ b/src/TVDBSharp/Models/DAO/DataProvider.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Net; +using System.Xml.Linq; +using TVDBSharp.Models.Enums; + +namespace TVDBSharp.Models.DAO +{ + /// + /// Standard implementation of the interface. + /// + public class DataProvider : IDataProvider + { + public string ApiKey { get; set; } + private const string BaseUrl = "http://thetvdb.com"; + + public XDocument GetShow(int showID) + { + return GetXDocumentFromUrl(string.Format("{0}/api/{1}/series/{2}/all/", BaseUrl, ApiKey, showID)); + } + + public XDocument GetEpisode(int episodeId, string lang) + { + return GetXDocumentFromUrl(string.Format("{0}/api/{1}/episodes/{2}/{3}.xml", BaseUrl, ApiKey, episodeId, lang)); + } + + public XDocument GetUpdates(Interval interval) + { + return GetXDocumentFromUrl(string.Format("{0}/api/{1}/updates/updates_{2}.xml", BaseUrl, ApiKey, IntervalHelpers.Print(interval))); + } + + public XDocument Search(string query) + { + return GetXDocumentFromUrl(string.Format("{0}/api/GetSeries.php?seriesname={1}", BaseUrl, query)); + } + + private static XDocument GetXDocumentFromUrl(string url) + { + using (var web = new WebClient()) + using (var memoryStream = new MemoryStream(web.DownloadData(url))) + return XDocument.Load(memoryStream); + } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/DAO/IDataProvider.cs b/src/TVDBSharp/Models/DAO/IDataProvider.cs new file mode 100644 index 000000000..58eb3981b --- /dev/null +++ b/src/TVDBSharp/Models/DAO/IDataProvider.cs @@ -0,0 +1,45 @@ +using System.Xml.Linq; +using TVDBSharp.Models.Enums; + +namespace TVDBSharp.Models.DAO +{ + /// + /// Defines a Dataprovider API. + /// + public interface IDataProvider + { + /// + /// The API key provided by TVDB. + /// + string ApiKey { get; set; } + + /// + /// Retrieves the show with the given id and returns the corresponding XML tree. + /// + /// ID of the show you wish to lookup. + /// Returns an XML tree of the show object. + XDocument GetShow(int showID); + + /// + /// Retrieves the episode with the given id and returns the corresponding XML tree. + /// + /// ID of the episode to retrieve + /// ISO 639-1 language code of the episode + /// XML tree of the episode object + XDocument GetEpisode(int episodeId, string lang); + + /// + /// Retrieves updates on tvdb (Shows, Episodes and Banners) + /// + /// The interval for the updates + /// XML tree of the Updates object + XDocument GetUpdates(Interval interval); + + /// + /// Returns an XML tree representing a search query for the given parameter. + /// + /// Query to perform the search with. + /// Returns an XML tree of a search result. + XDocument Search(string query); + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Enums/ContentRating.cs b/src/TVDBSharp/Models/Enums/ContentRating.cs new file mode 100644 index 000000000..8c1b43867 --- /dev/null +++ b/src/TVDBSharp/Models/Enums/ContentRating.cs @@ -0,0 +1,43 @@ +namespace TVDBSharp.Models.Enums +{ + /// + /// Different content ratings. View http://en.wikipedia.org/wiki/TV_Parental_Guidelines for more info. + /// + public enum ContentRating + { + /// + /// Not suitable for children under 14. + /// + TV14, + + /// + /// This program contains material that parents may find unsuitable for younger children. + /// + TVPG, + + /// + /// This program is designed to be appropriate for all children. + /// + TVY, + + /// + /// This program is designed for children age 7 and above. + /// + TVY7, + + /// + /// Most parents would find this program suitable for all ages. + /// + TVG, + + /// + /// This program is specifically designed to be viewed by adults and therefore may be unsuitable for children under 17. + /// + TVMA, + + /// + /// Default value if no rating is given. + /// + Unknown + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Enums/Frequency.cs b/src/TVDBSharp/Models/Enums/Frequency.cs new file mode 100644 index 000000000..f59ddb192 --- /dev/null +++ b/src/TVDBSharp/Models/Enums/Frequency.cs @@ -0,0 +1,14 @@ +namespace TVDBSharp.Models.Enums +{ + public enum Frequency + { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, + Daily + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Enums/Interval.cs b/src/TVDBSharp/Models/Enums/Interval.cs new file mode 100644 index 000000000..9783a25e2 --- /dev/null +++ b/src/TVDBSharp/Models/Enums/Interval.cs @@ -0,0 +1,32 @@ +using System; + +namespace TVDBSharp.Models.Enums +{ + public enum Interval + { + Day, + Week, + Month, + All + } + + public static class IntervalHelpers + { + public static string Print(Interval interval) + { + switch (interval) + { + case Interval.Day: + return "day"; + case Interval.Week: + return "week"; + case Interval.Month: + return "month"; + case Interval.All: + return "all"; + default: + throw new ArgumentException("Unsupported interval enum: " + interval); + } + } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Enums/Status.cs b/src/TVDBSharp/Models/Enums/Status.cs new file mode 100644 index 000000000..1cbe6ab07 --- /dev/null +++ b/src/TVDBSharp/Models/Enums/Status.cs @@ -0,0 +1,23 @@ +namespace TVDBSharp.Models.Enums +{ + /// + /// Describes the current status of a show. + /// + public enum Status + { + /// + /// No more episodes are being released. + /// + Ended, + + /// + /// The show is ongoing. + /// + Continuing, + + /// + /// Default value if no status is specified. + /// + Unknown + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Episode.cs b/src/TVDBSharp/Models/Episode.cs new file mode 100644 index 000000000..8ec6c9501 --- /dev/null +++ b/src/TVDBSharp/Models/Episode.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; + +namespace TVDBSharp.Models +{ + /// + /// Entity describing an episode of a show. + /// + public class Episode + { + /// + /// Unique identifier for an episode. + /// + public int Id { get; set; } + + /// + /// Director of the episode. + /// + public string Director { get; set; } + + /// + /// This episode's title. + /// + public string Title { get; set; } + + /// + /// This episode's number in the appropriate season. + /// + public int EpisodeNumber { get; set; } + + /// + /// This episode's season. + /// + public int SeasonNumber { get; set; } + + /// + /// The date of the first time this episode has aired. + /// + public DateTime? FirstAired { get; set; } + + /// + /// A list of guest stars. + /// + public List GuestStars { get; set; } + + /// + /// Unique identifier on IMDb. + /// + public string ImdbId { get; set; } + + /// + /// Main language spoken in the episode. + /// + public string Language { get; set; } + + /// + /// A short description of the episode. + /// + public string Description { get; set; } + + /// + /// Average rating as shown on IMDb. + /// + public double? Rating { get; set; } + + /// + /// Amount of votes cast. + /// + public int RatingCount { get; set; } + + /// + /// Writers(s) of the episode. + /// + public List Writers { get; set; } + + /// + /// Let me know if you find out what this is. + /// + public Uri EpisodeImage { get; set; } + + /// + /// Timestamp of the last update to this episode. + /// + public long? LastUpdated { get; set; } + + /// + /// Unique identifier of the season. + /// + public int SeasonId { get; set; } + + /// + /// Unique identifier of the show. + /// + public int SeriesId { get; set; } + + /// + /// Height dimension of the thumbnail in pixels. + /// + public int? ThumbHeight { get; set; } + + /// + /// Width dimension of the thumbnail in pixels; + /// + public int? ThumbWidth { get; set; } + + /// + /// Let me know if you find out what this is. + /// + public string TmsExport { get; set; } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Show.cs b/src/TVDBSharp/Models/Show.cs new file mode 100644 index 000000000..ee1ec4463 --- /dev/null +++ b/src/TVDBSharp/Models/Show.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using TVDBSharp.Models.Enums; + +namespace TVDBSharp.Models +{ + /// + /// Entity describing a show. + /// + public class Show + { + /// + /// Unique identifier used by IMDb. + /// + public string ImdbId { get; set; } + + /// + /// Unique identifier used by TVDB and TVDBSharp. + /// + public int Id { get; set; } + + /// + /// List of all actors in the show. + /// + public List Actors { get; set; } + + /// + /// Day of the week when the show airs. + /// + public Frequency? AirDay { get; set; } + + /// + /// Time of the day when the show airs. + /// + public TimeSpan? AirTime { get; set; } + + /// + /// Rating of the content provided by an official organ. + /// + public ContentRating ContentRating { get; set; } + + /// + /// The date the show aired for the first time. + /// + public DateTime? FirstAired { get; set; } + + /// + /// A list of genres the show is associated with. + /// + public List Genres { get; set; } + + /// + /// Main language of the show. + /// + public string Language { get; set; } + + /// + /// Network that broadcasts the show. + /// + public string Network { get; set; } + + /// + /// A short overview of the show. + /// + public string Description { get; set; } + + /// + /// Average rating as shown on IMDb. + /// + public double? Rating { get; set; } + + /// + /// Amount of votes cast. + /// + public int RatingCount { get; set; } + + /// + /// Let me know if you find out what this is. + /// + public int? Runtime { get; set; } + + /// + /// Name of the show. + /// + public string Name { get; set; } + + /// + /// Current status of the show. + /// + public Status Status { get; set; } + + /// + /// Link to the banner image. + /// + public Uri Banner { get; set; } + + /// + /// Link to a fanart image. + /// + public Uri Fanart { get; set; } + + /// + /// Timestamp of the latest update. + /// + public long? LastUpdated { get; set; } + + /// + /// Let me know if you find out what this is. + /// + public Uri Poster { get; set; } + + /// + /// No clue + /// + public string Zap2ItID { get; set; } + + /// + /// A list of all episodes associated with this show. + /// + public List Episodes { get; set; } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Models/Updates.cs b/src/TVDBSharp/Models/Updates.cs new file mode 100644 index 000000000..743def719 --- /dev/null +++ b/src/TVDBSharp/Models/Updates.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace TVDBSharp.Models +{ + public class Updates : UnixTimestampedObject + { + public List UpdatedBanners { get; set; } + public List UpdatedEpisodes { get; set; } + public List UpdatedSeries { get; set; } + } + + public class UnixTimestampedObject + { + private static DateTime _startDate = new DateTime(1970, 1, 1); + private int _unixTimestamp; + + public DateTime Timestamp + { + get { return _startDate.AddSeconds(_unixTimestamp); } + } + + public int Time + { + set { _unixTimestamp = value; } + } + } + + public class UpdatedSerie : UnixTimestampedObject + { + public int Id { get; set; } + } + + public class UpdatedEpisode : UnixTimestampedObject + { + public int Id { get; set; } + public int SerieId { get; set; } + } + + public class UpdatedBanner : UnixTimestampedObject + { + public int SerieId { get; set; } + public string Format { get; set; } + public string Language { get; set; } + public string Path { get; set; } + public string Type { get; set; } + public int? SeasonNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Properties/AssemblyInfo.cs b/src/TVDBSharp/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6b7f6448f --- /dev/null +++ b/src/TVDBSharp/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("TVDBSharp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TVDBSharp")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("c78961a8-afda-4a36-910b-bf5a090eebb3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/src/TVDBSharp/TVDB.cs b/src/TVDBSharp/TVDB.cs new file mode 100644 index 000000000..2566c0943 --- /dev/null +++ b/src/TVDBSharp/TVDB.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using TVDBSharp.Models; +using TVDBSharp.Models.DAO; +using TVDBSharp.Models.Enums; + +namespace TVDBSharp +{ + /// + /// The main class which will handle all user interaction. + /// + public class TVDB + { + private readonly IDataProvider _dataProvider; + + /// + /// Creates a new instance with the provided API key and dataProvider. + /// + /// The API key provided by TVDB. + /// Specify your own instance. + public TVDB(string apiKey, IDataProvider dataProvider) + { + _dataProvider = dataProvider; + _dataProvider.ApiKey = apiKey; + } + + /// + /// Creates a new instance with the provided API key and standard . + /// + /// The API key provided by TVDB. + public TVDB(string apiKey) + { + _dataProvider = new DataProvider {ApiKey = apiKey}; + } + + /// + /// Search for a show in the database. + /// + /// Query that identifies the show. + /// Maximal amount of results in the returning set. Default is 5. + /// Returns a list of shows. + public List Search(string query, int results = 5) + { + return new Builder(_dataProvider).Search(query, results); + } + + /// + /// Get a specific show based on its ID. + /// + /// ID of the show. + /// Returns the corresponding show. + public Show GetShow(int showId) + { + return new Builder(_dataProvider).BuildShow(showId); + } + + /// + /// Get a specific episode based on its ID. + /// + /// ID of the episode + /// ISO 639-1 language code for the episode + /// The corresponding episode + public Episode GetEpisode(int episodeId, string lang = "en") + { + return new Builder(_dataProvider).BuildEpisode(episodeId, lang); + } + + public Updates GetUpdates(Interval interval) + { + return new Builder(_dataProvider).BuildUpdates(interval); + } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/TVDBSharp.csproj b/src/TVDBSharp/TVDBSharp.csproj new file mode 100644 index 000000000..7f2ce9bae --- /dev/null +++ b/src/TVDBSharp/TVDBSharp.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {0CC493D7-0A9F-4199-9615-0A977945D716} + {0CC493D7-0A9F-4199-9615-0A977945D716} + Library + Properties + TVDBSharp + TVDBSharp + v4.0 + 512 + + + + + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/TVDBSharp/Utilities/Extensions.cs b/src/TVDBSharp/Utilities/Extensions.cs new file mode 100644 index 000000000..641413717 --- /dev/null +++ b/src/TVDBSharp/Utilities/Extensions.cs @@ -0,0 +1,58 @@ +using System.Xml.Linq; +using System.Xml.Schema; + +namespace TVDBSharp.Utilities +{ + /// + /// Extension methods used to simplify data extraction. + /// + public static class Extensions + { + /// + /// Retrieves a value from an XML tree representing a show. + /// + /// XML tree representing a show. + /// Name of the element with the data. + /// Returns the value corresponding to the given element name. + /// Thrown when the element doesn't exist or the XML tree is incorrect. + public static string GetSeriesData(this XDocument doc, string element) + { + var root = doc.Element("Data"); + if (root != null) + { + var xElement = root.Element("Series"); + if (xElement != null) + { + var result = xElement.Element(element); + if (result != null) + { + return result.Value; + } + throw new XmlSchemaException("Could not find element <" + element + ">"); + } + throw new XmlSchemaException("Could not find element "); + } + throw new XmlSchemaException("Could not find element "); + } + + /// + /// Retrieves a value from an XML tree. + /// + /// The given XML (sub)tree. + /// Name of the element with the data. + /// Returns the value corresponding to the given element name; + /// Thrown when the element doesn't exist. + public static string GetXmlData(this XElement xmlObject, string element) + { + var result = xmlObject.Element(element); + + return result != null ? result.Value : null; + + // Removed in favor of returning a null value + // This will allow us to catch a non-existing tag with the null-coalescing operator + // Never trust the XML provider. + + //throw new XmlSchemaException("Element <" + element + "> could not be found."); + } + } +} \ No newline at end of file diff --git a/src/TVDBSharp/Utilities/Utils.cs b/src/TVDBSharp/Utilities/Utils.cs new file mode 100644 index 000000000..5a3280da3 --- /dev/null +++ b/src/TVDBSharp/Utilities/Utils.cs @@ -0,0 +1,73 @@ +using System; +using System.Globalization; +using TVDBSharp.Models.Enums; + +namespace TVDBSharp.Utilities +{ + /// + /// Provides static utility methods. + /// + public static class Utils + { + /// + /// Parses a string of format yyyy-MM-dd to a object. + /// + /// String to be parsed. + /// Returns a representation. + public static DateTime ParseDate(string value) + { + DateTime date; + DateTime.TryParseExact(value, "yyyy-MM-dd", CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out date); + return date; + } + + /// + /// Parses a string of format hh:mm tt to a object. + /// + /// String to be parsed. + /// Returns a representation. + public static TimeSpan ParseTime(string value) + { + DateTime date; + + if (!DateTime.TryParse(value, out date)) + { + return new TimeSpan(); + } + return date.TimeOfDay; + } + + /// + /// Translates the incoming string to a enum, if applicable. + /// + /// The rating in string format. + /// Returns the appropriate value. + /// Throws an exception if no conversion could be applied. + public static ContentRating GetContentRating(string rating) + { + switch (rating) + { + case "TV-14": + return ContentRating.TV14; + + case "TV-PG": + return ContentRating.TVPG; + + case "TV-Y": + return ContentRating.TVY; + + case "TV-Y7": + return ContentRating.TVY7; + + case "TV-G": + return ContentRating.TVG; + + case "TV-MA": + return ContentRating.TVMA; + + default: + return ContentRating.Unknown; + } + } + } +} \ No newline at end of file