Bladeren bron

Merge pull request #1786 from MediaBrowser/dev

Dev
Luke 9 jaren geleden
bovenliggende
commit
e205caee03

+ 1 - 3
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -550,9 +550,7 @@ namespace MediaBrowser.Api.LiveTv
 
             var response = await _httpClient.Get(new HttpRequestOptions
             {
-                Url = "https://json.schedulesdirect.org/20141201/available/countries",
-                CacheLength = TimeSpan.FromDays(1),
-                CacheMode = CacheMode.Unconditional
+                Url = "https://json.schedulesdirect.org/20141201/available/countries"
 
             }).ConfigureAwait(false);
 

+ 8 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1518,6 +1518,13 @@ namespace MediaBrowser.Api.Playback
                     }
                 }
                 else if (i == 25)
+                {
+                    if (videoRequest != null)
+                    {
+                        videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                    }
+                }
+                else if (i == 26)
                 {
                     if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
                     {
@@ -1528,7 +1535,7 @@ namespace MediaBrowser.Api.Playback
                         }
                     }
                 }
-                else if (i == 26)
+                else if (i == 27)
                 {
                     request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
                 }

+ 5 - 1
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -98,6 +98,9 @@ namespace MediaBrowser.Api.Subtitles
 
         [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public long? EndPositionTicks { get; set; }
+
+        [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool CopyTimestamps { get; set; }
     }
 
     [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
@@ -175,7 +178,7 @@ namespace MediaBrowser.Api.Subtitles
 
                 var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
 
-                var url = string.Format("stream.vtt?StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
+                var url = string.Format("stream.vtt?CopyTimestamps=true,StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
                     positionTicks.ToString(CultureInfo.InvariantCulture),
                     endPositionTicks.ToString(CultureInfo.InvariantCulture),
                     accessToken);
@@ -222,6 +225,7 @@ namespace MediaBrowser.Api.Subtitles
                 request.Format,
                 request.StartPositionTicks,
                 request.EndPositionTicks,
+                request.CopyTimestamps,
                 CancellationToken.None).ConfigureAwait(false);
         }
 

+ 12 - 9
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -332,13 +332,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasAlbumArtist>();
+            }).Items.Cast<IHasAlbumArtist>();
 
             var artists = _libraryManager.GetAlbumArtists(items);
 
@@ -347,13 +348,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasArtist>();
+            }).Items.Cast<IHasArtist>();
 
             var artists = _libraryManager.GetArtists(items);
 
@@ -362,13 +364,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasAlbumArtist>();
+            }).Items.Cast<IHasAlbumArtist>();
 
             var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
 

+ 1 - 7
MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs

@@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <summary>
         /// Gets the subtitles.
         /// </summary>
-        /// <param name="itemId">The item identifier.</param>
-        /// <param name="mediaSourceId">The media source identifier.</param>
-        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="outputFormat">The output format.</param>
-        /// <param name="startTimeTicks">The start time ticks.</param>
-        /// <param name="endTimeTicks">The end time ticks.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{Stream}.</returns>
         Task<Stream> GetSubtitles(string itemId,
             string mediaSourceId,
@@ -24,6 +17,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             string outputFormat,
             long startTimeTicks,
             long? endTimeTicks,
+            bool preserveOriginalTimestamps,
             CancellationToken cancellationToken);
 
         /// <summary>

+ 4 - 2
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -58,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             string outputFormat,
             long startTimeTicks,
             long? endTimeTicks,
+            bool preserveOriginalTimestamps,
             CancellationToken cancellationToken)
         {
             var ms = new MemoryStream();
@@ -68,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
                 var trackInfo = reader.Parse(stream, cancellationToken);
 
-                FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false);
+                FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
 
                 var writer = GetWriter(outputFormat);
 
@@ -116,6 +117,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             string outputFormat,
             long startTimeTicks,
             long? endTimeTicks,
+            bool preserveOriginalTimestamps,
             CancellationToken cancellationToken)
         {
             var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
@@ -130,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
             using (var stream = subtitle.Item1)
             {
-                return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
+                return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken).ConfigureAwait(false);
             }
         }
 

+ 45 - 4
MediaBrowser.Server.Implementations/IO/FileRefresher.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -24,9 +25,14 @@ namespace MediaBrowser.Server.Implementations.IO
         private readonly List<string> _affectedPaths = new List<string>();
         private Timer _timer;
         private readonly object _timerLock = new object();
+        public string Path { get; private set; }
+
+        public event EventHandler<EventArgs> Completed;
 
         public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger)
         {
+            logger.Debug("New file refresher created for {0}", path);
+            Path = path;
             _affectedPaths.Add(path);
 
             _fileSystem = fileSystem;
@@ -36,7 +42,24 @@ namespace MediaBrowser.Server.Implementations.IO
             Logger = logger;
         }
 
-        private void RestartTimer()
+        private void AddAffectedPath(string path)
+        {
+            if (!_affectedPaths.Contains(path, StringComparer.Ordinal))
+            {
+                _affectedPaths.Add(path);
+            }
+        }
+
+        public void AddPath(string path)
+        {
+            lock (_timerLock)
+            {
+                AddAffectedPath(path);
+            }
+            RestartTimer();
+        }
+
+        public void RestartTimer()
         {
             lock (_timerLock)
             {
@@ -51,6 +74,23 @@ namespace MediaBrowser.Server.Implementations.IO
             }
         }
 
+        public void ResetPath(string path, string affectedFile)
+        {
+            lock (_timerLock)
+            {
+                Logger.Debug("Resetting file refresher from {0} to {1}", Path, path);
+
+                Path = path;
+                AddAffectedPath(path);
+
+                if (!string.IsNullOrWhiteSpace(affectedFile))
+                {
+                    AddAffectedPath(affectedFile);
+                }
+            }
+            RestartTimer();
+        }
+
         private async void OnTimerCallback(object state)
         {
             // Extend the timer as long as any of the paths are still being written to.
@@ -64,10 +104,11 @@ namespace MediaBrowser.Server.Implementations.IO
             Logger.Debug("Timer stopped.");
 
             DisposeTimer();
+            EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger);
 
             try
             {
-                await ProcessPathChanges(_affectedPaths).ConfigureAwait(false);
+                await ProcessPathChanges(_affectedPaths.ToList()).ConfigureAwait(false);
             }
             catch (Exception ex)
             {
@@ -130,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.IO
             {
                 item = LibraryManager.FindByPath(path, null);
 
-                path = Path.GetDirectoryName(path);
+                path = System.IO.Path.GetDirectoryName(path);
             }
 
             if (item != null)
@@ -222,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.IO
             }
         }
 
-        public void DisposeTimer()
+        private void DisposeTimer()
         {
             lock (_timerLock)
             {

+ 59 - 217
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -26,13 +26,9 @@ namespace MediaBrowser.Server.Implementations.IO
         /// </summary>
         private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
         /// <summary>
-        /// The update timer
-        /// </summary>
-        private Timer _updateTimer;
-        /// <summary>
         /// The affected paths
         /// </summary>
-        private readonly ConcurrentDictionary<string, string> _affectedPaths = new ConcurrentDictionary<string, string>();
+        private readonly List<FileRefresher> _activeRefreshers = new List<FileRefresher>();
 
         /// <summary>
         /// A dynamic list of paths that should be ignored.  Added to during our own file sytem modifications.
@@ -44,8 +40,8 @@ namespace MediaBrowser.Server.Implementations.IO
         /// </summary>
         private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string>
         {
-            "thumbs.db", 
-            "small.jpg", 
+            "thumbs.db",
+            "small.jpg",
             "albumart.jpg",
 
             // WMC temp recording directories that will constantly be written to
@@ -53,11 +49,6 @@ namespace MediaBrowser.Server.Implementations.IO
             "TempSBE"
         };
 
-        /// <summary>
-        /// The timer lock
-        /// </summary>
-        private readonly object _timerLock = new object();
-
         /// <summary>
         /// Add the path to our temporary ignore list.  Use when writing to a path within our listening scope.
         /// </summary>
@@ -463,226 +454,58 @@ namespace MediaBrowser.Server.Implementations.IO
             if (monitorPath)
             {
                 // Avoid implicitly captured closure
-                var affectedPath = path;
-                _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
-            }
-
-            RestartTimer();
-        }
-
-        private void RestartTimer()
-        {
-            lock (_timerLock)
-            {
-                if (_updateTimer == null)
-                {
-                    _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
-                }
-                else
-                {
-                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
-                }
-            }
-        }
-
-        /// <summary>
-        /// Timers the stopped.
-        /// </summary>
-        /// <param name="stateInfo">The state info.</param>
-        private async void TimerStopped(object stateInfo)
-        {
-            // Extend the timer as long as any of the paths are still being written to.
-            if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
-            {
-                Logger.Info("Timer extended.");
-                RestartTimer();
-                return;
-            }
-
-            Logger.Debug("Timer stopped.");
-
-            DisposeTimer();
-
-            var paths = _affectedPaths.Keys.ToList();
-            _affectedPaths.Clear();
-
-            try
-            {
-                await ProcessPathChanges(paths).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                Logger.ErrorException("Error processing directory changes", ex);
+                CreateRefresher(path);
             }
         }
 
-        private bool IsFileLocked(string path)
+        private void CreateRefresher(string path)
         {
-            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
-            {
-                // Causing lockups on linux
-                return false;
-            }
+            var parentPath = Path.GetDirectoryName(path);
 
-            try
+            lock (_activeRefreshers)
             {
-                var data = _fileSystem.GetFileSystemInfo(path);
-
-                if (!data.Exists
-                    || data.IsDirectory
-
-                    // Opening a writable stream will fail with readonly files
-                    || data.Attributes.HasFlag(FileAttributes.ReadOnly))
+                var refreshers = _activeRefreshers.ToList();
+                foreach (var refresher in refreshers)
                 {
-                    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;
+                    // Path is already being refreshed
+                    if (string.Equals(path, refresher.Path, StringComparison.Ordinal))
+                    {
+                        refresher.RestartTimer();
+                        return;
+                    }
 
-            try
-            {
-                using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite))
-                {
-                    if (_updateTimer != null)
+                    // Parent folder is already being refreshed
+                    if (_fileSystem.ContainsSubPath(refresher.Path, path))
                     {
-                        //file is not locked
-                        return false;
+                        refresher.AddPath(path);
+                        return;
                     }
-                }
-            }
-            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;
-            }
 
-            return false;
-        }
+                    // New path is a parent
+                    if (_fileSystem.ContainsSubPath(path, refresher.Path))
+                    {
+                        refresher.ResetPath(path, null);
+                        return;
+                    }
 
-        private void DisposeTimer()
-        {
-            lock (_timerLock)
-            {
-                if (_updateTimer != null)
-                {
-                    _updateTimer.Dispose();
-                    _updateTimer = null;
+                    // They are siblings. Rebase the refresher to the parent folder.
+                    if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
+                    {
+                        refresher.ResetPath(parentPath, path);
+                        return;
+                    }
                 }
-            }
-        }
-
-        /// <summary>
-        /// Processes the path changes.
-        /// </summary>
-        /// <param name="paths">The paths.</param>
-        /// <returns>Task.</returns>
-        private async Task ProcessPathChanges(List<string> 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<RefreshMediaLibraryTask>();
-                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);
-                }
+                var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger);
+                newRefresher.Completed += NewRefresher_Completed;
+                _activeRefreshers.Add(newRefresher);
             }
         }
 
-        /// <summary>
-        /// Gets the affected base item.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>BaseItem.</returns>
-        private BaseItem GetAffectedBaseItem(string path)
+        private void NewRefresher_Completed(object sender, EventArgs e)
         {
-            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;
+            var refresher = (FileRefresher)sender;
+            DisposeRefresher(refresher);
         }
 
         /// <summary>
@@ -713,10 +536,29 @@ namespace MediaBrowser.Server.Implementations.IO
                 watcher.Dispose();
             }
 
-            DisposeTimer();
-
             _fileSystemWatchers.Clear();
-            _affectedPaths.Clear();
+            DisposeRefreshers();
+        }
+
+        private void DisposeRefresher(FileRefresher refresher)
+        {
+            lock (_activeRefreshers)
+            {
+                refresher.Dispose();
+                _activeRefreshers.Remove(refresher);
+            }
+        }
+
+        private void DisposeRefreshers()
+        {
+            lock (_activeRefreshers)
+            {
+                foreach (var refresher in _activeRefreshers.ToList())
+                {
+                    refresher.Dispose();
+                }
+                _activeRefreshers.Clear();
+            }
         }
 
         /// <summary>

+ 1 - 4
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1959,10 +1959,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
             var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
 
-            info.Days = new List<DayOfWeek>
-            {
-                program.StartDate.ToLocalTime().DayOfWeek
-            };
+            info.Days = defaults.Item1.Days;
 
             info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
 

+ 1 - 1
MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs

@@ -748,7 +748,7 @@ namespace MediaBrowser.Server.Implementations.Sync
 
             _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
 
-            using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
+            using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false))
             {
                 using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {

+ 0 - 3
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -356,9 +356,6 @@ namespace MediaBrowser.WebDashboard.Api
             DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
             DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
             DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
-            DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "src");
-            DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "utils");
-            _fileSystem.DeleteFile(Path.Combine(bowerPath, "material-design-lite", "gulpfile.babel.js"));
 
             _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
             _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);