aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs5
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj5
-rw-r--r--MediaBrowser.Server.Implementations/TV/SeriesPostScanTask.cs233
3 files changed, 243 insertions, 0 deletions
diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
index 0b1878c06..e4db98b3f 100644
--- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
+++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
@@ -118,6 +118,11 @@ namespace MediaBrowser.Server.Implementations.Localization
).Normalize(NormalizationForm.FormC);
}
+ public string NormalizeFormKD(string text)
+ {
+ return text.Normalize(NormalizationForm.FormKD);
+ }
+
/// <summary>
/// Gets the cultures.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 23d373ac4..71e964eec 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -278,6 +278,7 @@
<Compile Include="Sync\SyncJobOptions.cs" />
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
<Compile Include="Threading\PeriodicTimer.cs" />
+ <Compile Include="TV\SeriesPostScanTask.cs" />
<Compile Include="UserViews\CollectionFolderImageProvider.cs" />
<Compile Include="UserViews\DynamicImageProvider.cs" />
<Compile Include="News\NewsEntryPoint.cs" />
@@ -383,6 +384,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj">
+ <Project>{442b5058-dcaf-4263-bb6a-f21e31120a1b}</Project>
+ <Name>MediaBrowser.Providers</Name>
+ </ProjectReference>
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
<Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
<Name>Mono.Nat</Name>
diff --git a/MediaBrowser.Server.Implementations/TV/SeriesPostScanTask.cs b/MediaBrowser.Server.Implementations/TV/SeriesPostScanTask.cs
new file mode 100644
index 000000000..f4ee3e1af
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/TV/SeriesPostScanTask.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
+using MediaBrowser.Providers.TV;
+
+namespace MediaBrowser.Server.Implementations.TV
+{
+ class SeriesGroup : List<Series>, IGrouping<string, Series>
+ {
+ public string Key { get; set; }
+ }
+
+ class SeriesPostScanTask : ILibraryPostScanTask, IHasOrder
+ {
+ /// <summary>
+ /// The _library manager
+ /// </summary>
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _config;
+ private readonly ILogger _logger;
+ private readonly ILocalizationManager _localization;
+ private readonly IFileSystem _fileSystem;
+
+ public SeriesPostScanTask(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, ILocalizationManager localization, IFileSystem fileSystem)
+ {
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _config = config;
+ _localization = localization;
+ _fileSystem = fileSystem;
+ }
+
+ public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ return RunInternal(progress, cancellationToken);
+ }
+
+ private Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+
+ }).Cast<Series>().ToList();
+
+ var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
+
+ return new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem).Run(seriesGroups, true, cancellationToken);
+ }
+
+ internal static IEnumerable<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList)
+ {
+ var links = seriesList.ToDictionary(s => s, s => seriesList.Where(c => c != s && ShareProviderId(s, c)).ToList());
+
+ var visited = new HashSet<Series>();
+
+ foreach (var series in seriesList)
+ {
+ if (!visited.Contains(series))
+ {
+ var group = new SeriesGroup();
+ FindAllLinked(series, visited, links, group);
+
+ group.Key = group.Select(s => s.GetProviderId(MetadataProviders.Tvdb)).FirstOrDefault(id => !string.IsNullOrEmpty(id));
+
+ yield return group;
+ }
+ }
+ }
+
+ private static void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> results)
+ {
+ results.Add(series);
+ visited.Add(series);
+
+ var links = linksMap[series];
+
+ foreach (var s in links)
+ {
+ if (!visited.Contains(s))
+ {
+ FindAllLinked(s, visited, linksMap, results);
+ }
+ }
+ }
+
+ private static bool ShareProviderId(Series a, Series b)
+ {
+ return a.ProviderIds.Any(id =>
+ {
+ string value;
+ return b.ProviderIds.TryGetValue(id.Key, out value) && id.Value == value;
+ });
+ }
+
+ public int Order
+ {
+ get
+ {
+ // Run after tvdb update task
+ return 1;
+ }
+ }
+ }
+
+ public class CleanMissingEpisodesEntryPoint : IServerEntryPoint
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _config;
+ private readonly ILogger _logger;
+ private readonly ILocalizationManager _localization;
+ private readonly IFileSystem _fileSystem;
+ private readonly object _libraryChangedSyncLock = new object();
+ private const int LibraryUpdateDuration = 180000;
+ private readonly ITaskManager _taskManager;
+
+ public CleanMissingEpisodesEntryPoint(ILibraryManager libraryManager, IServerConfigurationManager config, ILogger logger, ILocalizationManager localization, IFileSystem fileSystem, ITaskManager taskManager)
+ {
+ _libraryManager = libraryManager;
+ _config = config;
+ _logger = logger;
+ _localization = localization;
+ _fileSystem = fileSystem;
+ _taskManager = taskManager;
+ }
+
+ private Timer LibraryUpdateTimer { get; set; }
+
+ public void Run()
+ {
+ _libraryManager.ItemAdded += _libraryManager_ItemAdded;
+ }
+
+ private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
+ {
+ if (!FilterItem(e.Item))
+ {
+ return;
+ }
+
+ lock (_libraryChangedSyncLock)
+ {
+ if (LibraryUpdateTimer == null)
+ {
+ LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite);
+ }
+ else
+ {
+ LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
+ }
+ }
+ }
+
+ private async void LibraryUpdateTimerCallback(object state)
+ {
+ try
+ {
+ if (MissingEpisodeProvider.IsRunning)
+ {
+ return;
+ }
+
+ if (_libraryManager.IsScanRunning)
+ {
+ return;
+ }
+
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+
+ }).Cast<Series>().ToList();
+
+ var seriesGroups = SeriesPostScanTask.FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
+
+ await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
+ .Run(seriesGroups, false, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in SeriesPostScanTask", ex);
+ }
+ }
+
+ private bool FilterItem(BaseItem item)
+ {
+ return item is Episode && item.LocationType != LocationType.Virtual;
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ if (LibraryUpdateTimer != null)
+ {
+ LibraryUpdateTimer.Dispose();
+ LibraryUpdateTimer = null;
+ }
+
+ _libraryManager.ItemAdded -= _libraryManager_ItemAdded;
+ }
+ }
+ }
+}