diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 972578b7d..fead8e8a8 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -82,7 +82,7 @@ - + diff --git a/MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs b/MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs similarity index 93% rename from MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs rename to MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs index 521d29d89..085ff9b54 100644 --- a/MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs @@ -13,14 +13,14 @@ namespace MediaBrowser.Providers.Music /// /// Class MusicAlbumDynamicInfoProvider /// - public class MusicAlbumDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider + public class AlbumDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider { /// /// Initializes a new instance of the class. /// /// The log manager. /// The configuration manager. - public MusicAlbumDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + public AlbumDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager) { } diff --git a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs index ea4ef5e21..be7ff192e 100644 --- a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Net; +using System.Collections.Generic; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -127,7 +128,7 @@ namespace MediaBrowser.Providers.TV return base.NeedsRefreshInternal(item, providerInfo); } - protected override DateTime CompareDate(BaseItem item) + protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) { var episode = (Episode)item; @@ -136,17 +137,90 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrEmpty(seriesId)) { // Process images - var seriesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml"); + var seriesDataPath = RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId); - var seriesXmlFileInfo = new FileInfo(seriesXmlPath); + var files = GetEpisodeXmlFiles(episode, seriesDataPath); - if (seriesXmlFileInfo.Exists) + if (files.Count > 0) { - return seriesXmlFileInfo.LastWriteTimeUtc; + return files.Select(i => i.LastWriteTimeUtc).Max() > providerInfo.LastRefreshed; + } + } + + return false; + } + + /// + /// Gets the episode XML files. + /// + /// The episode. + /// The series data path. + /// List{FileInfo}. + private List GetEpisodeXmlFiles(Episode episode, string seriesDataPath) + { + var files = new List(); + + if (episode.IndexNumber == null) + { + return files; + } + + var episodeNumber = episode.IndexNumber.Value; + var seasonNumber = episode.ParentIndexNumber; + + if (seasonNumber == null) + { + return files; + } + + var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); + + var fileInfo = new FileInfo(file); + var usingAbsoluteData = false; + + if (fileInfo.Exists) + { + files.Add(fileInfo); + } + else + { + file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); + fileInfo = new FileInfo(file); + if (fileInfo.Exists) + { + files.Add(fileInfo); + usingAbsoluteData = true; } } - return base.CompareDate(item); + var end = episode.IndexNumberEnd ?? episodeNumber; + episodeNumber++; + + while (episodeNumber <= end) + { + if (usingAbsoluteData) + { + file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", episodeNumber)); + } + else + { + file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber)); + } + + fileInfo = new FileInfo(file); + if (fileInfo.Exists) + { + files.Add(fileInfo); + } + else + { + break; + } + + episodeNumber++; + } + + return files; } /// @@ -213,7 +287,7 @@ namespace MediaBrowser.Providers.TV } var episodeNumber = episode.IndexNumber.Value; - var seasonNumber = episode.ParentIndexNumber ?? TVUtils.GetSeasonNumberFromEpisodeFile(episode.Path); + var seasonNumber = episode.ParentIndexNumber; if (seasonNumber == null) { diff --git a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs index e86e53e3b..161348935 100644 --- a/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/RemoteSeriesProvider.cs @@ -252,7 +252,7 @@ namespace MediaBrowser.Providers.TV // The prescan task will take care of updates so we don't need to re-download here if (!files.Contains("banners.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains("actors.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains(seriesXmlFilename, StringComparer.OrdinalIgnoreCase)) { - await DownloadSeriesZip(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, seriesDataPath, null, cancellationToken).ConfigureAwait(false); } // Examine if there's no local metadata, or save local is on (to get updates) @@ -279,7 +279,7 @@ namespace MediaBrowser.Providers.TV /// The series data path. /// The cancellation token. /// Task. - internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, CancellationToken cancellationToken) + internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken) { var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage); @@ -301,12 +301,14 @@ namespace MediaBrowser.Providers.TV } } - foreach (var file in Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.AllDirectories).ToList()) + // Sanitize all files, except for extracted episode files + foreach (var file in Directory.EnumerateFiles(seriesDataPath, "*.xml", SearchOption.AllDirectories).ToList() + .Where(i => !Path.GetFileName(i).StartsWith("episode-", StringComparison.OrdinalIgnoreCase))) { await SanitizeXmlFile(file).ConfigureAwait(false); } - await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, ConfigurationManager.Configuration.PreferredMetadataLanguage + ".xml")).ConfigureAwait(false); + await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, ConfigurationManager.Configuration.PreferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false); } /// @@ -369,8 +371,9 @@ namespace MediaBrowser.Providers.TV /// /// The series data path. /// The XML file. + /// The last tv db update time. /// Task. - private async Task ExtractEpisodes(string seriesDataPath, string xmlFile) + private async Task ExtractEpisodes(string seriesDataPath, string xmlFile, long? lastTvDbUpdateTime) { var settings = new XmlReaderSettings { @@ -398,7 +401,7 @@ namespace MediaBrowser.Providers.TV { var outerXml = reader.ReadOuterXml(); - await SaveEpsiodeXml(seriesDataPath, outerXml).ConfigureAwait(false); + await SaveEpsiodeXml(seriesDataPath, outerXml, lastTvDbUpdateTime).ConfigureAwait(false); break; } @@ -412,7 +415,7 @@ namespace MediaBrowser.Providers.TV } } - private async Task SaveEpsiodeXml(string seriesDataPath, string xml) + private async Task SaveEpsiodeXml(string seriesDataPath, string xml, long? lastTvDbUpdateTime) { var settings = new XmlReaderSettings { @@ -425,6 +428,7 @@ namespace MediaBrowser.Providers.TV var seasonNumber = -1; var episodeNumber = -1; var absoluteNumber = -1; + var lastUpdateString = string.Empty; using (var streamReader = new StringReader(xml)) { @@ -440,6 +444,12 @@ namespace MediaBrowser.Providers.TV { switch (reader.Name) { + case "lastupdated": + { + lastUpdateString = reader.ReadElementContentAsString(); + break; + } + case "EpisodeNumber": { var val = reader.ReadElementContentAsString(); @@ -491,21 +501,21 @@ namespace MediaBrowser.Providers.TV } } - var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber, episodeNumber)); - - using (var writer = XmlWriter.Create(file, new XmlWriterSettings + var hasEpisodeChanged = true; + if (!string.IsNullOrEmpty(lastUpdateString) && lastTvDbUpdateTime.HasValue) { - Encoding = Encoding.UTF8, - Async = true - })) - { - await writer.WriteRawAsync(xml).ConfigureAwait(false); + long num; + if (long.TryParse(lastUpdateString, NumberStyles.Any, UsCulture, out num)) + { + hasEpisodeChanged = num >= lastTvDbUpdateTime.Value; + } } - if (absoluteNumber != -1) - { - file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", absoluteNumber)); + var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber, episodeNumber)); + // Only save the file if not already there, or if the episode has changed + if (hasEpisodeChanged || !File.Exists(file)) + { using (var writer = XmlWriter.Create(file, new XmlWriterSettings { Encoding = Encoding.UTF8, @@ -515,6 +525,24 @@ namespace MediaBrowser.Providers.TV await writer.WriteRawAsync(xml).ConfigureAwait(false); } } + + if (absoluteNumber != -1) + { + file = Path.Combine(seriesDataPath, string.Format("episode-abs-{0}.xml", absoluteNumber)); + + // Only save the file if not already there, or if the episode has changed + if (hasEpisodeChanged || !File.Exists(file)) + { + using (var writer = XmlWriter.Create(file, new XmlWriterSettings + { + Encoding = Encoding.UTF8, + Async = true + })) + { + await writer.WriteRawAsync(xml).ConfigureAwait(false); + } + } + } } /// diff --git a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs index 0bc7cc3e7..94f857d9c 100644 --- a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Net; +using System.Globalization; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; @@ -55,6 +56,8 @@ namespace MediaBrowser.Providers.TV _config = config; } + protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); + /// /// Runs the specified progress. /// @@ -72,7 +75,7 @@ namespace MediaBrowser.Providers.TV var path = RemoteSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); Directory.CreateDirectory(path); - + var timestampFile = Path.Combine(path, "time.txt"); var timestampFileInfo = new FileInfo(timestampFile); @@ -106,7 +109,7 @@ namespace MediaBrowser.Providers.TV newUpdateTime = GetUpdateTime(stream); } - await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false); + await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false); } else { @@ -114,7 +117,13 @@ namespace MediaBrowser.Providers.TV newUpdateTime = seriesToUpdate.Item2; - await UpdateSeries(seriesToUpdate.Item1, path, progress, cancellationToken).ConfigureAwait(false); + long lastUpdateValue; + + long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out lastUpdateValue); + + var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue; + + await UpdateSeries(seriesToUpdate.Item1, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false); } File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); @@ -251,10 +260,11 @@ namespace MediaBrowser.Providers.TV /// /// The series ids. /// The series data path. + /// The last tv db update time. /// The progress. /// The cancellation token. /// Task. - private async Task UpdateSeries(IEnumerable seriesIds, string seriesDataPath, IProgress progress, CancellationToken cancellationToken) + private async Task UpdateSeries(IEnumerable seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress progress, CancellationToken cancellationToken) { var list = seriesIds.ToList(); var numComplete = 0; @@ -263,7 +273,7 @@ namespace MediaBrowser.Providers.TV { try { - await UpdateSeries(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false); + await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, cancellationToken).ConfigureAwait(false); } catch (HttpException ex) { @@ -289,9 +299,10 @@ namespace MediaBrowser.Providers.TV /// /// The id. /// The series data path. + /// The last tv db update time. /// The cancellation token. /// Task. - private Task UpdateSeries(string id, string seriesDataPath, CancellationToken cancellationToken) + private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken) { _logger.Info("Updating series " + id); @@ -299,7 +310,7 @@ namespace MediaBrowser.Providers.TV Directory.CreateDirectory(seriesDataPath); - return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken); + return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, cancellationToken); } } } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index ea6cca6ff..6d3cbcf26 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -11,6 +11,9 @@ using System.Diagnostics; using System.IO; using System.Threading; using System.Windows; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; using Gtk; using Gdk; using System.Threading.Tasks; @@ -90,12 +93,17 @@ namespace MediaBrowser.Server.Mono } } + private static RemoteCertificateValidationCallback _ignoreCertificates = new RemoteCertificateValidationCallback(delegate { return true; }); + private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager) { // TODO: Show splash here SystemEvents.SessionEnding += SystemEvents_SessionEnding; + // Allow all https requests + ServicePointManager.ServerCertificateValidationCallback = _ignoreCertificates; + _appHost = new ApplicationHost(appPaths, logManager); var task = _appHost.Init(); @@ -264,4 +272,12 @@ namespace MediaBrowser.Server.Mono Shutdown (); } } + + class NoCheckCertificatePolicy : ICertificatePolicy + { + public bool CheckValidationResult (ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) + { + return true; + } + } }