diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index c8b3d51317..0f6b10b43a 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library
/// The file information.
/// The parent.
/// BaseItem.
- BaseItem ResolvePath(FileSystemMetadata fileInfo,
+ BaseItem ResolvePath(FileSystemMetadata fileInfo,
Folder parent = null);
///
@@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Library
/// The parent.
/// Type of the collection.
/// List{``0}.
- IEnumerable ResolvePaths(IEnumerable files,
+ IEnumerable ResolvePaths(IEnumerable files,
IDirectoryService directoryService,
- Folder parent, string
+ Folder parent, string
collectionType = null);
///
@@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Library
/// The path.
/// BaseItem.
BaseItem FindByPath(string path);
-
+
///
/// Gets the artist.
///
@@ -156,7 +156,7 @@ namespace MediaBrowser.Controller.Library
/// The identifier.
/// BaseItem.
BaseItem GetMemoryItemById(Guid id);
-
+
///
/// Gets the intros.
///
@@ -243,6 +243,8 @@ namespace MediaBrowser.Controller.Library
/// BaseItem.
BaseItem RetrieveItem(Guid id);
+ bool IsScanRunning { get; }
+
///
/// Occurs when [item added].
///
@@ -290,7 +292,7 @@ namespace MediaBrowser.Controller.Library
/// The path.
/// System.String.
string GetConfiguredContentType(string path);
-
+
///
/// Normalizes the root path list.
///
@@ -332,8 +334,8 @@ namespace MediaBrowser.Controller.Library
Task GetNamedView(User user,
string name,
string parentId,
- string viewType,
- string sortName,
+ string viewType,
+ string sortName,
CancellationToken cancellationToken);
///
@@ -346,8 +348,8 @@ namespace MediaBrowser.Controller.Library
/// The cancellation token.
/// Task<UserView>.
Task GetNamedView(User user,
- string name,
- string viewType,
+ string name,
+ string viewType,
string sortName,
CancellationToken cancellationToken);
@@ -393,7 +395,7 @@ namespace MediaBrowser.Controller.Library
string viewType,
string sortName,
CancellationToken cancellationToken);
-
+
///
/// Determines whether [is video file] [the specified path].
///
@@ -477,14 +479,14 @@ namespace MediaBrowser.Controller.Library
/// The query.
/// List<PersonInfo>.
List GetPeople(InternalPeopleQuery query);
-
+
///
/// Gets the people items.
///
/// The query.
/// List<Person>.
List GetPeopleItems(InternalPeopleQuery query);
-
+
///
/// Gets all people names.
///
@@ -559,7 +561,7 @@ namespace MediaBrowser.Controller.Library
/// The query.
/// QueryResult<BaseItem>.
QueryResult GetItemsResult(InternalItemsQuery query);
-
+
///
/// Ignores the file.
///
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 498602ddf9..1e7aa3de5a 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -46,6 +46,8 @@ namespace MediaBrowser.Controller.LiveTv
/// The cancellation token.
/// Task<List<MediaSourceInfo>>.
Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+
+ string ApplyDuration(string streamPath, TimeSpan duration);
}
public interface IConfigurableTunerHost
{
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index ad55c186a5..8ae9aa5bb2 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -27,6 +27,8 @@ namespace MediaBrowser.Providers.TV
private readonly IFileSystem _fileSystem;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private static readonly SemaphoreSlim _resourceLock = new SemaphoreSlim(1, 1);
+ public static bool IsRunning = false;
public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem)
{
@@ -37,13 +39,16 @@ namespace MediaBrowser.Providers.TV
_fileSystem = fileSystem;
}
- public async Task Run(IEnumerable> series, CancellationToken cancellationToken)
+ public async Task Run(List> series, bool addNewItems, CancellationToken cancellationToken)
{
+ await _resourceLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+ IsRunning = true;
+
foreach (var seriesGroup in series)
{
try
{
- await Run(seriesGroup, cancellationToken).ConfigureAwait(false);
+ await Run(seriesGroup, addNewItems, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -58,9 +63,12 @@ namespace MediaBrowser.Providers.TV
_logger.ErrorException("Error in missing episode provider for series id {0}", ex, seriesGroup.Key);
}
}
+
+ IsRunning = false;
+ _resourceLock.Release();
}
- private async Task Run(IGrouping group, CancellationToken cancellationToken)
+ private async Task Run(IGrouping group, bool addNewItems, CancellationToken cancellationToken)
{
var tvdbId = group.Key;
@@ -110,7 +118,7 @@ namespace MediaBrowser.Providers.TV
var hasNewEpisodes = false;
- if (_config.Configuration.EnableInternetProviders)
+ if (_config.Configuration.EnableInternetProviders && addNewItems)
{
var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));
@@ -427,7 +435,7 @@ namespace MediaBrowser.Providers.TV
await season.AddChild(episode, cancellationToken).ConfigureAwait(false);
- await episode.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
+ await episode.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
{
}, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index 5428e6c920..0fd6fa56f2 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -11,6 +11,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Providers.TV
{
@@ -46,14 +50,17 @@ namespace MediaBrowser.Providers.TV
private async Task RunInternal(IProgress progress, CancellationToken cancellationToken)
{
- var seriesList = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series)
- .Cast()
- .ToList();
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true
+
+ }).Cast().ToList();
var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
- await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
+ await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
+ .Run(seriesGroups, true, cancellationToken).ConfigureAwait(false);
var numComplete = 0;
@@ -82,7 +89,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private IEnumerable> FindSeriesGroups(List seriesList)
+ internal static IEnumerable> FindSeriesGroups(List seriesList)
{
var links = seriesList.ToDictionary(s => s, s => seriesList.Where(c => c != s && ShareProviderId(s, c)).ToList());
@@ -102,7 +109,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private void FindAllLinked(Series series, HashSet visited, IDictionary> linksMap, List results)
+ private static void FindAllLinked(Series series, HashSet visited, IDictionary> linksMap, List results)
{
results.Add(series);
visited.Add(series);
@@ -118,7 +125,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private bool ShareProviderId(Series a, Series b)
+ private static bool ShareProviderId(Series a, Series b)
{
return a.ProviderIds.Any(id =>
{
@@ -137,4 +144,108 @@ namespace MediaBrowser.Providers.TV
}
}
+ 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)
+ {
+ if (MissingEpisodeProvider.IsRunning)
+ {
+ return;
+ }
+
+ if (_libraryManager.IsScanRunning)
+ {
+ return ;
+ }
+
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true
+
+ }).Cast().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);
+ }
+
+ private bool FilterItem(BaseItem item)
+ {
+ return item is Episode && item.LocationType != LocationType.Virtual;
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ if (LibraryUpdateTimer != null)
+ {
+ LibraryUpdateTimer.Dispose();
+ LibraryUpdateTimer = null;
+ }
+
+ _libraryManager.ItemAdded -= _libraryManager_ItemAdded;
+ }
+ }
+ }
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index ccba293a38..17c4f59ba0 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -143,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Library
private readonly Func _libraryMonitorFactory;
private readonly Func _providerManagerFactory;
private readonly Func _userviewManager;
+ public bool IsScanRunning { get; private set; }
///
/// The _library items cache
@@ -1102,6 +1103,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// Task.
public async Task ValidateMediaLibraryInternal(IProgress progress, CancellationToken cancellationToken)
{
+ IsScanRunning = true;
_libraryMonitorFactory().Stop();
try
@@ -1111,6 +1113,7 @@ namespace MediaBrowser.Server.Implementations.Library
finally
{
_libraryMonitorFactory().Start();
+ IsScanRunning = false;
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 3c7ee55b7d..98249f5ac9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -42,10 +42,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file {0}", targetFile);
- var durationToken = new CancellationTokenSource(duration);
- var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ if (!mediaSource.RunTimeTicks.HasValue)
+ {
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
+ await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 60ff23b04b..d85e8ba3f9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -591,7 +591,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new ApplicationException("Tuner not found.");
}
- private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+ private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
{
_logger.Info("Streaming Channel " + channelId);
@@ -599,7 +599,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
try
{
- return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+ return new Tuple(result.Item1, hostInstance, result.Item2);
}
catch (Exception e)
{
@@ -797,8 +799,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
//await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
- var duration = recordingEndDate - DateTime.UtcNow;
-
var recorder = await GetRecorder().ConfigureAwait(false);
if (recorder is EncodedRecorder)
@@ -816,6 +816,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recording.DateLastUpdated = DateTime.UtcNow;
_recordingProvider.AddOrUpdate(recording);
+ var duration = recordingEndDate - DateTime.UtcNow;
+
_logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
_logger.Info("Writing file to path: " + recordPath);
@@ -823,10 +825,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
Action onStarted = () =>
{
- result.Item2.Release();
+ result.Item3.Release();
isResourceOpen = false;
};
+ var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
+
+ // If it supports supplying duration via url
+ if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase))
+ {
+ mediaStreamInfo.Path = pathWithDuration;
+ mediaStreamInfo.RunTimeTicks = duration.Ticks;
+ }
+
await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
recording.Status = RecordingStatus.Completed;
@@ -836,7 +847,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
if (isResourceOpen)
{
- result.Item2.Release();
+ result.Item3.Release();
}
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 469767c658..a3e5589e88 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -59,6 +59,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return id;
}
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&";
+ streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture);
+
+ return streamPath;
+ }
+
private async Task> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 523f14dfc8..c874e51b61 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -146,5 +146,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index ffd85fd18a..1e571c84fb 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -164,5 +164,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return list;
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}