Browse Source

Merge pull request #1683 from MediaBrowser/dev

Dev
Luke 9 years ago
parent
commit
9126a8555b

+ 16 - 14
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="fileInfo">The file information.</param>
         /// <param name="parent">The parent.</param>
         /// <returns>BaseItem.</returns>
-        BaseItem ResolvePath(FileSystemMetadata fileInfo, 
+        BaseItem ResolvePath(FileSystemMetadata fileInfo,
             Folder parent = null);
 
         /// <summary>
@@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Library
         /// <param name="parent">The parent.</param>
         /// <param name="collectionType">Type of the collection.</param>
         /// <returns>List{``0}.</returns>
-        IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, 
+        IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
             IDirectoryService directoryService,
-            Folder parent, string 
+            Folder parent, string
             collectionType = null);
 
         /// <summary>
@@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="path">The path.</param>
         /// <returns>BaseItem.</returns>
         BaseItem FindByPath(string path);
-        
+
         /// <summary>
         /// Gets the artist.
         /// </summary>
@@ -156,7 +156,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="id">The identifier.</param>
         /// <returns>BaseItem.</returns>
         BaseItem GetMemoryItemById(Guid id);
-        
+
         /// <summary>
         /// Gets the intros.
         /// </summary>
@@ -243,6 +243,8 @@ namespace MediaBrowser.Controller.Library
         /// <returns>BaseItem.</returns>
         BaseItem RetrieveItem(Guid id);
 
+        bool IsScanRunning { get; }
+
         /// <summary>
         /// Occurs when [item added].
         /// </summary>
@@ -290,7 +292,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="path">The path.</param>
         /// <returns>System.String.</returns>
         string GetConfiguredContentType(string path);
-        
+
         /// <summary>
         /// Normalizes the root path list.
         /// </summary>
@@ -332,8 +334,8 @@ namespace MediaBrowser.Controller.Library
         Task<UserView> GetNamedView(User user,
             string name,
             string parentId,
-            string viewType, 
-            string sortName, 
+            string viewType,
+            string sortName,
             CancellationToken cancellationToken);
 
         /// <summary>
@@ -346,8 +348,8 @@ namespace MediaBrowser.Controller.Library
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
         Task<UserView> 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);
-        
+
         /// <summary>
         /// Determines whether [is video file] [the specified path].
         /// </summary>
@@ -477,14 +479,14 @@ namespace MediaBrowser.Controller.Library
         /// <param name="query">The query.</param>
         /// <returns>List&lt;PersonInfo&gt;.</returns>
         List<PersonInfo> GetPeople(InternalPeopleQuery query);
-        
+
         /// <summary>
         /// Gets the people items.
         /// </summary>
         /// <param name="query">The query.</param>
         /// <returns>List&lt;Person&gt;.</returns>
         List<Person> GetPeopleItems(InternalPeopleQuery query);
-        
+
         /// <summary>
         /// Gets all people names.
         /// </summary>
@@ -559,7 +561,7 @@ namespace MediaBrowser.Controller.Library
         /// <param name="query">The query.</param>
         /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
         QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query);
-        
+
         /// <summary>
         /// Ignores the file.
         /// </summary>

+ 2 - 0
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -46,6 +46,8 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
         Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+
+        string ApplyDuration(string streamPath, TimeSpan duration);
     }
     public interface IConfigurableTunerHost
     {

+ 13 - 5
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<IGrouping<string, Series>> series, CancellationToken cancellationToken)
+        public async Task Run(List<IGrouping<string, Series>> 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<string, Series> group, CancellationToken cancellationToken)
+        private async Task Run(IGrouping<string, Series> 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);
         }

+ 119 - 8
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<double> progress, CancellationToken cancellationToken)
         {
-            var seriesList = _libraryManager.RootFolder
-                .GetRecursiveChildren(i => i is Series)
-                .Cast<Series>()
-                .ToList();
+            var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+            {
+                IncludeItemTypes = new[] { typeof(Series).Name },
+                Recursive = true
+
+            }).Cast<Series>().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<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList)
+        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());
 
@@ -102,7 +109,7 @@ namespace MediaBrowser.Providers.TV
             }
         }
 
-        private void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> results)
+        private static void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> 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<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);
+        }
+
+        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;
+            }
+        }
+    }
 }

+ 3 - 0
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -143,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Library
         private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
         private readonly Func<IProviderManager> _providerManagerFactory;
         private readonly Func<IUserViewManager> _userviewManager;
+        public bool IsScanRunning { get; private set; }
 
         /// <summary>
         /// The _library items cache
@@ -1102,6 +1103,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>Task.</returns>
         public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
         {
+            IsScanRunning = true;
             _libraryMonitorFactory().Stop();
 
             try
@@ -1111,6 +1113,7 @@ namespace MediaBrowser.Server.Implementations.Library
             finally
             {
                 _libraryMonitorFactory().Start();
+                IsScanRunning = false;
             }
         }
 

+ 6 - 3
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);
                 }
             }
 

+ 17 - 6
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<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+        private async Task<Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>> 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<MediaSourceInfo, ITunerHost, SemaphoreSlim>(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);

+ 8 - 0
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<IEnumerable<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
         {
             var options = new HttpRequestOptions

+ 5 - 0
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;
+        }
     }
 }

+ 5 - 0
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;
+        }
     }
 }