diff options
Diffstat (limited to 'Emby.Server.Implementations/LiveTv/Listings')
35 files changed, 1604 insertions, 770 deletions
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0bbffb824b..1f963e4a29 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,44 +1,61 @@ +#nullable disable + +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common; +using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.Listings { public class SchedulesDirect : IListingsProvider { - private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - private readonly IHttpClient _httpClient; + private const string ApiUrl = "https://json.schedulesdirect.org/20141201"; + + private readonly ILogger<SchedulesDirect> _logger; + private readonly IHttpClientFactory _httpClientFactory; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); - private readonly IApplicationHost _appHost; + private readonly ICryptoProvider _cryptoProvider; - private const string ApiUrl = "https://json.schedulesdirect.org/20141201"; + private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private DateTime _lastErrorResponse; - public SchedulesDirect(ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IApplicationHost appHost) + public SchedulesDirect( + ILogger<SchedulesDirect> logger, + IHttpClientFactory httpClientFactory, + ICryptoProvider cryptoProvider) { _logger = logger; - _jsonSerializer = jsonSerializer; - _httpClient = httpClient; - _appHost = appHost; + _httpClientFactory = httpClientFactory; + _cryptoProvider = cryptoProvider; } - private string UserAgent => _appHost.ApplicationUserAgent; + /// <inheritdoc /> + public string Name => "Schedules Direct"; + + /// <inheritdoc /> + public string Type => nameof(SchedulesDirect); private static List<string> GetScheduleRequestDates(DateTime startDateUtc, DateTime endDateUtc) { @@ -49,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings while (start <= end) { - dates.Add(start.ToString("yyyy-MM-dd")); + dates.Add(start.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); start = start.AddDays(1); } @@ -78,173 +95,170 @@ namespace Emby.Server.Implementations.LiveTv.Listings var dates = GetScheduleRequestDates(startDateUtc, endDateUtc); _logger.LogInformation("Channel Station ID is: {ChannelID}", channelId); - var requestList = new List<ScheduleDirect.RequestScheduleForChannel>() + var requestList = new List<RequestScheduleForChannelDto>() { - new ScheduleDirect.RequestScheduleForChannel() + new RequestScheduleForChannelDto() { - stationID = channelId, - date = dates + StationId = channelId, + Date = dates } }; - var requestString = _jsonSerializer.SerializeToString(requestList); + var requestString = JsonSerializer.Serialize(requestList, _jsonOptions); _logger.LogDebug("Request string for schedules is: {RequestString}", requestString); - var httpOptions = new HttpRequestOptions() + using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules"); + options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json); + options.Headers.TryAddWithoutValidation("token", token); + using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var dailySchedules = await JsonSerializer.DeserializeAsync<IReadOnlyList<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (dailySchedules == null) { - Url = ApiUrl + "/schedules", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - // The data can be large so give it some extra time - TimeoutMs = 60000, - LogErrorResponseBody = true, - RequestContent = requestString - }; - - httpOptions.RequestHeaders["token"] = token; - - using (var response = await Post(httpOptions, true, info).ConfigureAwait(false)) - using (var reader = new StreamReader(response.Content)) - { - var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(response.Content).ConfigureAwait(false); - _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); + return Array.Empty<ProgramInfo>(); + } - httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/programs", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 - }; + _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); - httpOptions.RequestHeaders["token"] = token; + using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); + programRequestOptions.Headers.TryAddWithoutValidation("token", token); - var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct(); - httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]"; + var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); + programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions)); + programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); - using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false)) - using (var innerReader = new StreamReader(innerResponse.Content)) - { - var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponse.Content).ConfigureAwait(false); - var programDict = programDetails.ToDictionary(p => p.programID, y => y); + using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); + await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var programDetails = await JsonSerializer.DeserializeAsync<IReadOnlyList<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (programDetails == null) + { + return Array.Empty<ProgramInfo>(); + } - var programIdsWithImages = - programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) - .ToList(); + var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y); - var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); + var programIdsWithImages = programDetails + .Where(p => p.HasImageArtwork).Select(p => p.ProgramId) + .ToList(); - var programsInfo = new List<ProgramInfo>(); - foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) - { - //_logger.LogDebug("Proccesing Schedule for statio ID " + stationID + - // " which corresponds to channel " + channelNumber + " and program id " + - // schedule.programID + " which says it has images? " + - // programDict[schedule.programID].hasImageArtwork); + var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); - if (images != null) - { - var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); - if (imageIndex > -1) - { - var programEntry = programDict[schedule.programID]; + var programsInfo = new List<ProgramInfo>(); + foreach (ProgramDto schedule in dailySchedules.SelectMany(d => d.Programs)) + { + // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + + // " which corresponds to channel " + channelNumber + " and program id " + + // schedule.ProgramId + " which says it has images? " + + // programDict[schedule.ProgramId].hasImageArtwork); - var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>(); - var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)); - var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)); + if (string.IsNullOrEmpty(schedule.ProgramId)) + { + continue; + } - const double desiredAspect = 0.666666667; + if (images != null) + { + var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]); + if (imageIndex > -1) + { + var programEntry = programDict[schedule.ProgramId]; - programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, desiredAspect) ?? - GetProgramImage(ApiUrl, allImages, true, desiredAspect); + var allImages = images[imageIndex].Data; + var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)); + var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)); - const double wideAspect = 1.77777778; + const double DesiredAspect = 2.0 / 3; - programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, wideAspect); + programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? + GetProgramImage(ApiUrl, allImages, true, DesiredAspect); - // Don't supply the same image twice - if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal)) - { - programEntry.thumbImage = null; - } + const double WideAspect = 16.0 / 9; - programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, wideAspect); + programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); - //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? - // GetProgramImage(ApiUrl, data, "Banner-LOT", false); - } + // Don't supply the same image twice + if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal)) + { + programEntry.ThumbImage = null; } - programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); + programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); + + // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LOT", false); } - return programsInfo; } + + programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.ProgramId])); } + + return programsInfo; } - private static int GetSizeOrder(ScheduleDirect.ImageData image) + private static int GetSizeOrder(ImageDataDto image) { - if (!string.IsNullOrWhiteSpace(image.height)) + if (int.TryParse(image.Height, out int value)) { - if (int.TryParse(image.height, out int value)) - { - return value; - } + return value; } return 0; } - private static string GetChannelNumber(ScheduleDirect.Map map) + private static string GetChannelNumber(MapDto map) { - var channelNumber = map.logicalChannelNumber; + var channelNumber = map.LogicalChannelNumber; if (string.IsNullOrWhiteSpace(channelNumber)) { - channelNumber = map.channel; + channelNumber = map.Channel; } + if (string.IsNullOrWhiteSpace(channelNumber)) { - channelNumber = map.atscMajor + "." + map.atscMinor; + channelNumber = map.AtscMajor + "." + map.AtscMinor; } return channelNumber.TrimStart('0'); } - private static bool IsMovie(ScheduleDirect.ProgramDetails programInfo) + private static bool IsMovie(ProgramDetailsDto programInfo) { - return string.Equals(programInfo.entityType, "movie", StringComparison.OrdinalIgnoreCase); + return string.Equals(programInfo.EntityType, "movie", StringComparison.OrdinalIgnoreCase); } - private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details) + private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details) { - var startAt = GetDate(programInfo.airDateTime); - var endAt = startAt.AddSeconds(programInfo.duration); + if (programInfo.AirDateTime == null) + { + return null; + } + + var startAt = programInfo.AirDateTime.Value; + var endAt = startAt.AddSeconds(programInfo.Duration); var audioType = ProgramAudio.Stereo; - var programId = programInfo.programID ?? string.Empty; + var programId = programInfo.ProgramId ?? string.Empty; string newID = programId + "T" + startAt.Ticks + "C" + channelId; - if (programInfo.audioProperties != null) + if (programInfo.AudioProperties.Count != 0) { - if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.Atmos; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.DolbyDigital; } - else if (programInfo.audioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase))) + else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase)) { audioType = ProgramAudio.Stereo; } @@ -255,9 +269,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings } string episodeTitle = null; - if (details.episodeTitle150 != null) + if (details.EpisodeTitle150 != null) { - episodeTitle = details.episodeTitle150; + episodeTitle = details.EpisodeTitle150; } var info = new ProgramInfo @@ -266,22 +280,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings Id = newID, StartDate = startAt, EndDate = endAt, - Name = details.titles[0].title120 ?? "Unkown", + Name = details.Titles[0].Title120 ?? "Unknown", OfficialRating = null, CommunityRating = null, EpisodeTitle = episodeTitle, Audio = audioType, - //IsNew = programInfo.@new ?? false, - IsRepeat = programInfo.@new == null, - IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase), - ImageUrl = details.primaryImage, - ThumbImageUrl = details.thumbImage, - IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase), - IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase), + // IsNew = programInfo.@new ?? false, + IsRepeat = programInfo.New == null, + IsSeries = string.Equals(details.EntityType, "episode", StringComparison.OrdinalIgnoreCase), + ImageUrl = details.PrimaryImage, + ThumbImageUrl = details.ThumbImage, + IsKids = string.Equals(details.Audience, "children", StringComparison.OrdinalIgnoreCase), + IsSports = string.Equals(details.EntityType, "sports", StringComparison.OrdinalIgnoreCase), IsMovie = IsMovie(details), - Etag = programInfo.md5, - IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), - IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 + Etag = programInfo.Md5, + IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), + IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 }; var showId = programId; @@ -304,15 +318,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings info.ShowId = showId; - if (programInfo.videoProperties != null) + if (programInfo.VideoProperties != null) { - info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); - info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); + info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); + info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); } - if (details.contentRating != null && details.contentRating.Count > 0) + if (details.ContentRating != null && details.ContentRating.Count > 0) { - info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-").Replace("--", "-"); + info.OfficialRating = details.ContentRating[0].Code.Replace("TV", "TV-", StringComparison.Ordinal) + .Replace("--", "-", StringComparison.Ordinal); var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" }; if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase)) @@ -321,15 +336,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - if (details.descriptions != null) + if (details.Descriptions != null) { - if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0) + if (details.Descriptions.Description1000 != null && details.Descriptions.Description1000.Count > 0) { - info.Overview = details.descriptions.description1000[0].description; + info.Overview = details.Descriptions.Description1000[0].Description; } - else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0) + else if (details.Descriptions.Description100 != null && details.Descriptions.Description100.Count > 0) { - info.Overview = details.descriptions.description100[0].description; + info.Overview = details.Descriptions.Description100[0].Description; } } @@ -337,20 +352,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings { info.SeriesId = programId.Substring(0, 10); - info.SeriesProviderIds[MetadataProviders.Zap2It.ToString()] = info.SeriesId; + info.SeriesProviderIds[MetadataProvider.Zap2It.ToString()] = info.SeriesId; - if (details.metadata != null) + if (details.Metadata != null) { - foreach (var metadataProgram in details.metadata) + foreach (var metadataProgram in details.Metadata) { var gracenote = metadataProgram.Gracenote; if (gracenote != null) { - info.SeasonNumber = gracenote.season; + info.SeasonNumber = gracenote.Season; - if (gracenote.episode > 0) + if (gracenote.Episode > 0) { - info.EpisodeNumber = gracenote.episode; + info.EpisodeNumber = gracenote.Episode; } break; @@ -359,24 +374,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - if (!string.IsNullOrWhiteSpace(details.originalAirDate)) + if (details.OriginalAirDate != null) { - info.OriginalAirDate = DateTime.Parse(details.originalAirDate); + info.OriginalAirDate = details.OriginalAirDate; info.ProductionYear = info.OriginalAirDate.Value.Year; } - if (details.movie != null) + if (details.Movie != null) { - if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year)) + if (!string.IsNullOrEmpty(details.Movie.Year) + && int.TryParse(details.Movie.Year, out int year)) { info.ProductionYear = year; } } - if (details.genres != null) + if (details.Genres != null) { - info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); - info.IsNews = details.genres.Contains("news", StringComparer.OrdinalIgnoreCase); + info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(); + info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase); if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase)) { @@ -387,22 +403,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings return info; } - private static DateTime GetDate(string value) - { - var date = DateTime.ParseExact(value, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", CultureInfo.InvariantCulture); - - if (date.Kind != DateTimeKind.Utc) - { - date = DateTime.SpecifyKind(date, DateTimeKind.Utc); - } - return date; - } - - private string GetProgramImage(string apiUrl, IEnumerable<ScheduleDirect.ImageData> images, bool returnDefaultImage, double desiredAspect) + private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect) { var match = images .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i))) - .ThenByDescending(GetSizeOrder) + .ThenByDescending(i => GetSizeOrder(i)) .FirstOrDefault(); if (match == null) @@ -410,7 +415,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return null; } - var uri = match.uri; + var uri = match.Uri; if (string.IsNullOrWhiteSpace(uri)) { @@ -426,19 +431,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - private static double GetAspectRatio(ScheduleDirect.ImageData i) + private static double GetAspectRatio(ImageDataDto i) { int width = 0; int height = 0; - if (!string.IsNullOrWhiteSpace(i.width)) + if (!string.IsNullOrWhiteSpace(i.Width)) { - int.TryParse(i.width, out width); + _ = int.TryParse(i.Width, out width); } - if (!string.IsNullOrWhiteSpace(i.height)) + if (!string.IsNullOrWhiteSpace(i.Height)) { - int.TryParse(i.height, out height); + _ = int.TryParse(i.Height, out height); } if (height == 0 || width == 0) @@ -451,60 +456,50 @@ namespace Emby.Server.Implementations.LiveTv.Listings return result; } - private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms( + private async Task<IReadOnlyList<ShowImagesDto>> GetImageForPrograms( ListingsProviderInfo info, - List<string> programIds, - CancellationToken cancellationToken) + IReadOnlyList<string> programIds, + CancellationToken cancellationToken) { if (programIds.Count == 0) { - return new List<ScheduleDirect.ShowImages>(); + return Array.Empty<ShowImagesDto>(); } - var imageIdString = "["; - - foreach (var i in programIds) + StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); + foreach (ReadOnlySpan<char> i in programIds) { - var imageId = i.Substring(0, 10); - - if (!imageIdString.Contains(imageId)) - { - imageIdString += "\"" + imageId + "\","; - } + str.Append('"') + .Append(i.Slice(0, 10)) + .Append("\","); } - imageIdString = imageIdString.TrimEnd(',') + "]"; + // Remove last , + str.Length--; + str.Append(']'); - var httpOptions = new HttpRequestOptions() + using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs") { - Url = ApiUrl + "/metadata/programs", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - RequestContent = imageIdString, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 + Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json) }; try { - using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false)) - { - return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>( - innerResponse2.Content).ConfigureAwait(false); - } + using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); + await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return await JsonSerializer.DeserializeAsync<IReadOnlyList<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error getting image info from schedules direct"); - return new List<ScheduleDirect.ShowImages>(); + return Array.Empty<ShowImagesDto>(); } } public async Task<List<NameIdPair>> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); var lineups = new List<NameIdPair>(); @@ -513,41 +508,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings return lineups; } - var options = new HttpRequestOptions() - { - Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - - options.RequestHeaders["token"] = token; + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/headends?country=" + country + "&postalcode=" + location); + options.Headers.TryAddWithoutValidation("token", token); try { - using (var httpResponse = await Get(options, false, info).ConfigureAwait(false)) - using (Stream responce = httpResponse.Content) - { - var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(responce).ConfigureAwait(false); + using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); + await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + + var root = await JsonSerializer.DeserializeAsync<IReadOnlyList<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (root != null) + if (root != null) + { + foreach (HeadendsDto headend in root) { - foreach (ScheduleDirect.Headends headend in root) + foreach (LineupDto lineup in headend.Lineups) { - foreach (ScheduleDirect.Lineup lineup in headend.lineups) + lineups.Add(new NameIdPair { - lineups.Add(new NameIdPair - { - Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, - Id = lineup.uri.Substring(18) - }); - } + Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name, + Id = lineup.Uri?[18..] + }); } } - else - { - _logger.LogInformation("No lineups available"); - } + } + else + { + _logger.LogInformation("No lineups available"); } } catch (Exception ex) @@ -558,8 +545,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings return lineups; } - private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); - private DateTime _lastErrorResponse; private async Task<string> GetToken(ListingsProviderInfo info, CancellationToken cancellationToken) { var username = info.Username; @@ -582,8 +567,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return null; } - NameValuePair savedToken = null; - if (!_tokens.TryGetValue(username, out savedToken)) + if (!_tokens.TryGetValue(username, out NameValuePair savedToken)) { savedToken = new NameValuePair(); _tokens.TryAdd(username, savedToken); @@ -609,7 +593,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings savedToken.Value = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture); return result; } - catch (HttpException ex) + catch (HttpRequestException ex) { if (ex.StatusCode.HasValue) { @@ -619,6 +603,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings _lastErrorResponse = DateTime.UtcNow; } } + throw; } finally @@ -627,112 +612,64 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } - private async Task<HttpResponseInfo> Post(HttpRequestOptions options, + private async Task<HttpResponseMessage> Send( + HttpRequestMessage options, bool enableRetry, - ListingsProviderInfo providerInfo) + ListingsProviderInfo providerInfo, + CancellationToken cancellationToken, + HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false); + if (response.IsSuccessStatusCode) { - options.RequestHeaders["Accept-Encoding"] = "deflate"; + return response; } -#endif - try + // Response is automatically disposed in the calling function, + // so dispose manually if not returning. + response.Dispose(); + if (!enableRetry || (int)response.StatusCode >= 500) { - return await _httpClient.Post(options).ConfigureAwait(false); + throw new HttpRequestException( + string.Format(CultureInfo.InvariantCulture, "Request failed: {0}", response.ReasonPhrase), + null, + response.StatusCode); } - catch (HttpException ex) - { - _tokens.Clear(); - if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500) - { - enableRetry = false; - } - - if (!enableRetry) - { - throw; - } - } - - options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false); - return await Post(options, false, providerInfo).ConfigureAwait(false); + _tokens.Clear(); + options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); + return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false); } - private async Task<HttpResponseInfo> Get(HttpRequestOptions options, - bool enableRetry, - ListingsProviderInfo providerInfo) - { - // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - options.RequestHeaders["Accept-Encoding"] = "deflate"; - } -#endif - - try - { - return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); - } - catch (HttpException ex) - { - _tokens.Clear(); - - if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500) - { - enableRetry = false; - } - - if (!enableRetry) - { - throw; - } - } - - options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false); - return await Get(options, false, providerInfo).ConfigureAwait(false); - } - - private async Task<string> GetTokenInternal(string username, string password, + private async Task<string> GetTokenInternal( + string username, + string password, CancellationToken cancellationToken) { - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/token", - UserAgent = UserAgent, - RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - //_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + - // httpOptions.RequestContent); + using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); + var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty<byte>()); + // TODO: remove ToLower when Convert.ToHexString supports lowercase + // Schedules Direct requires the hex to be lowercase + string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); + options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); - using (var response = await Post(httpOptions, false, null).ConfigureAwait(false)) + using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (string.Equals(root?.Message, "OK", StringComparison.Ordinal)) { - var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(response.Content).ConfigureAwait(false); - if (root.message == "OK") - { - _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token); - return root.token; - } - - throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message); + _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token); + return root.Token; } + + throw new Exception("Could not authenticate with Schedules Direct Error: " + root.Message); } private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -746,27 +683,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.LogInformation("Adding new LineUp "); - var httpOptions = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups/" + info.ListingsId, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - BufferContent = false - }; - - httpOptions.RequestHeaders["token"] = token; - - using (var response = await _httpClient.SendAsync(httpOptions, "PUT")) - { - } + using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId); + options.Headers.TryAddWithoutValidation("token", token); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); } - public string Name => "Schedules Direct"; - - public static string TypeName = "SchedulesDirect"; - public string Type => TypeName; - private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(info.ListingsId)) @@ -774,7 +695,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new ArgumentException("Listings Id required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -783,30 +704,23 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.LogInformation("Headends on account "); - var options = new HttpRequestOptions() - { - Url = ApiUrl + "/lineups", - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true - }; - - options.RequestHeaders["token"] = token; + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups"); + options.Headers.TryAddWithoutValidation("token", token); try { - using (var httpResponse = await Get(options, false, null).ConfigureAwait(false)) - using (var response = httpResponse.Content) - { - var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false); + using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false); + httpResponse.EnsureSuccessStatusCode(); + await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + using var response = httpResponse.Content; + var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); - } + return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false; } - catch (HttpException ex) + catch (HttpRequestException ex) { - // Apparently we're supposed to swallow this - if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest) + // SchedulesDirect returns 400 if no lineups are configured. + if (ex.StatusCode is HttpStatusCode.BadRequest) { return false; } @@ -823,11 +737,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings { throw new ArgumentException("Username is required"); } + if (string.IsNullOrEmpty(info.Password)) { throw new ArgumentException("Password is required"); } } + if (validateListings) { if (string.IsNullOrEmpty(info.ListingsId)) @@ -857,388 +773,62 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new Exception("ListingsId required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { throw new Exception("token required"); } - var httpOptions = new HttpRequestOptions() + using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId); + options.Headers.TryAddWithoutValidation("token", token); + + using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); + await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var root = await JsonSerializer.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (root == null) { - Url = ApiUrl + "/lineups/" + listingsId, - UserAgent = UserAgent, - CancellationToken = cancellationToken, - LogErrorResponseBody = true, - // The data can be large so give it some extra time - TimeoutMs = 60000 - }; + return new List<ChannelInfo>(); + } - httpOptions.RequestHeaders["token"] = token; + _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count); + _logger.LogInformation("Mapping Stations to Channel"); - var list = new List<ChannelInfo>(); + var allStations = root.Stations; - using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false)) - using (var response = httpResponse.Content) + var map = root.Map; + var list = new List<ChannelInfo>(map.Count); + foreach (var channel in map) { - var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false); - _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); - _logger.LogInformation("Mapping Stations to Channel"); + var channelNumber = GetChannelNumber(channel); - var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>(); + var stationIndex = allStations.FindIndex(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase)); + var station = stationIndex == -1 + ? new StationDto { StationId = channel.StationId } + : allStations[stationIndex]; - foreach (ScheduleDirect.Map map in root.map) + var channelInfo = new ChannelInfo { - var channelNumber = GetChannelNumber(map); - - var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station - { - stationID = map.stationID - }; - } - - var channelInfo = new ChannelInfo - { - Id = station.stationID, - CallSign = station.callsign, - Number = channelNumber, - Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name - }; - - if (station.logo != null) - { - channelInfo.ImageUrl = station.logo.URL; - } - - list.Add(channelInfo); - } - } - - return list; - } - - private ScheduleDirect.Station GetStation(List<ScheduleDirect.Station> allStations, string channelNumber, string channelName) - { - if (!string.IsNullOrWhiteSpace(channelName)) - { - channelName = NormalizeName(channelName); - - var result = allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); + Id = station.StationId, + CallSign = station.Callsign, + Number = channelNumber, + Name = string.IsNullOrWhiteSpace(station.Name) ? channelNumber : station.Name + }; - if (result != null) + if (station.Logo != null) { - return result; + channelInfo.ImageUrl = station.Logo.Url; } - } - if (!string.IsNullOrWhiteSpace(channelNumber)) - { - return allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase)); + list.Add(channelInfo); } - return null; + return list; } private static string NormalizeName(string value) { - return value.Replace(" ", string.Empty).Replace("-", string.Empty); - } - - public class ScheduleDirect - { - public class Token - { - public int code { get; set; } - public string message { get; set; } - public string serverID { get; set; } - public string token { get; set; } - } - public class Lineup - { - public string lineup { get; set; } - public string name { get; set; } - public string transport { get; set; } - public string location { get; set; } - public string uri { get; set; } - } - - public class Lineups - { - public int code { get; set; } - public string serverID { get; set; } - public string datetime { get; set; } - public List<Lineup> lineups { get; set; } - } - - - public class Headends - { - public string headend { get; set; } - public string transport { get; set; } - public string location { get; set; } - public List<Lineup> lineups { get; set; } - } - - - - public class Map - { - public string stationID { get; set; } - public string channel { get; set; } - public string logicalChannelNumber { get; set; } - public int uhfVhf { get; set; } - public int atscMajor { get; set; } - public int atscMinor { get; set; } - } - - public class Broadcaster - { - public string city { get; set; } - public string state { get; set; } - public string postalcode { get; set; } - public string country { get; set; } - } - - public class Logo - { - public string URL { get; set; } - public int height { get; set; } - public int width { get; set; } - public string md5 { get; set; } - } - - public class Station - { - public string stationID { get; set; } - public string name { get; set; } - public string callsign { get; set; } - public List<string> broadcastLanguage { get; set; } - public List<string> descriptionLanguage { get; set; } - public Broadcaster broadcaster { get; set; } - public string affiliate { get; set; } - public Logo logo { get; set; } - public bool? isCommercialFree { get; set; } - } - - public class Metadata - { - public string lineup { get; set; } - public string modified { get; set; } - public string transport { get; set; } - } - - public class Channel - { - public List<Map> map { get; set; } - public List<Station> stations { get; set; } - public Metadata metadata { get; set; } - } - - public class RequestScheduleForChannel - { - public string stationID { get; set; } - public List<string> date { get; set; } - } - - - - - public class Rating - { - public string body { get; set; } - public string code { get; set; } - } - - public class Multipart - { - public int partNumber { get; set; } - public int totalParts { get; set; } - } - - public class Program - { - public string programID { get; set; } - public string airDateTime { get; set; } - public int duration { get; set; } - public string md5 { get; set; } - public List<string> audioProperties { get; set; } - public List<string> videoProperties { get; set; } - public List<Rating> ratings { get; set; } - public bool? @new { get; set; } - public Multipart multipart { get; set; } - public string liveTapeDelay { get; set; } - public bool premiere { get; set; } - public bool repeat { get; set; } - public string isPremiereOrFinale { get; set; } - } - - - - public class MetadataSchedule - { - public string modified { get; set; } - public string md5 { get; set; } - public string startDate { get; set; } - public string endDate { get; set; } - public int days { get; set; } - } - - public class Day - { - public string stationID { get; set; } - public List<Program> programs { get; set; } - public MetadataSchedule metadata { get; set; } - - public Day() - { - programs = new List<Program>(); - } - } - - // - public class Title - { - public string title120 { get; set; } - } - - public class EventDetails - { - public string subType { get; set; } - } - - public class Description100 - { - public string descriptionLanguage { get; set; } - public string description { get; set; } - } - - public class Description1000 - { - public string descriptionLanguage { get; set; } - public string description { get; set; } - } - - public class DescriptionsProgram - { - public List<Description100> description100 { get; set; } - public List<Description1000> description1000 { get; set; } - } - - public class Gracenote - { - public int season { get; set; } - public int episode { get; set; } - } - - public class MetadataPrograms - { - public Gracenote Gracenote { get; set; } - } - - public class ContentRating - { - public string body { get; set; } - public string code { get; set; } - } - - public class Cast - { - public string billingOrder { get; set; } - public string role { get; set; } - public string nameId { get; set; } - public string personId { get; set; } - public string name { get; set; } - public string characterName { get; set; } - } - - public class Crew - { - public string billingOrder { get; set; } - public string role { get; set; } - public string nameId { get; set; } - public string personId { get; set; } - public string name { get; set; } - } - - public class QualityRating - { - public string ratingsBody { get; set; } - public string rating { get; set; } - public string minRating { get; set; } - public string maxRating { get; set; } - public string increment { get; set; } - } - - public class Movie - { - public string year { get; set; } - public int duration { get; set; } - public List<QualityRating> qualityRating { get; set; } - } - - public class Recommendation - { - public string programID { get; set; } - public string title120 { get; set; } - } - - public class ProgramDetails - { - public string audience { get; set; } - public string programID { get; set; } - public List<Title> titles { get; set; } - public EventDetails eventDetails { get; set; } - public DescriptionsProgram descriptions { get; set; } - public string originalAirDate { get; set; } - public List<string> genres { get; set; } - public string episodeTitle150 { get; set; } - public List<MetadataPrograms> metadata { get; set; } - public List<ContentRating> contentRating { get; set; } - public List<Cast> cast { get; set; } - public List<Crew> crew { get; set; } - public string entityType { get; set; } - public string showType { get; set; } - public bool hasImageArtwork { get; set; } - public string primaryImage { get; set; } - public string thumbImage { get; set; } - public string backdropImage { get; set; } - public string bannerImage { get; set; } - public string imageID { get; set; } - public string md5 { get; set; } - public List<string> contentAdvisory { get; set; } - public Movie movie { get; set; } - public List<Recommendation> recommendations { get; set; } - } - - public class Caption - { - public string content { get; set; } - public string lang { get; set; } - } - - public class ImageData - { - public string width { get; set; } - public string height { get; set; } - public string uri { get; set; } - public string size { get; set; } - public string aspect { get; set; } - public string category { get; set; } - public string text { get; set; } - public string primary { get; set; } - public string tier { get; set; } - public Caption caption { get; set; } - } - - public class ShowImages - { - public string programID { get; set; } - public List<ImageData> data { get; set; } - } - + return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal); } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs new file mode 100644 index 0000000000..95ac996e01 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Broadcaster dto. + /// </summary> + public class BroadcasterDto + { + /// <summary> + /// Gets or sets the city. + /// </summary> + [JsonPropertyName("city")] + public string? City { get; set; } + + /// <summary> + /// Gets or sets the state. + /// </summary> + [JsonPropertyName("state")] + public string? State { get; set; } + + /// <summary> + /// Gets or sets the postal code. + /// </summary> + [JsonPropertyName("postalCode")] + public string? Postalcode { get; set; } + + /// <summary> + /// Gets or sets the country. + /// </summary> + [JsonPropertyName("country")] + public string? Country { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs new file mode 100644 index 0000000000..f6251b9ad8 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Caption dto. + /// </summary> + public class CaptionDto + { + /// <summary> + /// Gets or sets the content. + /// </summary> + [JsonPropertyName("content")] + public string? Content { get; set; } + + /// <summary> + /// Gets or sets the lang. + /// </summary> + [JsonPropertyName("lang")] + public string? Lang { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs new file mode 100644 index 0000000000..0b7a2c63ad --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs @@ -0,0 +1,46 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Cast dto. + /// </summary> + public class CastDto + { + /// <summary> + /// Gets or sets the billing order. + /// </summary> + [JsonPropertyName("billingOrder")] + public string? BillingOrder { get; set; } + + /// <summary> + /// Gets or sets the role. + /// </summary> + [JsonPropertyName("role")] + public string? Role { get; set; } + + /// <summary> + /// Gets or sets the name id. + /// </summary> + [JsonPropertyName("nameId")] + public string? NameId { get; set; } + + /// <summary> + /// Gets or sets the person id. + /// </summary> + [JsonPropertyName("personId")] + public string? PersonId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the character name. + /// </summary> + [JsonPropertyName("characterName")] + public string? CharacterName { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs new file mode 100644 index 0000000000..87c327ed82 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Channel dto. + /// </summary> + public class ChannelDto + { + /// <summary> + /// Gets or sets the list of maps. + /// </summary> + [JsonPropertyName("map")] + public IReadOnlyList<MapDto> Map { get; set; } = Array.Empty<MapDto>(); + + /// <summary> + /// Gets or sets the list of stations. + /// </summary> + [JsonPropertyName("stations")] + public IReadOnlyList<StationDto> Stations { get; set; } = Array.Empty<StationDto>(); + + /// <summary> + /// Gets or sets the metadata. + /// </summary> + [JsonPropertyName("metadata")] + public MetadataDto? Metadata { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs new file mode 100644 index 0000000000..c19cd2e48d --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Content rating dto. + /// </summary> + public class ContentRatingDto + { + /// <summary> + /// Gets or sets the body. + /// </summary> + [JsonPropertyName("body")] + public string? Body { get; set; } + + /// <summary> + /// Gets or sets the code. + /// </summary> + [JsonPropertyName("code")] + public string? Code { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs new file mode 100644 index 0000000000..f00c9accdd --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Crew dto. + /// </summary> + public class CrewDto + { + /// <summary> + /// Gets or sets the billing order. + /// </summary> + [JsonPropertyName("billingOrder")] + public string? BillingOrder { get; set; } + + /// <summary> + /// Gets or sets the role. + /// </summary> + [JsonPropertyName("role")] + public string? Role { get; set; } + + /// <summary> + /// Gets or sets the name id. + /// </summary> + [JsonPropertyName("nameId")] + public string? NameId { get; set; } + + /// <summary> + /// Gets or sets the person id. + /// </summary> + [JsonPropertyName("personId")] + public string? PersonId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string? Name { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs new file mode 100644 index 0000000000..1a371965cf --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Day dto. + /// </summary> + public class DayDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string? StationId { get; set; } + + /// <summary> + /// Gets or sets the list of programs. + /// </summary> + [JsonPropertyName("programs")] + public IReadOnlyList<ProgramDto> Programs { get; set; } = Array.Empty<ProgramDto>(); + + /// <summary> + /// Gets or sets the metadata schedule. + /// </summary> + [JsonPropertyName("metadata")] + public MetadataScheduleDto? Metadata { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs new file mode 100644 index 0000000000..ca6ae7fb13 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Description 1_000 dto. + /// </summary> + public class Description1000Dto + { + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public string? DescriptionLanguage { get; set; } + + /// <summary> + /// Gets or sets the description. + /// </summary> + [JsonPropertyName("description")] + public string? Description { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs new file mode 100644 index 0000000000..1577219ed2 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Description 100 dto. + /// </summary> + public class Description100Dto + { + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public string? DescriptionLanguage { get; set; } + + /// <summary> + /// Gets or sets the description. + /// </summary> + [JsonPropertyName("description")] + public string? Description { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs new file mode 100644 index 0000000000..eaf4a340bd --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Descriptions program dto. + /// </summary> + public class DescriptionsProgramDto + { + /// <summary> + /// Gets or sets the list of description 100. + /// </summary> + [JsonPropertyName("description100")] + public IReadOnlyList<Description100Dto> Description100 { get; set; } = Array.Empty<Description100Dto>(); + + /// <summary> + /// Gets or sets the list of description1000. + /// </summary> + [JsonPropertyName("description1000")] + public IReadOnlyList<Description1000Dto> Description1000 { get; set; } = Array.Empty<Description1000Dto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs new file mode 100644 index 0000000000..fbdfb1f716 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Event details dto. + /// </summary> + public class EventDetailsDto + { + /// <summary> + /// Gets or sets the sub type. + /// </summary> + [JsonPropertyName("subType")] + public string? SubType { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs new file mode 100644 index 0000000000..6852d89d71 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Gracenote dto. + /// </summary> + public class GracenoteDto + { + /// <summary> + /// Gets or sets the season. + /// </summary> + [JsonPropertyName("season")] + public int Season { get; set; } + + /// <summary> + /// Gets or sets the episode. + /// </summary> + [JsonPropertyName("episode")] + public int Episode { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs new file mode 100644 index 0000000000..b9844562f3 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Headends dto. + /// </summary> + public class HeadendsDto + { + /// <summary> + /// Gets or sets the headend. + /// </summary> + [JsonPropertyName("headend")] + public string? Headend { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport")] + public string? Transport { get; set; } + + /// <summary> + /// Gets or sets the location. + /// </summary> + [JsonPropertyName("location")] + public string? Location { get; set; } + + /// <summary> + /// Gets or sets the list of lineups. + /// </summary> + [JsonPropertyName("lineups")] + public IReadOnlyList<LineupDto> Lineups { get; set; } = Array.Empty<LineupDto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs new file mode 100644 index 0000000000..a1ae3ca6d4 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs @@ -0,0 +1,70 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Image data dto. + /// </summary> + public class ImageDataDto + { + /// <summary> + /// Gets or sets the width. + /// </summary> + [JsonPropertyName("width")] + public string? Width { get; set; } + + /// <summary> + /// Gets or sets the height. + /// </summary> + [JsonPropertyName("height")] + public string? Height { get; set; } + + /// <summary> + /// Gets or sets the uri. + /// </summary> + [JsonPropertyName("uri")] + public string? Uri { get; set; } + + /// <summary> + /// Gets or sets the size. + /// </summary> + [JsonPropertyName("size")] + public string? Size { get; set; } + + /// <summary> + /// Gets or sets the aspect. + /// </summary> + [JsonPropertyName("aspect")] + public string? Aspect { get; set; } + + /// <summary> + /// Gets or sets the category. + /// </summary> + [JsonPropertyName("category")] + public string? Category { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + [JsonPropertyName("text")] + public string? Text { get; set; } + + /// <summary> + /// Gets or sets the primary. + /// </summary> + [JsonPropertyName("primary")] + public string? Primary { get; set; } + + /// <summary> + /// Gets or sets the tier. + /// </summary> + [JsonPropertyName("tier")] + public string? Tier { get; set; } + + /// <summary> + /// Gets or sets the caption. + /// </summary> + [JsonPropertyName("caption")] + public CaptionDto? Caption { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs new file mode 100644 index 0000000000..3dc64e5d8a --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs @@ -0,0 +1,46 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// The lineup dto. + /// </summary> + public class LineupDto + { + /// <summary> + /// Gets or sets the linup. + /// </summary> + [JsonPropertyName("lineup")] + public string? Lineup { get; set; } + + /// <summary> + /// Gets or sets the lineup name. + /// </summary> + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport")] + public string? Transport { get; set; } + + /// <summary> + /// Gets or sets the location. + /// </summary> + [JsonPropertyName("location")] + public string? Location { get; set; } + + /// <summary> + /// Gets or sets the uri. + /// </summary> + [JsonPropertyName("uri")] + public string? Uri { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this lineup was deleted. + /// </summary> + [JsonPropertyName("isDeleted")] + public bool? IsDeleted { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs new file mode 100644 index 0000000000..f190817813 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Lineups dto. + /// </summary> + public class LineupsDto + { + /// <summary> + /// Gets or sets the response code. + /// </summary> + [JsonPropertyName("code")] + public int Code { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + [JsonPropertyName("serverID")] + public string? ServerId { get; set; } + + /// <summary> + /// Gets or sets the datetime. + /// </summary> + [JsonPropertyName("datetime")] + public DateTime? LineupTimestamp { get; set; } + + /// <summary> + /// Gets or sets the list of lineups. + /// </summary> + [JsonPropertyName("lineups")] + public IReadOnlyList<LineupDto> Lineups { get; set; } = Array.Empty<LineupDto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs new file mode 100644 index 0000000000..fecc55e037 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Logo dto. + /// </summary> + public class LogoDto + { + /// <summary> + /// Gets or sets the url. + /// </summary> + [JsonPropertyName("URL")] + public string? Url { get; set; } + + /// <summary> + /// Gets or sets the height. + /// </summary> + [JsonPropertyName("height")] + public int Height { get; set; } + + /// <summary> + /// Gets or sets the width. + /// </summary> + [JsonPropertyName("width")] + public int Width { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string? Md5 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs new file mode 100644 index 0000000000..ffd02d474b --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs @@ -0,0 +1,58 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Map dto. + /// </summary> + public class MapDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string? StationId { get; set; } + + /// <summary> + /// Gets or sets the channel. + /// </summary> + [JsonPropertyName("channel")] + public string? Channel { get; set; } + + /// <summary> + /// Gets or sets the provider callsign. + /// </summary> + [JsonPropertyName("providerCallsign")] + public string? ProvderCallsign { get; set; } + + /// <summary> + /// Gets or sets the logical channel number. + /// </summary> + [JsonPropertyName("logicalChannelNumber")] + public string? LogicalChannelNumber { get; set; } + + /// <summary> + /// Gets or sets the uhfvhf. + /// </summary> + [JsonPropertyName("uhfVhf")] + public int UhfVhf { get; set; } + + /// <summary> + /// Gets or sets the atsc major. + /// </summary> + [JsonPropertyName("atscMajor")] + public int AtscMajor { get; set; } + + /// <summary> + /// Gets or sets the atsc minor. + /// </summary> + [JsonPropertyName("atscMinor")] + public int AtscMinor { get; set; } + + /// <summary> + /// Gets or sets the match type. + /// </summary> + [JsonPropertyName("matchType")] + public string? MatchType { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs new file mode 100644 index 0000000000..40faa493c5 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata dto. + /// </summary> + public class MetadataDto + { + /// <summary> + /// Gets or sets the linup. + /// </summary> + [JsonPropertyName("lineup")] + public string? Lineup { get; set; } + + /// <summary> + /// Gets or sets the modified timestamp. + /// </summary> + [JsonPropertyName("modified")] + public string? Modified { get; set; } + + /// <summary> + /// Gets or sets the transport. + /// </summary> + [JsonPropertyName("transport")] + public string? Transport { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs new file mode 100644 index 0000000000..43f2901566 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata programs dto. + /// </summary> + public class MetadataProgramsDto + { + /// <summary> + /// Gets or sets the gracenote object. + /// </summary> + [JsonPropertyName("Gracenote")] + public GracenoteDto? Gracenote { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs new file mode 100644 index 0000000000..04560ab55d --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Metadata schedule dto. + /// </summary> + public class MetadataScheduleDto + { + /// <summary> + /// Gets or sets the modified timestamp. + /// </summary> + [JsonPropertyName("modified")] + public string? Modified { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string? Md5 { get; set; } + + /// <summary> + /// Gets or sets the start date. + /// </summary> + [JsonPropertyName("startDate")] + public DateTime? StartDate { get; set; } + + /// <summary> + /// Gets or sets the end date. + /// </summary> + [JsonPropertyName("endDate")] + public DateTime? EndDate { get; set; } + + /// <summary> + /// Gets or sets the days count. + /// </summary> + [JsonPropertyName("days")] + public int Days { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs new file mode 100644 index 0000000000..31bef423b2 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Movie dto. + /// </summary> + public class MovieDto + { + /// <summary> + /// Gets or sets the year. + /// </summary> + [JsonPropertyName("year")] + public string? Year { get; set; } + + /// <summary> + /// Gets or sets the duration. + /// </summary> + [JsonPropertyName("duration")] + public int Duration { get; set; } + + /// <summary> + /// Gets or sets the list of quality rating. + /// </summary> + [JsonPropertyName("qualityRating")] + public IReadOnlyList<QualityRatingDto> QualityRating { get; set; } = Array.Empty<QualityRatingDto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs new file mode 100644 index 0000000000..e8b15dc07c --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Multipart dto. + /// </summary> + public class MultipartDto + { + /// <summary> + /// Gets or sets the part number. + /// </summary> + [JsonPropertyName("partNumber")] + public int PartNumber { get; set; } + + /// <summary> + /// Gets or sets the total parts. + /// </summary> + [JsonPropertyName("totalParts")] + public int TotalParts { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs new file mode 100644 index 0000000000..84c48f67f3 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Program details dto. + /// </summary> + public class ProgramDetailsDto + { + /// <summary> + /// Gets or sets the audience. + /// </summary> + [JsonPropertyName("audience")] + public string? Audience { get; set; } + + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string? ProgramId { get; set; } + + /// <summary> + /// Gets or sets the list of titles. + /// </summary> + [JsonPropertyName("titles")] + public IReadOnlyList<TitleDto> Titles { get; set; } = Array.Empty<TitleDto>(); + + /// <summary> + /// Gets or sets the event details object. + /// </summary> + [JsonPropertyName("eventDetails")] + public EventDetailsDto? EventDetails { get; set; } + + /// <summary> + /// Gets or sets the descriptions. + /// </summary> + [JsonPropertyName("descriptions")] + public DescriptionsProgramDto? Descriptions { get; set; } + + /// <summary> + /// Gets or sets the original air date. + /// </summary> + [JsonPropertyName("originalAirDate")] + public DateTime? OriginalAirDate { get; set; } + + /// <summary> + /// Gets or sets the list of genres. + /// </summary> + [JsonPropertyName("genres")] + public IReadOnlyList<string> Genres { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + [JsonPropertyName("episodeTitle150")] + public string? EpisodeTitle150 { get; set; } + + /// <summary> + /// Gets or sets the list of metadata. + /// </summary> + [JsonPropertyName("metadata")] + public IReadOnlyList<MetadataProgramsDto> Metadata { get; set; } = Array.Empty<MetadataProgramsDto>(); + + /// <summary> + /// Gets or sets the list of content raitings. + /// </summary> + [JsonPropertyName("contentRating")] + public IReadOnlyList<ContentRatingDto> ContentRating { get; set; } = Array.Empty<ContentRatingDto>(); + + /// <summary> + /// Gets or sets the list of cast. + /// </summary> + [JsonPropertyName("cast")] + public IReadOnlyList<CastDto> Cast { get; set; } = Array.Empty<CastDto>(); + + /// <summary> + /// Gets or sets the list of crew. + /// </summary> + [JsonPropertyName("crew")] + public IReadOnlyList<CrewDto> Crew { get; set; } = Array.Empty<CrewDto>(); + + /// <summary> + /// Gets or sets the entity type. + /// </summary> + [JsonPropertyName("entityType")] + public string? EntityType { get; set; } + + /// <summary> + /// Gets or sets the show type. + /// </summary> + [JsonPropertyName("showType")] + public string? ShowType { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether there is image artwork. + /// </summary> + [JsonPropertyName("hasImageArtwork")] + public bool HasImageArtwork { get; set; } + + /// <summary> + /// Gets or sets the primary image. + /// </summary> + [JsonPropertyName("primaryImage")] + public string? PrimaryImage { get; set; } + + /// <summary> + /// Gets or sets the thumb image. + /// </summary> + [JsonPropertyName("thumbImage")] + public string? ThumbImage { get; set; } + + /// <summary> + /// Gets or sets the backdrop image. + /// </summary> + [JsonPropertyName("backdropImage")] + public string? BackdropImage { get; set; } + + /// <summary> + /// Gets or sets the banner image. + /// </summary> + [JsonPropertyName("bannerImage")] + public string? BannerImage { get; set; } + + /// <summary> + /// Gets or sets the image id. + /// </summary> + [JsonPropertyName("imageID")] + public string? ImageId { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string? Md5 { get; set; } + + /// <summary> + /// Gets or sets the list of content advisory. + /// </summary> + [JsonPropertyName("contentAdvisory")] + public IReadOnlyList<string> ContentAdvisory { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the movie object. + /// </summary> + [JsonPropertyName("movie")] + public MovieDto? Movie { get; set; } + + /// <summary> + /// Gets or sets the list of recommendations. + /// </summary> + [JsonPropertyName("recommendations")] + public IReadOnlyList<RecommendationDto> Recommendations { get; set; } = Array.Empty<RecommendationDto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs new file mode 100644 index 0000000000..60389b45bf --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Program dto. + /// </summary> + public class ProgramDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string? ProgramId { get; set; } + + /// <summary> + /// Gets or sets the air date time. + /// </summary> + [JsonPropertyName("airDateTime")] + public DateTime? AirDateTime { get; set; } + + /// <summary> + /// Gets or sets the duration. + /// </summary> + [JsonPropertyName("duration")] + public int Duration { get; set; } + + /// <summary> + /// Gets or sets the md5. + /// </summary> + [JsonPropertyName("md5")] + public string? Md5 { get; set; } + + /// <summary> + /// Gets or sets the list of audio properties. + /// </summary> + [JsonPropertyName("audioProperties")] + public IReadOnlyList<string> AudioProperties { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the list of video properties. + /// </summary> + [JsonPropertyName("videoProperties")] + public IReadOnlyList<string> VideoProperties { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the list of ratings. + /// </summary> + [JsonPropertyName("ratings")] + public IReadOnlyList<RatingDto> Ratings { get; set; } = Array.Empty<RatingDto>(); + + /// <summary> + /// Gets or sets a value indicating whether this program is new. + /// </summary> + [JsonPropertyName("new")] + public bool? New { get; set; } + + /// <summary> + /// Gets or sets the multipart object. + /// </summary> + [JsonPropertyName("multipart")] + public MultipartDto? Multipart { get; set; } + + /// <summary> + /// Gets or sets the live tape delay. + /// </summary> + [JsonPropertyName("liveTapeDelay")] + public string? LiveTapeDelay { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this is the premiere. + /// </summary> + [JsonPropertyName("premiere")] + public bool Premiere { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this is a repeat. + /// </summary> + [JsonPropertyName("repeat")] + public bool Repeat { get; set; } + + /// <summary> + /// Gets or sets the premiere or finale. + /// </summary> + [JsonPropertyName("isPremiereOrFinale")] + public string? IsPremiereOrFinale { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs new file mode 100644 index 0000000000..c5ddcf7c51 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Quality rating dto. + /// </summary> + public class QualityRatingDto + { + /// <summary> + /// Gets or sets the ratings body. + /// </summary> + [JsonPropertyName("ratingsBody")] + public string? RatingsBody { get; set; } + + /// <summary> + /// Gets or sets the rating. + /// </summary> + [JsonPropertyName("rating")] + public string? Rating { get; set; } + + /// <summary> + /// Gets or sets the min rating. + /// </summary> + [JsonPropertyName("minRating")] + public string? MinRating { get; set; } + + /// <summary> + /// Gets or sets the max rating. + /// </summary> + [JsonPropertyName("maxRating")] + public string? MaxRating { get; set; } + + /// <summary> + /// Gets or sets the increment. + /// </summary> + [JsonPropertyName("increment")] + public string? Increment { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs new file mode 100644 index 0000000000..e04b619a4d --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Rating dto. + /// </summary> + public class RatingDto + { + /// <summary> + /// Gets or sets the body. + /// </summary> + [JsonPropertyName("body")] + public string? Body { get; set; } + + /// <summary> + /// Gets or sets the code. + /// </summary> + [JsonPropertyName("code")] + public string? Code { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs new file mode 100644 index 0000000000..c8f79fd1c2 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Recommendation dto. + /// </summary> + public class RecommendationDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string? ProgramId { get; set; } + + /// <summary> + /// Gets or sets the title. + /// </summary> + [JsonPropertyName("title120")] + public string? Title120 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs new file mode 100644 index 0000000000..0cd05709b3 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Request schedule for channel dto. + /// </summary> + public class RequestScheduleForChannelDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string? StationId { get; set; } + + /// <summary> + /// Gets or sets the list of dates. + /// </summary> + [JsonPropertyName("date")] + public IReadOnlyList<string> Date { get; set; } = Array.Empty<string>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs new file mode 100644 index 0000000000..84e224b71e --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Show image dto. + /// </summary> + public class ShowImagesDto + { + /// <summary> + /// Gets or sets the program id. + /// </summary> + [JsonPropertyName("programID")] + public string? ProgramId { get; set; } + + /// <summary> + /// Gets or sets the list of data. + /// </summary> + [JsonPropertyName("data")] + public IReadOnlyList<ImageDataDto> Data { get; set; } = Array.Empty<ImageDataDto>(); + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs new file mode 100644 index 0000000000..d797fd49b1 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Station dto. + /// </summary> + public class StationDto + { + /// <summary> + /// Gets or sets the station id. + /// </summary> + [JsonPropertyName("stationID")] + public string? StationId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the callsign. + /// </summary> + [JsonPropertyName("callsign")] + public string? Callsign { get; set; } + + /// <summary> + /// Gets or sets the broadcast language. + /// </summary> + [JsonPropertyName("broadcastLanguage")] + public IReadOnlyList<string> BroadcastLanguage { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the description language. + /// </summary> + [JsonPropertyName("descriptionLanguage")] + public IReadOnlyList<string> DescriptionLanguage { get; set; } = Array.Empty<string>(); + + /// <summary> + /// Gets or sets the broadcaster. + /// </summary> + [JsonPropertyName("broadcaster")] + public BroadcasterDto? Broadcaster { get; set; } + + /// <summary> + /// Gets or sets the affiliate. + /// </summary> + [JsonPropertyName("affiliate")] + public string? Affiliate { get; set; } + + /// <summary> + /// Gets or sets the logo. + /// </summary> + [JsonPropertyName("logo")] + public LogoDto? Logo { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether it is commercial free. + /// </summary> + [JsonPropertyName("isCommercialFree")] + public bool? IsCommercialFree { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs new file mode 100644 index 0000000000..61cd4a9b00 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// Title dto. + /// </summary> + public class TitleDto + { + /// <summary> + /// Gets or sets the title. + /// </summary> + [JsonPropertyName("title120")] + public string? Title120 { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs new file mode 100644 index 0000000000..afb9994869 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json.Serialization; + +namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos +{ + /// <summary> + /// The token dto. + /// </summary> + public class TokenDto + { + /// <summary> + /// Gets or sets the response code. + /// </summary> + [JsonPropertyName("code")] + public int Code { get; set; } + + /// <summary> + /// Gets or sets the response message. + /// </summary> + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + [JsonPropertyName("serverID")] + public string? ServerId { get; set; } + + /// <summary> + /// Gets or sets the token. + /// </summary> + [JsonPropertyName("token")] + public string? Token { get; set; } + + /// <summary> + /// Gets or sets the current datetime. + /// </summary> + [JsonPropertyName("datetime")] + public DateTime? TokenTimestamp { get; set; } + + /// <summary> + /// Gets or sets the response message. + /// </summary> + [JsonPropertyName("response")] + public string? Response { get; set; } + } +} diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 69b10e6daa..0c0ec48d96 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,15 +1,20 @@ +#nullable disable + +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Emby.XmlTv.Classes; -using Emby.XmlTv.Entities; +using Jellyfin.Extensions; +using Jellyfin.XmlTv; +using Jellyfin.XmlTv.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; @@ -22,15 +27,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class XmlTvListingsProvider : IListingsProvider { private readonly IServerConfigurationManager _config; - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger<XmlTvListingsProvider> _logger; private readonly IFileSystem _fileSystem; private readonly IZipClient _zipClient; - public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient) + public XmlTvListingsProvider( + IServerConfigurationManager config, + IHttpClientFactory httpClientFactory, + ILogger<XmlTvListingsProvider> logger, + IFileSystem fileSystem, + IZipClient zipClient) { _config = config; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; _fileSystem = fileSystem; _zipClient = zipClient; @@ -50,51 +60,41 @@ namespace Emby.Server.Implementations.LiveTv.Listings return _config.Configuration.PreferredMetadataLanguage; } - private async Task<string> GetXml(string path, CancellationToken cancellationToken) + private async Task<string> GetXml(ListingsProviderInfo info, CancellationToken cancellationToken) { - _logger.LogInformation("xmltv path: {path}", path); + _logger.LogInformation("xmltv path: {Path}", info.Path); - if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - return UnzipIfNeeded(path, path); + return UnzipIfNeeded(info.Path, info.Path); } - string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml"; + string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + "-" + info.Id + ".xml"; string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename); if (File.Exists(cacheFile)) { - return UnzipIfNeeded(path, cacheFile); + return UnzipIfNeeded(info.Path, cacheFile); } - _logger.LogInformation("Downloading xmltv listings from {path}", path); - - string tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = path, - Progress = new SimpleProgress<double>(), - DecompressionMethod = CompressionMethod.Gzip, - - // It's going to come back gzipped regardless of this value - // So we need to make sure the decompression method is set to gzip - EnableHttpCompression = true, - - UserAgent = "Emby/3.0" - - }).ConfigureAwait(false); + _logger.LogInformation("Downloading xmltv listings from {Path}", info.Path); Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); - File.Copy(tempFile, cacheFile, true); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous)) + { + await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + } - return UnzipIfNeeded(path, cacheFile); + return UnzipIfNeeded(info.Path, cacheFile); } - private string UnzipIfNeeded(string originalUrl, string file) + private string UnzipIfNeeded(ReadOnlySpan<char> originalUrl, string file) { - string ext = Path.GetExtension(originalUrl.Split('?')[0]); + ReadOnlySpan<char> ext = Path.GetExtension(originalUrl.LeftPart('?')); - if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase)) { try { @@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } catch (Exception ex) { - _logger.LogError(ex, "Error extracting from gz file {file}", file); + _logger.LogError(ex, "Error extracting from gz file {File}", file); } try @@ -113,7 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } catch (Exception ex) { - _logger.LogError(ex, "Error extracting from zip file {file}", file); + _logger.LogError(ex, "Error extracting from zip file {File}", file); } } @@ -161,20 +161,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new ArgumentNullException(nameof(channelId)); } - /* - if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false)) - { - var length = endDateUtc - startDateUtc; - if (length.TotalDays > 1) - { - endDateUtc = startDateUtc.AddDays(1); - } - }*/ + _logger.LogDebug("Getting xmltv programs for channel {Id}", channelId); - _logger.LogDebug("Getting xmltv programs for channel {id}", channelId); - - string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + string path = await GetXml(info, cancellationToken).ConfigureAwait(false); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); return reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken) @@ -208,7 +198,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source), OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null, CommunityRating = program.StarRating, - SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N") + SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; if (string.IsNullOrWhiteSpace(program.ProgramId)) @@ -219,19 +209,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings { uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture); } + if (programInfo.EpisodeNumber.HasValue) { uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); } - programInfo.ShowId = uniqueString.GetMD5().ToString("N"); + programInfo.ShowId = uniqueString.GetMD5().ToString("N", CultureInfo.InvariantCulture); // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped if (programInfo.IsSeries && !programInfo.IsRepeat && (programInfo.EpisodeNumber ?? 0) == 0) { - programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture); + programInfo.ShowId += programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture); } } else @@ -240,7 +231,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings } // Construct an id from the channel and start date - programInfo.Id = string.Format("{0}_{1:O}", program.ChannelId, program.StartDate); + programInfo.Id = string.Format(CultureInfo.InvariantCulture, "{0}_{1:O}", program.ChannelId, program.StartDate); if (programInfo.IsMovie) { @@ -266,8 +257,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings public async Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location) { // In theory this should never be called because there is always only one lineup - string path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + string path = await GetXml(info, CancellationToken.None).ConfigureAwait(false); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); IEnumerable<XmlTvChannel> results = reader.GetChannels(); @@ -278,8 +269,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings public async Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken) { // In theory this should never be called because there is always only one lineup - string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); - _logger.LogDebug("Opening XmlTvReader for {path}", path); + string path = await GetXml(info, cancellationToken).ConfigureAwait(false); + _logger.LogDebug("Opening XmlTvReader for {Path}", path); var reader = new XmlTvReader(path, GetLanguage(info)); var results = reader.GetChannels(); @@ -290,7 +281,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings Name = c.DisplayName, ImageUrl = c.Icon != null && !string.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null, Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number - }).ToList(); } } |
