diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 57855008d5..4d9999b37d 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -290,8 +290,6 @@ - - @@ -329,8 +327,6 @@ - - diff --git a/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs b/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs deleted file mode 100644 index bfdd1dbf3a..0000000000 --- a/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public interface IItemIdentityConverter { } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs b/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs deleted file mode 100644 index 6b403bb55f..0000000000 --- a/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public interface IItemIdentityProvider { } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 57e4ff3200..a976a7f715 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -97,13 +97,11 @@ namespace MediaBrowser.Controller.Providers /// /// The image providers. /// The metadata services. - /// The identity providers. - /// The identity converters. /// The metadata providers. /// The savers. /// The image savers. /// The external ids. - void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable identityProviders, IEnumerable identityConverters, IEnumerable metadataProviders, + void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, IEnumerable metadataProviders, IEnumerable savers, IEnumerable imageSavers, IEnumerable externalIds); @@ -190,21 +188,5 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. /// Task{HttpResponseInfo}. Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken); - - /// - /// Gets the item identity providers. - /// - /// The type of the t lookup information. - /// IEnumerable<IItemIdentityProvider<TLookupInfo, TIdentity>>. - IEnumerable> GetItemIdentityProviders() - where TLookupInfo : ItemLookupInfo; - - /// - /// Gets the item identity converters. - /// - /// The type of the t lookup information. - /// IEnumerable<IItemIdentityConverter<TIdentity>>. - IEnumerable> GetItemIdentityConverters() - where TLookupInfo : ItemLookupInfo; } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/ItemIdentifier.cs b/MediaBrowser.Controller/Providers/ItemIdentifier.cs deleted file mode 100644 index bbc6dd76cd..0000000000 --- a/MediaBrowser.Controller/Providers/ItemIdentifier.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Providers -{ - public static class ItemIdentifier - where TLookupInfo : ItemLookupInfo - { - public static async Task FindIdentities(TLookupInfo item, IProviderManager providerManager, CancellationToken cancellationToken) - { - var providers = providerManager.GetItemIdentityProviders(); - var converters = providerManager.GetItemIdentityConverters().ToList(); - - foreach (var provider in providers) - { - await provider.Identify(item); - } - - bool changesMade = true; - - while (changesMade) - { - changesMade = false; - - foreach (var converter in converters) - { - if (await converter.Convert(item)) - { - changesMade = true; - } - } - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/ItemIdentities.cs b/MediaBrowser.Controller/Providers/ItemIdentities.cs deleted file mode 100644 index 48316d0f44..0000000000 --- a/MediaBrowser.Controller/Providers/ItemIdentities.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Providers -{ - public interface IItemIdentityProvider : IItemIdentityProvider - where TLookupInfo : ItemLookupInfo - { - Task Identify(TLookupInfo info); - } - - public interface IItemIdentityConverter : IItemIdentityConverter - where TLookupInfo : ItemLookupInfo - { - Task Convert(TLookupInfo info); - } -} \ No newline at end of file diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index b84d1b0ff0..678d495cbc 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -179,18 +179,6 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - private async Task FindIdentities(TIdType id, CancellationToken cancellationToken) - { - try - { - await ItemIdentifier.FindIdentities(id, ProviderManager, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error in FindIdentities", ex); - } - } - private DateTime GetLastRefreshDate(IHasMetadata item) { return item.DateLastRefreshed; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index c95d58a421..29897e0736 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -55,8 +55,6 @@ namespace MediaBrowser.Providers.Manager private readonly IFileSystem _fileSystem; private IMetadataService[] _metadataServices = { }; - private IItemIdentityProvider[] _identityProviders = { }; - private IItemIdentityConverter[] _identityConverters = { }; private IMetadataProvider[] _metadataProviders = { }; private IEnumerable _savers; private IImageSaver[] _imageSavers; @@ -92,22 +90,17 @@ namespace MediaBrowser.Providers.Manager /// /// The image providers. /// The metadata services. - /// The identity providers. - /// The identity converters. /// The metadata providers. /// The metadata savers. /// The image savers. /// The external ids. public void AddParts(IEnumerable imageProviders, IEnumerable metadataServices, - IEnumerable identityProviders, IEnumerable identityConverters, IEnumerable metadataProviders, IEnumerable metadataSavers, IEnumerable imageSavers, IEnumerable externalIds) { ImageProviders = imageProviders.ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); - _identityProviders = identityProviders.ToArray(); - _identityConverters = identityConverters.ToArray(); _metadataProviders = metadataProviders.ToArray(); _imageSavers = imageSavers.ToArray(); _externalIds = externalIds.OrderBy(i => i.Name).ToArray(); @@ -301,18 +294,6 @@ namespace MediaBrowser.Providers.Manager .ThenBy(GetDefaultOrder); } - public IEnumerable> GetItemIdentityProviders() - where TLookupInfo : ItemLookupInfo - { - return _identityProviders.OfType>(); - } - - public IEnumerable> GetItemIdentityConverters() - where TLookupInfo : ItemLookupInfo - { - return _identityConverters.OfType>(); - } - private IEnumerable GetRemoteImageProviders(IHasImages item, bool includeDisabled) { var options = GetMetadataOptions(item); diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index bbfa20738f..240b2e2cca 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -177,7 +177,6 @@ - diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 7005ba8f58..a41a95c126 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.TV /// /// Class RemoteEpisodeProvider /// - class TvdbEpisodeProvider : IRemoteMetadataProvider, IItemIdentityProvider + class TvdbEpisodeProvider : IRemoteMetadataProvider { private static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full"; @@ -871,86 +871,6 @@ namespace MediaBrowser.Providers.TV }); } - public Task Identify(EpisodeInfo info) - { - if (info.ProviderIds.ContainsKey(FullIdKey)) - { - return Task.FromResult(null); - } - - string seriesTvdbId; - info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId); - - if (string.IsNullOrEmpty(seriesTvdbId) || info.IndexNumber == null) - { - return Task.FromResult(null); - } - - var id = new Identity(seriesTvdbId, info.ParentIndexNumber, info.IndexNumber.Value, info.IndexNumberEnd); - info.SetProviderId(FullIdKey, id.ToString()); - - return Task.FromResult(id); - } - public int Order { get { return 0; } } - - public struct Identity - { - public string SeriesId { get; private set; } - public int? SeasonIndex { get; private set; } - public int EpisodeNumber { get; private set; } - public int? EpisodeNumberEnd { get; private set; } - - public Identity(string id) - : this() - { - this = ParseIdentity(id).Value; - } - - public Identity(string seriesId, int? seasonIndex, int episodeNumber, int? episodeNumberEnd) - : this() - { - SeriesId = seriesId; - SeasonIndex = seasonIndex; - EpisodeNumber = episodeNumber; - EpisodeNumberEnd = episodeNumberEnd; - } - - public override string ToString() - { - return string.Format("{0}:{1}:{2}", - SeriesId, - SeasonIndex != null ? SeasonIndex.Value.ToString() : "A", - EpisodeNumber + (EpisodeNumberEnd != null ? "-" + EpisodeNumberEnd.Value.ToString() : "")); - } - - public static Identity? ParseIdentity(string id) - { - if (string.IsNullOrEmpty(id)) - return null; - - try { - var parts = id.Split(':'); - var series = parts[0]; - var season = parts[1] != "A" ? (int?)int.Parse(parts[1]) : null; - - int index; - int? indexEnd; - - if (parts[2].Contains("-")) { - var split = parts[2].IndexOf("-", StringComparison.OrdinalIgnoreCase); - index = int.Parse(parts[2].Substring(0, split)); - indexEnd = int.Parse(parts[2].Substring(split + 1)); - } else { - index = int.Parse(parts[2]); - indexEnd = null; - } - - return new Identity(series, season, index, indexEnd); - } catch { - return null; - } - } - } } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs deleted file mode 100644 index 4198430c9f..0000000000 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Providers.TV -{ - public class TvdbSeasonIdentityProvider : IItemIdentityProvider - { - public static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full"; - - public Task Identify(SeasonInfo info) - { - string tvdbSeriesId; - if (!info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbSeriesId) || string.IsNullOrEmpty(tvdbSeriesId) || info.IndexNumber == null) - { - return Task.FromResult(null); - } - - if (string.IsNullOrEmpty(info.GetProviderId(FullIdKey))) - { - var id = string.Format("{0}:{1}", tvdbSeriesId, info.IndexNumber.Value); - info.SetProviderId(FullIdKey, id); - } - - return Task.FromResult(null); - } - - public static TvdbSeasonIdentity? ParseIdentity(string id) - { - if (id == null) - { - return null; - } - - try - { - var parts = id.Split(':'); - return new TvdbSeasonIdentity(parts[0], int.Parse(parts[1])); - } - catch - { - return null; - } - } - } - - public struct TvdbSeasonIdentity - { - public string SeriesId { get; private set; } - public int Index { get; private set; } - - public TvdbSeasonIdentity(string id) - : this() - { - this = TvdbSeasonIdentityProvider.ParseIdentity(id).Value; - } - - public TvdbSeasonIdentity(string seriesId, int index) - : this() - { - SeriesId = seriesId; - Index = index; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index 71587db979..4b619665cd 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -70,21 +70,6 @@ namespace MediaBrowser.Providers.TV var seriesProviderIds = series.ProviderIds; var seasonNumber = season.IndexNumber.Value; - var identity = TvdbSeasonIdentityProvider.ParseIdentity(season.GetProviderId(TvdbSeasonIdentityProvider.FullIdKey)); - if (identity == null) - { - identity = new TvdbSeasonIdentity(series.GetProviderId(MetadataProviders.Tvdb), seasonNumber); - } - - if (identity != null) - { - var id = identity.Value; - seasonNumber = AdjustForSeriesOffset(series, id.Index); - - seriesProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - seriesProviderIds[MetadataProviders.Tvdb.ToString()] = id.SeriesId; - } - var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(seriesDataPath)) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 15b0c97aca..49ca5cdf20 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -25,7 +25,7 @@ using CommonIO; namespace MediaBrowser.Providers.TV { - public class TvdbSeriesProvider : IRemoteMetadataProvider, IItemIdentityProvider, IHasOrder + public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { private const string TvdbSeriesOffset = "TvdbSeriesOffset"; private const string TvdbSeriesOffsetFormat = "{0}-{1}"; diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs new file mode 100644 index 0000000000..74dfbc679c --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.ScheduledTasks; + +namespace MediaBrowser.Server.Implementations.IO +{ + public class FileRefresher : IDisposable + { + private ILogger Logger { get; set; } + private ITaskManager TaskManager { get; set; } + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + private readonly IFileSystem _fileSystem; + private readonly List _affectedPaths = new List(); + private Timer _timer; + private readonly object _timerLock = new object(); + + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger) + { + _affectedPaths.Add(path); + + _fileSystem = fileSystem; + ConfigurationManager = configurationManager; + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logger; + } + + private void RestartTimer() + { + lock (_timerLock) + { + if (_timer == null) + { + _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + private async void OnTimerCallback(object state) + { + // Extend the timer as long as any of the paths are still being written to. + if (_affectedPaths.Any(IsFileLocked)) + { + Logger.Info("Timer extended."); + RestartTimer(); + return; + } + + Logger.Debug("Timer stopped."); + + DisposeTimer(); + + try + { + await ProcessPathChanges(_affectedPaths).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error processing directory changes", ex); + } + } + + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .Distinct() + .ToList(); + + foreach (var p in paths) + { + Logger.Info(p + " reports change."); + } + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + TaskManager.CancelIfRunningAndQueue(); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.FindByPath(path, null); + + path = Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) + { + item = item.GetParent(); + + if (item == null) + { + break; + } + } + } + + return item; + } + + private bool IsFileLocked(string path) + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + // Causing lockups on linux + return false; + } + + try + { + var data = _fileSystem.GetFileSystemInfo(path); + + if (!data.Exists + || data.IsDirectory + + // Opening a writable stream will fail with readonly files + || data.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + return false; + } + } + catch (IOException) + { + return false; + } + catch (Exception ex) + { + Logger.ErrorException("Error getting file system info for: {0}", ex, path); + return false; + } + + // In order to determine if the file is being written to, we have to request write access + // But if the server only has readonly access, this is going to cause this entire algorithm to fail + // So we'll take a best guess about our access level + var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta + ? FileAccess.ReadWrite + : FileAccess.Read; + + try + { + using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite)) + { + //file is not locked + return false; + } + } + catch (DirectoryNotFoundException) + { + // File may have been deleted + return false; + } + catch (FileNotFoundException) + { + // File may have been deleted + return false; + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + Logger.Debug("{0} is locked.", path); + return true; + } + catch (Exception ex) + { + Logger.ErrorException("Error determining if file is locked: {0}", ex, path); + return false; + } + } + + public void DisposeTimer() + { + lock (_timerLock) + { + if (_timer != null) + { + _timer.Dispose(); + } + } + } + + public void Dispose() + { + DisposeTimer(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 205000fdc6..25822a81bb 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -929,10 +929,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); - _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); - activeRecordingInfo.Path = recordPath; _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); + _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); + activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 3363108881..28edbfcc48 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -180,6 +180,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index ec10fdaf69..b1f43d20f7 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -247,7 +247,8 @@ namespace MediaBrowser.Server.Implementations.Persistence { "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", "create index if not exists idx_Type on TypedBaseItems(Type)", - "create index if not exists idx_TopParentId on TypedBaseItems(TopParentId)" + "create index if not exists idx_TopParentId on TypedBaseItems(TopParentId)", + //"create index if not exists idx_TypeTopParentId on TypedBaseItems(Type,TopParentId)" }; _connection.RunQueries(postQueries, Logger); @@ -1673,7 +1674,7 @@ namespace MediaBrowser.Server.Implementations.Persistence var slowThreshold = 1000; #if DEBUG - slowThreshold = 200; + slowThreshold = 100; #endif if (elapsed >= slowThreshold) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 7b958779da..59a8a4f78a 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -804,8 +804,6 @@ namespace MediaBrowser.Server.Startup.Common ProviderManager.AddParts(GetExports(), GetExports(), - GetExports(), - GetExports(), GetExports(), GetExports(), GetExports(),