瀏覽代碼

Merge pull request #2618 from MediaBrowser/beta

Beta
Luke 8 年之前
父節點
當前提交
21edff3ac2
共有 100 個文件被更改,包括 585 次插入792 次删除
  1. 2 2
      BDInfo/BDROM.cs
  2. 1 1
      Emby.Common.Implementations/Devices/DeviceId.cs
  3. 1 1
      Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs
  4. 1 1
      Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
  5. 37 19
      Emby.Common.Implementations/IO/ManagedFileSystem.cs
  6. 28 0
      Emby.Common.Implementations/IO/SharpCifsFileSystem.cs
  7. 1 1
      Emby.Common.Implementations/Net/NetAcceptSocket.cs
  8. 2 2
      Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  9. 1 1
      Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
  10. 3 3
      Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs
  11. 2 2
      Emby.Drawing.Net/GDIImageEncoder.cs
  12. 7 7
      Emby.Drawing/ImageProcessor.cs
  13. 34 15
      Emby.Server.Core/ApplicationHost.cs
  14. 6 6
      Emby.Server.Core/IO/LibraryMonitor.cs
  15. 4 15
      Emby.Server.Implementations/Channels/ChannelManager.cs
  16. 3 3
      Emby.Server.Implementations/Collections/CollectionManager.cs
  17. 1 111
      Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
  18. 32 93
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  19. 1 6
      Emby.Server.Implementations/Dto/DtoService.cs
  20. 6 6
      Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  21. 7 3
      Emby.Server.Implementations/HttpServer/FileWriter.cs
  22. 3 2
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  23. 8 3
      Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
  24. 2 18
      Emby.Server.Implementations/HttpServer/ResponseFilter.cs
  25. 1 1
      Emby.Server.Implementations/IO/FileRefresher.cs
  26. 2 2
      Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
  27. 1 1
      Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
  28. 35 6
      Emby.Server.Implementations/Library/LibraryManager.cs
  29. 29 50
      Emby.Server.Implementations/Library/MusicManager.cs
  30. 5 2
      Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
  31. 0 1
      Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
  32. 0 1
      Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
  33. 10 1
      Emby.Server.Implementations/Library/SearchEngine.cs
  34. 4 3
      Emby.Server.Implementations/Library/UserManager.cs
  35. 9 19
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  36. 57 28
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  37. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
  38. 1 1
      Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
  39. 4 3
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  40. 2 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  41. 5 0
      Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
  42. 5 3
      Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs
  43. 1 1
      Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
  44. 4 2
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  45. 1 1
      Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  46. 1 1
      Emby.Server.Implementations/Security/MBLicenseFile.cs
  47. 1 1
      Emby.Server.Implementations/Session/HttpSessionController.cs
  48. 2 1
      Emby.Server.Implementations/Session/SessionManager.cs
  49. 1 1
      Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs
  50. 2 2
      Emby.Server.Implementations/Updates/InstallationManager.cs
  51. 1 1
      MediaBrowser.Api/ApiEntryPoint.cs
  52. 2 2
      MediaBrowser.Api/BaseApiService.cs
  53. 2 2
      MediaBrowser.Api/ChannelService.cs
  54. 1 1
      MediaBrowser.Api/EnvironmentService.cs
  55. 1 1
      MediaBrowser.Api/Images/ImageByNameService.cs
  56. 2 2
      MediaBrowser.Api/Images/ImageService.cs
  57. 2 2
      MediaBrowser.Api/Images/RemoteImageService.cs
  58. 2 2
      MediaBrowser.Api/ItemLookupService.cs
  59. 1 8
      MediaBrowser.Api/ItemRefreshService.cs
  60. 0 1
      MediaBrowser.Api/ItemUpdateService.cs
  61. 5 5
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  62. 16 122
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  63. 1 4
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  64. 5 4
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  65. 6 0
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  66. 1 0
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  67. 1 36
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  68. 2 0
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  69. 2 0
      MediaBrowser.Api/Playback/StreamState.cs
  70. 1 1
      MediaBrowser.Api/PlaylistService.cs
  71. 0 5
      MediaBrowser.Api/Reports/ReportsService.cs
  72. 44 4
      MediaBrowser.Api/SearchService.cs
  73. 1 1
      MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
  74. 1 1
      MediaBrowser.Api/SimilarItemsHelper.cs
  75. 1 1
      MediaBrowser.Api/StartupWizardService.cs
  76. 1 1
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  77. 5 5
      MediaBrowser.Api/TvShowsService.cs
  78. 1 1
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  79. 9 1
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  80. 1 1
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  81. 4 4
      MediaBrowser.Api/UserService.cs
  82. 0 3
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  83. 1 1
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  84. 1 3
      MediaBrowser.Controller/Entities/AudioBook.cs
  85. 5 28
      MediaBrowser.Controller/Entities/BaseItem.cs
  86. 1 3
      MediaBrowser.Controller/Entities/Book.cs
  87. 3 3
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  88. 20 7
      MediaBrowser.Controller/Entities/Folder.cs
  89. 1 1
      MediaBrowser.Controller/Entities/Game.cs
  90. 1 1
      MediaBrowser.Controller/Entities/GameGenre.cs
  91. 1 1
      MediaBrowser.Controller/Entities/Genre.cs
  92. 0 1
      MediaBrowser.Controller/Entities/IHasSeries.cs
  93. 0 4
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  94. 9 1
      MediaBrowser.Controller/Entities/LinkedChild.cs
  95. 2 4
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  96. 1 4
      MediaBrowser.Controller/Entities/TV/Episode.cs
  97. 2 5
      MediaBrowser.Controller/Entities/TV/Season.cs
  98. 7 22
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  99. 29 26
      MediaBrowser.Controller/Entities/Video.cs
  100. 2 0
      MediaBrowser.Controller/Library/ILibraryManager.cs

+ 2 - 2
BDInfo/BDROM.cs

@@ -96,7 +96,7 @@ namespace BDInfo
             }
 
             DirectoryRoot =
-                _fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
+                _fileSystem.GetDirectoryInfo(_fileSystem.GetDirectoryName(DirectoryBDMV.FullName));
             DirectoryBDJO =
                 GetDirectory("BDJO", DirectoryBDMV, 0);
             DirectoryCLIPINF =
@@ -349,7 +349,7 @@ namespace BDInfo
                 {
                     return dir;
                 }
-                var parentFolder = Path.GetDirectoryName(dir.FullName);
+                var parentFolder = _fileSystem.GetDirectoryName(dir.FullName);
                 if (string.IsNullOrEmpty(parentFolder))
                 {
                     dir = null;

+ 1 - 1
Emby.Common.Implementations/Devices/DeviceId.cs

@@ -57,7 +57,7 @@ namespace Emby.Common.Implementations.Devices
             {
                 var path = CachePath;
 
-				_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+				_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
                 lock (_syncLock)
                 {

+ 1 - 1
Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs

@@ -131,4 +131,4 @@ namespace Emby.Common.Implementations.EnvironmentInfo
             Environment.SetEnvironmentVariable(name, value);
         }
     }
-}
+}

+ 1 - 1
Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -418,7 +418,7 @@ namespace Emby.Common.Implementations.HttpClientManager
 
         private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
         {
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(responseCachePath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(responseCachePath));
 
             using (var responseStream = response.Content)
             {

+ 37 - 19
Emby.Common.Implementations/IO/ManagedFileSystem.cs

@@ -546,24 +546,6 @@ namespace Emby.Common.Implementations.IO
             return Path.DirectorySeparatorChar;
         }
 
-        public bool AreEqual(string path1, string path2)
-        {
-            if (path1 == null && path2 == null)
-            {
-                return true;
-            }
-
-            if (path1 == null || path2 == null)
-            {
-                return false;
-            }
-
-            path1 = path1.TrimEnd(GetDirectorySeparatorChar(path1));
-            path2 = path2.TrimEnd(GetDirectorySeparatorChar(path2));
-
-            return string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase);
-        }
-
         public bool ContainsSubPath(string parentPath, string path)
         {
             if (string.IsNullOrEmpty(parentPath))
@@ -588,7 +570,7 @@ namespace Emby.Common.Implementations.IO
                 throw new ArgumentNullException("path");
             }
 
-            var parent = Path.GetDirectoryName(path);
+            var parent = GetDirectoryName(path);
 
             if (!string.IsNullOrEmpty(parent))
             {
@@ -598,6 +580,16 @@ namespace Emby.Common.Implementations.IO
             return true;
         }
 
+        public string GetDirectoryName(string path)
+        {
+            if (_sharpCifsFileSystem.IsEnabledForPath(path))
+            {
+                return _sharpCifsFileSystem.GetDirectoryName(path);
+            }
+
+            return Path.GetDirectoryName(path);
+        }
+
         public string NormalizePath(string path)
         {
             if (string.IsNullOrEmpty(path))
@@ -605,6 +597,11 @@ namespace Emby.Common.Implementations.IO
                 throw new ArgumentNullException("path");
             }
 
+            if (_sharpCifsFileSystem.IsEnabledForPath(path))
+            {
+                return _sharpCifsFileSystem.NormalizePath(path);
+            }
+
             if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
             {
                 return path;
@@ -613,6 +610,21 @@ namespace Emby.Common.Implementations.IO
             return path.TrimEnd(GetDirectorySeparatorChar(path));
         }
 
+        public bool AreEqual(string path1, string path2)
+        {
+            if (path1 == null && path2 == null)
+            {
+                return true;
+            }
+
+            if (path1 == null || path2 == null)
+            {
+                return false;
+            }
+
+            return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase);
+        }
+
         public string GetFileNameWithoutExtension(FileSystemMetadata info)
         {
             if (info.IsDirectory)
@@ -637,11 +649,17 @@ namespace Emby.Common.Implementations.IO
 
             // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
 
+            if (_sharpCifsFileSystem.IsEnabledForPath(path))
+            {
+                return true;
+            }
+
             if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
                 !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
             {
                 return false;
             }
+
             return true;
 
             //return Path.IsPathRooted(path);

+ 28 - 0
Emby.Common.Implementations/IO/SharpCifsFileSystem.cs

@@ -30,6 +30,34 @@ namespace Emby.Common.Implementations.IO
             return path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase) || IsUncPath(path);
         }
 
+        public string NormalizePath(string path)
+        {
+            if (path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase))
+            {
+                return path;
+            }
+
+            if (IsUncPath(path))
+            {
+                return ConvertUncToSmb(path);
+            }
+
+            return path;
+        }
+
+        public string GetDirectoryName(string path)
+        {
+            var separator = GetDirectorySeparatorChar(path);
+            var result = Path.GetDirectoryName(path);
+
+            if (separator == '/')
+            {
+                result = result.Replace('\\', '/');
+            }
+
+            return result;
+        }
+
         public char GetDirectorySeparatorChar(string path)
         {
             if (path.IndexOf('/') != -1)

+ 1 - 1
Emby.Common.Implementations/Net/NetAcceptSocket.cs

@@ -100,7 +100,7 @@ namespace Emby.Common.Implementations.Net
 #if NET46
         public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
         {
-            var options = TransmitFileOptions.UseKernelApc;
+            var options = TransmitFileOptions.UseDefaultWorkerThread;
 
             var completionSource = new TaskCompletionSource<bool>();
 

+ 2 - 2
Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -158,7 +158,7 @@ namespace Emby.Common.Implementations.ScheduledTasks
                 _lastExecutionResult = value;
 
                 var path = GetHistoryFilePath();
-				_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+				_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
                 lock (_lastExecutionResultSyncLock)
                 {
@@ -575,7 +575,7 @@ namespace Emby.Common.Implementations.ScheduledTasks
         {
             var path = GetConfigurationFilePath();
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+			_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
             JsonSerializer.SerializeToFile(triggers, path);
         }

+ 1 - 1
Emby.Drawing.ImageMagick/ImageMagickEncoder.cs

@@ -91,7 +91,7 @@ namespace Emby.Drawing.ImageMagick
             try
             {
                 var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".webp");
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
 
                 using (var wand = new MagickWand(1, 1, new PixelWand("none", 1)))
                 {

+ 3 - 3
Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs

@@ -68,7 +68,7 @@ namespace Emby.Drawing.ImageMagick
 
             var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name;
             var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf");
-			fileSystem.CreateDirectory(Path.GetDirectoryName(tempPath));
+			fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath));
 
             using (var stream = typeof(PlayedIndicatorDrawer).Assembly.GetManifestResourceStream(namespacePath))
             {
@@ -78,7 +78,7 @@ namespace Emby.Drawing.ImageMagick
                 }
             }
 
-			fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
+			fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath));
 
             try
             {
@@ -108,7 +108,7 @@ namespace Emby.Drawing.ImageMagick
 
             }).ConfigureAwait(false);
 
-			fileSystem.CreateDirectory(Path.GetDirectoryName(filePath));
+			fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath));
 
             try
             {

+ 2 - 2
Emby.Drawing.Net/GDIImageEncoder.cs

@@ -81,7 +81,7 @@ namespace Emby.Drawing.Net
             {
                 using (var croppedImage = image.CropWhitespace())
                 {
-                    _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
+                    _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath));
 
                     using (var outputStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false))
                     {
@@ -135,7 +135,7 @@ namespace Emby.Drawing.Net
 
                         var outputFormat = GetOutputFormat(originalImage, selectedOutputFormat);
 
-                        _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+                        _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
 
                         // Save to the cache location
                         using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false))

+ 7 - 7
Emby.Drawing/ImageProcessor.cs

@@ -244,9 +244,9 @@ namespace Emby.Drawing
                     var newWidth = Convert.ToInt32(newSize.Width);
                     var newHeight = Convert.ToInt32(newSize.Height);
 
-                    _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+                    _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
                     var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
-                    _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
+                    _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
 
                     _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
                     CopyFile(tmpPath, cacheFilePath);
@@ -418,9 +418,9 @@ namespace Emby.Drawing
 
             try
             {
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(croppedImagePath));
                 var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(croppedImagePath));
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
 
                 _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
                 CopyFile(tmpPath, croppedImagePath);
@@ -592,7 +592,7 @@ namespace Emby.Drawing
                 try
                 {
                     var path = ImageSizeFile;
-                    _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+                    _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
                     _jsonSerializer.SerializeToFile(_cachedImagedSizes, path);
                 }
                 catch (Exception ex)
@@ -765,10 +765,10 @@ namespace Emby.Drawing
                 return enhancedImagePath;
             }
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath));
 
             var tmpPath = Path.Combine(_appPaths.TempDirectory, Path.ChangeExtension(Guid.NewGuid().ToString(), Path.GetExtension(enhancedImagePath)));
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
 
             await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, tmpPath, item, imageType, imageIndex).ConfigureAwait(false);
 

+ 34 - 15
Emby.Server.Core/ApplicationHost.cs

@@ -257,7 +257,7 @@ namespace Emby.Server.Core
         internal IPowerManagement PowerManagement { get; private set; }
         internal IImageEncoder ImageEncoder { get; private set; }
 
-        private readonly Action<string, string> _certificateGenerator;
+        private readonly Action<string, string, string> _certificateGenerator;
         private readonly Func<string> _defaultUserNameFactory;
 
         /// <summary>
@@ -274,7 +274,7 @@ namespace Emby.Server.Core
             ISystemEvents systemEvents,
             IMemoryStreamFactory memoryStreamFactory,
             INetworkManager networkManager,
-            Action<string, string> certificateGenerator,
+            Action<string, string, string> certificateGenerator,
             Func<string> defaultUsernameFactory)
             : base(applicationPaths,
                   logManager,
@@ -609,8 +609,8 @@ namespace Emby.Server.Core
 
             RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
 
-            CertificatePath = GetCertificatePath(true);
-            Certificate = GetCertificate(CertificatePath);
+            CertificateInfo = GetCertificateInfo(true);
+            Certificate = GetCertificate(CertificateInfo);
 
             HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets);
             HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
@@ -745,8 +745,10 @@ namespace Emby.Server.Core
             }
         }
 
-        private ICertificate GetCertificate(string certificateLocation)
+        private ICertificate GetCertificate(CertificateInfo info)
         {
+            var certificateLocation = info == null ? null : info.Path;
+
             if (string.IsNullOrWhiteSpace(certificateLocation))
             {
                 return null;
@@ -759,7 +761,7 @@ namespace Emby.Server.Core
                     return null;
                 }
 
-                X509Certificate2 localCert = new X509Certificate2(certificateLocation);
+                X509Certificate2 localCert = new X509Certificate2(certificateLocation, info.Password);
                 //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA;
                 if (!localCert.HasPrivateKey)
                 {
@@ -1064,7 +1066,7 @@ namespace Emby.Server.Core
             SyncManager.AddParts(GetExports<ISyncProvider>());
         }
 
-        private string CertificatePath { get; set; }
+        private CertificateInfo CertificateInfo { get; set; }
         private ICertificate Certificate { get; set; }
 
         private IEnumerable<string> GetUrlPrefixes()
@@ -1080,7 +1082,7 @@ namespace Emby.Server.Core
                     "http://"+i+":" + HttpPort + "/"
                 };
 
-                if (!string.IsNullOrWhiteSpace(CertificatePath))
+                if (CertificateInfo != null)
                 {
                     prefixes.Add("https://" + i + ":" + HttpsPort + "/");
                 }
@@ -1123,27 +1125,31 @@ namespace Emby.Server.Core
             }
         }
 
-        private string GetCertificatePath(bool generateCertificate)
+        private CertificateInfo GetCertificateInfo(bool generateCertificate)
         {
             if (!string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePath))
             {
                 // Custom cert
-                return ServerConfigurationManager.Configuration.CertificatePath;
+                return new CertificateInfo
+                {
+                    Path = ServerConfigurationManager.Configuration.CertificatePath
+                };
             }
 
             // Generate self-signed cert
             var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns);
-            var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "1").GetMD5().ToString("N") + ".pfx");
+            var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx");
+            var password = "embycert";
 
             if (generateCertificate)
             {
                 if (!FileSystemManager.FileExists(certPath))
                 {
-                    FileSystemManager.CreateDirectory(Path.GetDirectoryName(certPath));
+                    FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath));
 
                     try
                     {
-                        _certificateGenerator(certPath, certHost);
+                        _certificateGenerator(certPath, certHost, password);
                     }
                     catch (Exception ex)
                     {
@@ -1153,7 +1159,11 @@ namespace Emby.Server.Core
                 }
             }
 
-            return certPath;
+            return new CertificateInfo
+            {
+                Path = certPath,
+                Password = password
+            };
         }
 
         /// <summary>
@@ -1189,7 +1199,11 @@ namespace Emby.Server.Core
                 requiresRestart = true;
             }
 
-            if (!string.Equals(CertificatePath, GetCertificatePath(false), StringComparison.OrdinalIgnoreCase))
+            var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path;
+            var newCertInfo = GetCertificateInfo(false);
+            var newCertPath = newCertInfo == null ? null : newCertInfo.Path;
+
+            if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
             {
                 requiresRestart = true;
             }
@@ -1779,6 +1793,11 @@ namespace Emby.Server.Core
         {
             Container.Register(typeInterface, typeImplementation);
         }
+    }
 
+    internal class CertificateInfo
+    {
+        public string Path { get; set; }
+        public string Password { get; set; }
     }
 }

+ 6 - 6
Emby.Server.Core/IO/LibraryMonitor.cs

@@ -453,7 +453,7 @@ namespace Emby.Server.Core.IO
             // If the parent of an ignored path has a change event, ignore that too
             if (tempIgnorePaths.Any(i =>
             {
-                if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase))
+                if (_fileSystem.AreEqual(i, path))
                 {
                     Logger.Debug("Ignoring change to {0}", path);
                     return true;
@@ -466,10 +466,10 @@ namespace Emby.Server.Core.IO
                 }
 
                 // Go up a level
-                var parent = Path.GetDirectoryName(i);
+                var parent = _fileSystem.GetDirectoryName(i);
                 if (!string.IsNullOrEmpty(parent))
                 {
-                    if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
+                    if (_fileSystem.AreEqual(parent, path))
                     {
                         Logger.Debug("Ignoring change to {0}", path);
                         return true;
@@ -492,7 +492,7 @@ namespace Emby.Server.Core.IO
 
         private void CreateRefresher(string path)
         {
-            var parentPath = Path.GetDirectoryName(path);
+            var parentPath = _fileSystem.GetDirectoryName(path);
 
             lock (_activeRefreshers)
             {
@@ -500,7 +500,7 @@ namespace Emby.Server.Core.IO
                 foreach (var refresher in refreshers)
                 {
                     // Path is already being refreshed
-                    if (string.Equals(path, refresher.Path, StringComparison.Ordinal))
+                    if (_fileSystem.AreEqual(path, refresher.Path))
                     {
                         refresher.RestartTimer();
                         return;
@@ -521,7 +521,7 @@ namespace Emby.Server.Core.IO
                     }
 
                     // They are siblings. Rebase the refresher to the parent folder.
-                    if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
+                    if (string.Equals(parentPath, _fileSystem.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
                     {
                         refresher.ResetPath(parentPath, path);
                         return;

+ 4 - 15
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -268,24 +268,14 @@ namespace Emby.Server.Implementations.Channels
                 return;
             }
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
             _jsonSerializer.SerializeToFile(mediaSources, path);
         }
 
         public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
         {
-            IEnumerable<ChannelMediaInfo> results = new List<ChannelMediaInfo>();
-            var video = item as Video;
-            if (video != null)
-            {
-                results = video.ChannelMediaSources;
-            }
-            var audio = item as Audio;
-            if (audio != null)
-            {
-                results = audio.ChannelMediaSources ?? GetSavedMediaSources(audio);
-            }
+            IEnumerable<ChannelMediaInfo> results = GetSavedMediaSources(item);
 
             return SortMediaInfoResults(results)
                 .Select(i => GetMediaSource(item, i))
@@ -1115,7 +1105,7 @@ namespace Emby.Server.Implementations.Channels
         {
             try
             {
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
                 _jsonSerializer.SerializeToFile(result, path);
             }
@@ -1378,7 +1368,6 @@ namespace Emby.Server.Implementations.Channels
             if (channelVideoItem != null)
             {
                 channelVideoItem.ExtraType = info.ExtraType;
-                channelVideoItem.ChannelMediaSources = info.MediaSources;
 
                 var mediaSource = info.MediaSources.FirstOrDefault();
                 item.Path = mediaSource == null ? null : mediaSource.Path;
@@ -1427,7 +1416,7 @@ namespace Emby.Server.Implementations.Channels
             if (!_refreshedItems.ContainsKey(program.Id))
             {
                 _refreshedItems.TryAdd(program.Id, true);
-                _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
+                _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low);
             }
 
         }

+ 3 - 3
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Collections
                 }
                 else
                 {
-                    _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem));
+                    _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High);
                 }
 
                 EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs
@@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Collections
 
                 await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
-                _providerManager.QueueRefresh(collection.Id, refreshOptions);
+                _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High);
 
                 if (fireEvent)
                 {
@@ -244,7 +244,7 @@ namespace Emby.Server.Implementations.Collections
             collection.UpdateRatingToContent();
 
             await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
-            _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem));
+            _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High);
 
             EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs
             {

+ 1 - 111
Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs

@@ -71,10 +71,9 @@ namespace Emby.Server.Implementations.Data
                 double newPercentCommplete = 45 + .55 * p;
                 progress.Report(newPercentCommplete);
             });
-            await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false);
-            progress.Report(100);
 
             await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false);
+            progress.Report(100);
         }
 
         private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress)
@@ -115,115 +114,6 @@ namespace Emby.Server.Implementations.Data
             progress.Report(100);
         }
 
-        private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress<double> progress)
-        {
-            var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery
-            {
-                LocationTypes = new[] { LocationType.FileSystem },
-                //Limit = limit,
-
-                // These have their own cleanup routines
-                ExcludeItemTypes = new[]
-                {
-                    typeof(Person).Name,
-                    typeof(Genre).Name,
-                    typeof(MusicGenre).Name,
-                    typeof(GameGenre).Name,
-                    typeof(Studio).Name,
-                    typeof(Year).Name,
-                    typeof(Channel).Name,
-                    typeof(AggregateFolder).Name,
-                    typeof(CollectionFolder).Name
-                }
-            });
-
-            var numComplete = 0;
-            var numItems = result.Count;
-
-            var allLibraryPaths = _libraryManager
-                .GetVirtualFolders()
-                .SelectMany(i => i.Locations)
-                .ToList();
-
-            foreach (var item in result)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                var path = item.Item2;
-
-                try
-                {
-                    var isPathInLibrary = false;
-
-                    if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) || 
-                        allLibraryPaths.Contains(path, StringComparer.Ordinal) || 
-                        path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
-                    {
-                        isPathInLibrary = true;
-
-                        if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
-                        {
-                            continue;
-                        }
-                    }
-
-                    var libraryItem = _libraryManager.GetItemById(item.Item1);
-
-                    if (libraryItem == null)
-                    {
-                        continue;
-                    }
-
-                    if (libraryItem.IsTopParent)
-                    {
-                        continue;
-                    }
-
-                    var hasDualAccess = libraryItem as IHasDualAccess;
-                    if (hasDualAccess != null && hasDualAccess.IsAccessedByName)
-                    {
-                        continue;
-                    }
-
-                    var libraryItemPath = libraryItem.Path;
-                    if (!string.Equals(libraryItemPath, path, StringComparison.OrdinalIgnoreCase))
-                    {
-                        _logger.Error("CleanDeletedItems aborting delete for item {0}-{1} because paths don't match. {2}---{3}", libraryItem.Id, libraryItem.Name, libraryItem.Path ?? string.Empty, path ?? string.Empty);
-                        continue;
-                    }
-
-                    if (Folder.IsPathOffline(path, allLibraryPaths))
-                    {
-                        continue;
-                    }
-
-                    if (isPathInLibrary)
-                    {
-                        _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
-                    }
-                    else
-                    {
-                        _logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
-                    }
-
-                    await libraryItem.OnFileDeleted().ConfigureAwait(false);
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error in CleanDeletedItems. File {0}", ex, path);
-                }
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= numItems;
-                progress.Report(percent * 100);
-            }
-        }
-
         /// <summary>
         /// Creates the triggers that define when the task will run
         /// </summary>

+ 32 - 93
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -201,7 +201,6 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "TypedBaseItems", "SortName", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames);
 
-                    AddColumn(db, "TypedBaseItems", "OfficialRatingDescription", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "HomePageUrl", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "VoteCount", "INT", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "DisplayMediaType", "Text", existingColumnNames);
@@ -209,7 +208,6 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames);
 
                     AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames);
-                    AddColumn(db, "TypedBaseItems", "LocationType", "Text", existingColumnNames);
 
                     AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "IsLive", "BIT", existingColumnNames);
@@ -240,7 +238,6 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "TypedBaseItems", "SourceType", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "CriticRating", "Float", existingColumnNames);
-                    AddColumn(db, "TypedBaseItems", "CriticRatingSummary", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "InheritedTags", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "CleanName", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames);
@@ -255,7 +252,6 @@ namespace Emby.Server.Implementations.Data
                     AddColumn(db, "TypedBaseItems", "SeasonName", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames);
-                    AddColumn(db, "TypedBaseItems", "SeriesSortName", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames);
                     AddColumn(db, "TypedBaseItems", "Keywords", "Text", existingColumnNames);
@@ -429,7 +425,6 @@ namespace Emby.Server.Implementations.Data
             "ParentIndexNumber",
             "ProductionYear",
             "OfficialRating",
-            "OfficialRatingDescription",
             "HomePageUrl",
             "DisplayMediaType",
             "ForcedSortName",
@@ -454,13 +449,11 @@ namespace Emby.Server.Implementations.Data
             "DateLastMediaAdded",
             "Album",
             "CriticRating",
-            "CriticRatingSummary",
             "IsVirtualItem",
             "SeriesName",
             "SeasonName",
             "SeasonId",
             "SeriesId",
-            "SeriesSortName",
             "PresentationUniqueKey",
             "InheritedParentalRatingValue",
             "InheritedTags",
@@ -552,14 +545,12 @@ namespace Emby.Server.Implementations.Data
                 "InheritedParentalRatingValue",
                 "SortName",
                 "RunTimeTicks",
-                "OfficialRatingDescription",
                 "HomePageUrl",
                 "VoteCount",
                 "DisplayMediaType",
                 "DateCreated",
                 "DateModified",
                 "ForcedSortName",
-                "LocationType",
                 "PreferredMetadataLanguage",
                 "PreferredMetadataCountryCode",
                 "IsHD",
@@ -579,7 +570,6 @@ namespace Emby.Server.Implementations.Data
                 "SourceType",
                 "TrailerTypes",
                 "CriticRating",
-                "CriticRatingSummary",
                 "InheritedTags",
                 "CleanName",
                 "PresentationUniqueKey",
@@ -594,7 +584,6 @@ namespace Emby.Server.Implementations.Data
                 "SeasonName",
                 "SeasonId",
                 "SeriesId",
-                "SeriesSortName",
                 "ExternalSeriesId",
                 "Tagline",
                 "Keywords",
@@ -833,7 +822,6 @@ namespace Emby.Server.Implementations.Data
             saveItemStatement.TryBind("@SortName", item.SortName);
             saveItemStatement.TryBind("@RunTimeTicks", item.RunTimeTicks);
 
-            saveItemStatement.TryBind("@OfficialRatingDescription", item.OfficialRatingDescription);
             saveItemStatement.TryBind("@HomePageUrl", item.HomePageUrl);
             saveItemStatement.TryBind("@VoteCount", item.VoteCount);
             saveItemStatement.TryBind("@DisplayMediaType", item.DisplayMediaType);
@@ -841,7 +829,6 @@ namespace Emby.Server.Implementations.Data
             saveItemStatement.TryBind("@DateModified", item.DateModified);
 
             saveItemStatement.TryBind("@ForcedSortName", item.ForcedSortName);
-            saveItemStatement.TryBind("@LocationType", item.LocationType.ToString());
 
             saveItemStatement.TryBind("@PreferredMetadataLanguage", item.PreferredMetadataLanguage);
             saveItemStatement.TryBind("@PreferredMetadataCountryCode", item.PreferredMetadataCountryCode);
@@ -942,7 +929,6 @@ namespace Emby.Server.Implementations.Data
             }
 
             saveItemStatement.TryBind("@CriticRating", item.CriticRating);
-            saveItemStatement.TryBind("@CriticRatingSummary", item.CriticRatingSummary);
 
             var inheritedTags = item.InheritedTags;
             if (inheritedTags.Count > 0)
@@ -1024,13 +1010,11 @@ namespace Emby.Server.Implementations.Data
             if (hasSeries != null)
             {
                 saveItemStatement.TryBind("@SeriesId", hasSeries.SeriesId);
-                saveItemStatement.TryBind("@SeriesSortName", hasSeries.SeriesSortName);
                 saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
             }
             else
             {
                 saveItemStatement.TryBindNull("@SeriesId");
-                saveItemStatement.TryBindNull("@SeriesSortName");
                 saveItemStatement.TryBindNull("@SeriesPresentationUniqueKey");
             }
 
@@ -1290,22 +1274,10 @@ namespace Emby.Server.Implementations.Data
                 {
                     return false;
                 }
-                if (type == typeof(Year))
-                {
-                    return false;
-                }
-                if (type == typeof(Book))
-                {
-                    return false;
-                }
                 if (type == typeof(Person))
                 {
                     return false;
                 }
-                if (type == typeof(RecordingGroup))
-                {
-                    return false;
-                }
                 if (type == typeof(Channel))
                 {
                     return false;
@@ -1339,31 +1311,42 @@ namespace Emby.Server.Implementations.Data
                     return false;
                 }
             }
-            if (_config.Configuration.SkipDeserializationForPrograms)
+
+            if (type == typeof(Year))
+            {
+                return false;
+            }
+            if (type == typeof(Book))
             {
-                if (type == typeof(LiveTvProgram))
-                {
-                    return false;
-                }
+                return false;
+            }
+            if (type == typeof(RecordingGroup))
+            {
+                return false;
             }
+            if (type == typeof(LiveTvProgram))
+            {
+                return false;
+            }
+            if (type == typeof(LiveTvAudioRecording))
+            {
+                return false;
+            }
+            if (type == typeof(AudioPodcast))
+            {
+                return false;
+            }
+            if (type == typeof(AudioBook))
+            {
+                return false;
+            }
+
             if (_config.Configuration.SkipDeserializationForAudio)
             {
                 if (type == typeof(Audio))
                 {
                     return false;
                 }
-                if (type == typeof(LiveTvAudioRecording))
-                {
-                    return false;
-                }
-                if (type == typeof(AudioPodcast))
-                {
-                    return false;
-                }
-                if (type == typeof(AudioBook))
-                {
-                    return false;
-                }
                 if (type == typeof(MusicAlbum))
                 {
                     return false;
@@ -1609,15 +1592,6 @@ namespace Emby.Server.Implementations.Data
             }
             index++;
 
-            if (query.HasField(ItemFields.OfficialRatingDescription))
-            {
-                if (!reader.IsDBNull(index))
-                {
-                    item.OfficialRatingDescription = reader.GetString(index);
-                }
-                index++;
-            }
-
             if (query.HasField(ItemFields.HomePageUrl))
             {
                 if (!reader.IsDBNull(index))
@@ -1803,15 +1777,6 @@ namespace Emby.Server.Implementations.Data
             }
             index++;
 
-            if (query.HasField(ItemFields.CriticRatingSummary))
-            {
-                if (!reader.IsDBNull(index))
-                {
-                    item.CriticRatingSummary = reader.GetString(index);
-                }
-                index++;
-            }
-
             if (!reader.IsDBNull(index))
             {
                 item.IsVirtualItem = reader.GetBoolean(index);
@@ -1856,15 +1821,6 @@ namespace Emby.Server.Implementations.Data
             }
             index++;
 
-            if (hasSeries != null)
-            {
-                if (!reader.IsDBNull(index))
-                {
-                    hasSeries.SeriesSortName = reader.GetString(index);
-                }
-            }
-            index++;
-
             if (!reader.IsDBNull(index))
             {
                 item.PresentationUniqueKey = reader.GetString(index);
@@ -2893,6 +2849,10 @@ namespace Emby.Server.Implementations.Data
             {
                 return new Tuple<string, bool>("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false);
             }
+            if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase))
+            {
+                return new Tuple<string, bool>("(Select SortName from TypedBaseItems where B.Guid=A.SeriesId)", false);
+            }
 
             return new Tuple<string, bool>(name, false);
         }
@@ -4100,27 +4060,6 @@ namespace Emby.Server.Implementations.Data
                 whereClauses.Add("ProductionYear in (" + val + ")");
             }
 
-            if (query.LocationTypes.Length == 1)
-            {
-                if (query.LocationTypes[0] == LocationType.Virtual && _config.Configuration.SchemaVersion >= 90)
-                {
-                    query.IsVirtualItem = true;
-                }
-                else
-                {
-                    whereClauses.Add("LocationType=@LocationType");
-                    if (statement != null)
-                    {
-                        statement.TryBind("@LocationType", query.LocationTypes[0].ToString());
-                    }
-                }
-            }
-            else if (query.LocationTypes.Length > 1)
-            {
-                var val = string.Join(",", query.LocationTypes.Select(i => "'" + i + "'").ToArray());
-
-                whereClauses.Add("LocationType in (" + val + ")");
-            }
             if (query.IsVirtualItem.HasValue)
             {
                 whereClauses.Add("IsVirtualItem=@IsVirtualItem");

+ 1 - 6
Emby.Server.Implementations/Dto/DtoService.cs

@@ -499,7 +499,7 @@ namespace Emby.Server.Implementations.Dto
 
             if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo))
             {
-                var userCanSync = user != null && user.Policy.EnableSync;
+                var userCanSync = user != null && user.Policy.EnableContentDownloading;
                 if (userCanSync && _syncManager.SupportsSync(item))
                 {
                     dto.SupportsSync = true;
@@ -967,11 +967,6 @@ namespace Emby.Server.Implementations.Dto
 
             dto.CriticRating = item.CriticRating;
 
-            if (fields.Contains(ItemFields.CriticRatingSummary))
-            {
-                dto.CriticRatingSummary = item.CriticRatingSummary;
-            }
-
             var hasTrailers = item as IHasTrailers;
             if (hasTrailers != null)
             {

+ 6 - 6
Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -352,7 +352,7 @@ namespace Emby.Server.Implementations.FileOrganization
                         _libraryMonitor.ReportFileSystemChangeBeginning(path);
 
                         var renameRelatedFiles = !hasRenamedFiles &&
-                            string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase);
+                            string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase);
 
                         if (renameRelatedFiles)
                         {
@@ -432,7 +432,7 @@ namespace Emby.Server.Implementations.FileOrganization
 
             // Now find other files
             var originalFilenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
-            var directory = Path.GetDirectoryName(path);
+            var directory = _fileSystem.GetDirectoryName(path);
 
             if (!string.IsNullOrWhiteSpace(originalFilenameWithoutExtension) && !string.IsNullOrWhiteSpace(directory))
             {
@@ -445,7 +445,7 @@ namespace Emby.Server.Implementations.FileOrganization
 
                 foreach (var file in files)
                 {
-                    directory = Path.GetDirectoryName(file);
+                    directory = _fileSystem.GetDirectoryName(file);
                     var filename = Path.GetFileName(file);
 
                     filename = filename.Replace(originalFilenameWithoutExtension, targetFilenameWithoutExtension,
@@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.FileOrganization
                 return new List<string>();
             }
 
-            var episodePaths = series.GetRecursiveChildren()
+            var episodePaths = series.GetRecursiveChildren(i => i is Episode)
                 .OfType<Episode>()
                 .Where(i =>
                 {
@@ -499,7 +499,7 @@ namespace Emby.Server.Implementations.FileOrganization
                 .Select(i => i.Path)
                 .ToList();
 
-            var folder = Path.GetDirectoryName(targetPath);
+            var folder = _fileSystem.GetDirectoryName(targetPath);
             var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath);
 
             try
@@ -529,7 +529,7 @@ namespace Emby.Server.Implementations.FileOrganization
 
             _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(result.TargetPath));
 
             var targetAlreadyExists = _fileSystem.FileExists(result.TargetPath);
 

+ 7 - 3
Emby.Server.Implementations/HttpServer/FileWriter.cs

@@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.HttpServer
             Headers["Content-Type"] = contentType;
 
             TotalContentLength = fileSystem.GetFileInfo(path).Length;
+            Headers["Accept-Ranges"] = "bytes";
 
             if (string.IsNullOrWhiteSpace(rangeHeader))
             {
@@ -66,7 +67,6 @@ namespace Emby.Server.Implementations.HttpServer
             }
             else
             {
-                Headers["Accept-Ranges"] = "bytes";
                 StatusCode = HttpStatusCode.PartialContent;
                 SetRangeValues();
             }
@@ -96,8 +96,12 @@ namespace Emby.Server.Implementations.HttpServer
             RangeLength = 1 + RangeEnd - RangeStart;
 
             // Content-Length is the length of what we're serving, not the original content
-            Headers["Content-Length"] = RangeLength.ToString(UsCulture);
-            Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+            var lengthString = RangeLength.ToString(UsCulture);
+            Headers["Content-Length"] = lengthString;
+            var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+            Headers["Content-Range"] = rangeString;
+
+            Logger.Info("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
         }
 
         /// <summary>

+ 3 - 2
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.HttpServer
         private bool ShouldCompressResponse(IRequest requestContext, string contentType)
         {
             // It will take some work to support compression with byte range requests
-            if (!string.IsNullOrEmpty(requestContext.Headers.Get("Range")))
+            if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range")))
             {
                 return false;
             }
@@ -566,7 +566,7 @@ namespace Emby.Server.Implementations.HttpServer
                     };
                 }
 
-                if (!string.IsNullOrEmpty(rangeHeader))
+                if (!string.IsNullOrWhiteSpace(rangeHeader))
                 {
                     var stream = await factoryFn().ConfigureAwait(false);
 
@@ -621,6 +621,7 @@ namespace Emby.Server.Implementations.HttpServer
                     responseHeaders["Content-Encoding"] = requestedCompressionType;
                 }
 
+                responseHeaders["Vary"] = "Accept-Encoding";
                 responseHeaders["Content-Length"] = content.Length.ToString(UsCulture);
 
                 if (isHeadRequest)

+ 8 - 3
Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -189,10 +189,15 @@ namespace Emby.Server.Implementations.HttpServer
         private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
         {
             var array = new byte[BufferSize];
-            int count;
-            while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
+            int bytesRead;
+            while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
             {
-                var bytesToCopy = Math.Min(count, copyLength);
+                if (bytesRead == 0)
+                {
+                    break;
+                }
+
+                var bytesToCopy = Math.Min(bytesRead, copyLength);
 
                 await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
 

+ 2 - 18
Emby.Server.Implementations/HttpServer/ResponseFilter.cs

@@ -26,8 +26,8 @@ namespace Emby.Server.Implementations.HttpServer
         public void FilterResponse(IRequest req, IResponse res, object dto)
         {
             // Try to prevent compatibility view
-            res.AddHeader("X-UA-Compatible", "IE=Edge");
-            res.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
+            //res.AddHeader("X-UA-Compatible", "IE=Edge");
+            res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
             res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
             res.AddHeader("Access-Control-Allow-Origin", "*");
 
@@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.HttpServer
                 }
             }
 
-            var vary = "Accept-Encoding";
-
             var hasHeaders = dto as IHasHeaders;
             var sharpResponse = res as WebSocketSharpResponse;
 
@@ -86,23 +84,9 @@ namespace Emby.Server.Implementations.HttpServer
                         }
                     }
                 }
-
-                string hasHeadersVary;
-                if (hasHeaders.Headers.TryGetValue("Vary", out hasHeadersVary))
-                {
-                    vary = hasHeadersVary;
-                }
-
-                hasHeaders.Headers["Vary"] = vary;
             }
 
             //res.KeepAlive = false;
-
-            // Per Google PageSpeed
-            // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. 
-            // The correct version of the resource is delivered based on the client request header. 
-            // This is a good choice for applications that are singly homed and depend on public proxies for user locality.                        
-            res.AddHeader("Vary", vary);
         }
 
         /// <summary>

+ 1 - 1
Emby.Server.Implementations/IO/FileRefresher.cs

@@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.IO
             {
                 item = LibraryManager.FindByPath(path, null);
 
-                path = System.IO.Path.GetDirectoryName(path);
+                path = _fileSystem.GetDirectoryName(path);
             }
 
             if (item != null)

+ 2 - 2
Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs

@@ -139,7 +139,7 @@ namespace Emby.Server.Implementations.Images
             CancellationToken cancellationToken)
         {
             var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
-            FileSystem.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension));
+            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension));
             string outputPath = await CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0).ConfigureAwait(false);
 
             if (string.IsNullOrWhiteSpace(outputPath))
@@ -205,7 +205,7 @@ namespace Emby.Server.Implementations.Images
 
         private async Task<string> CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
         {
-            FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
+            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
 
             var options = new ImageCollageOptions
             {

+ 1 - 1
Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs

@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Library
             {
                 if (parent == null)
                 {
-                    var parentFolderName = Path.GetFileName(Path.GetDirectoryName(path));
+                    var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path));
 
                     if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
                     {

+ 35 - 6
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -1962,8 +1962,34 @@ namespace Emby.Server.Implementations.Library
                 return new List<Folder>();
             }
 
-            return GetUserRootFolder().Children
-                .OfType<Folder>()
+            return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>().ToList());
+        }
+
+        public List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren)
+        {
+            while (item != null)
+            {
+                var parent = item.GetParent();
+
+                if (parent == null || parent is AggregateFolder)
+                {
+                    break;
+                }
+
+                item = parent;
+            }
+
+            if (item == null)
+            {
+                return new List<Folder>();
+            }
+
+            return GetCollectionFoldersInternal(item, allUserRootChildren);
+        }
+
+        private List<Folder> GetCollectionFoldersInternal(BaseItem item, List<Folder> allUserRootChildren)
+        {
+            return allUserRootChildren
                 .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase))
                 .ToList();
         }
@@ -2126,7 +2152,8 @@ namespace Emby.Server.Implementations.Library
                     // Not sure why this is necessary but need to figure it out
                     // View images are not getting utilized without this
                     ForceSave = true
-                });
+
+                }, RefreshPriority.Normal);
             }
 
             return item;
@@ -2188,7 +2215,8 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Need to force save to increment DateLastSaved
                     ForceSave = true
-                });
+
+                }, RefreshPriority.Normal);
             }
 
             return item;
@@ -2252,7 +2280,8 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Need to force save to increment DateLastSaved
                     ForceSave = true
-                });
+
+                }, RefreshPriority.Normal);
             }
 
             return item;
@@ -2328,7 +2357,7 @@ namespace Emby.Server.Implementations.Library
                 {
                     // Need to force save to increment DateLastSaved
                     ForceSave = true
-                });
+                }, RefreshPriority.Normal);
             }
 
             return item;

+ 29 - 50
Emby.Server.Implementations/Library/MusicManager.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Playlists;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 namespace Emby.Server.Implementations.Library
 {
@@ -27,35 +28,14 @@ namespace Emby.Server.Implementations.Library
             return list.Concat(GetInstantMixFromGenres(item.Genres, user));
         }
 
-        public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user)
+        public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user)
         {
-            var genres = user.RootFolder
-                .GetRecursiveChildren(user, new InternalItemsQuery(user)
-                {
-                    IncludeItemTypes = new[] { typeof(Audio).Name }
-                })
-                .Cast<Audio>()
-                .Where(i => i.HasAnyArtist(artist.Name))
-                .SelectMany(i => i.Genres)
-                .Concat(artist.Genres)
-                .Distinct(StringComparer.OrdinalIgnoreCase);
-
-            return GetInstantMixFromGenres(genres, user);
+            return GetInstantMixFromGenres(item.Genres, user);
         }
 
         public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
         {
-            var genres = item
-                .GetRecursiveChildren(user, new InternalItemsQuery(user)
-                {
-                    IncludeItemTypes = new[] { typeof(Audio).Name }
-                })
-               .Cast<Audio>()
-               .SelectMany(i => i.Genres)
-               .Concat(item.Genres)
-               .DistinctNames();
-
-            return GetInstantMixFromGenres(genres, user);
+            return GetInstantMixFromGenres(item.Genres, user);
         }
 
         public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
@@ -63,7 +43,7 @@ namespace Emby.Server.Implementations.Library
             var genres = item
                .GetRecursiveChildren(user, new InternalItemsQuery(user)
                {
-                   IncludeItemTypes = new[] {typeof(Audio).Name}
+                   IncludeItemTypes = new[] { typeof(Audio).Name }
                })
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
@@ -75,41 +55,40 @@ namespace Emby.Server.Implementations.Library
 
         public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
         {
-            var genres = item
-               .GetRecursiveChildren(user, new InternalItemsQuery(user)
-               {
-                   IncludeItemTypes = new[] { typeof(Audio).Name }
-               })
-               .Cast<Audio>()
-               .SelectMany(i => i.Genres)
-               .Concat(item.Genres)
-               .DistinctNames();
-
-            return GetInstantMixFromGenres(genres, user);
+            return GetInstantMixFromGenres(item.Genres, user);
         }
 
         public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user)
         {
-            var genreList = genres.ToList();
+            var genreIds = genres.DistinctNames().Select(i =>
+            {
+                try
+                {
+                    return _libraryManager.GetMusicGenre(i).Id.ToString("N");
+                }
+                catch
+                {
+                    return null;
+                }
+
+            }).Where(i => i != null);
 
-            var inputItems = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            return GetInstantMixFromGenreIds(genreIds, user);
+        }
+
+        public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user)
+        {
+            return _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
                 IncludeItemTypes = new[] { typeof(Audio).Name },
 
-                Genres = genreList.ToArray()
+                GenreIds = genreIds.ToArray(),
 
-            });
+                Limit = 200,
 
-            var genresDictionary = genreList.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+                SortBy = new[] { ItemSortBy.Random }
 
-            return inputItems
-                .Cast<Audio>()
-                .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
-                .OrderByDescending(i => i.Item2)
-                .ThenBy(i => Guid.NewGuid())
-                .Select(i => i.Item1)
-                .Take(200)
-                .OrderBy(i => Guid.NewGuid());
+            }).Cast<Audio>();
         }
 
         public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user)
@@ -117,7 +96,7 @@ namespace Emby.Server.Implementations.Library
             var genre = item as MusicGenre;
             if (genre != null)
             {
-                return GetInstantMixFromGenres(new[] { item.Name }, user);
+                return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user);
             }
 
             var playlist = item as Playlist;

+ 5 - 2
Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs

@@ -6,6 +6,7 @@ using System;
 using System.IO;
 using System.Linq;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.IO;
 
 namespace Emby.Server.Implementations.Library.Resolvers
 {
@@ -13,11 +14,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
     {
         private readonly IImageProcessor _imageProcessor;
         private readonly ILibraryManager _libraryManager;
+        private readonly IFileSystem _fileSystem;
 
-        public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
+        public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem)
         {
             _imageProcessor = imageProcessor;
             _libraryManager = libraryManager;
+            _fileSystem = fileSystem;
         }
 
         /// <summary>
@@ -41,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
                         var filename = Path.GetFileNameWithoutExtension(args.Path);
 
                         // Make sure the image doesn't belong to a video file
-                        if (args.DirectoryService.GetFilePaths(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename)))
+                        if (args.DirectoryService.GetFilePaths(_fileSystem.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename)))
                         {
                             return null;
                         }

+ 0 - 1
Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs

@@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                     {
                         episode.SeriesId = series.Id;
                         episode.SeriesName = series.Name;
-                        episode.SeriesSortName = series.SortName;
                     }
                     if (season != null)
                     {

+ 0 - 1
Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -44,7 +44,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                 {
                     IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
                     SeriesId = series.Id,
-                    SeriesSortName = series.SortName,
                     SeriesName = series.Name
                 };
 

+ 10 - 1
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -165,7 +165,16 @@ namespace Emby.Server.Implementations.Library
                 ExcludeItemTypes = excludeItemTypes.ToArray(),
                 IncludeItemTypes = includeItemTypes.ToArray(),
                 Limit = query.Limit,
-                IncludeItemsByName = true
+                IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId),
+                ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId),
+                SortBy = new[] { ItemSortBy.SortName },
+                Recursive = true,
+
+                IsKids = query.IsKids,
+                IsMovie = query.IsMovie,
+                IsNews = query.IsNews,
+                IsSeries = query.IsSeries,
+                IsSports = query.IsSports
             });
 
             // Add search hints based on item name

+ 4 - 3
Emby.Server.Implementations/Library/UserManager.cs

@@ -942,7 +942,8 @@ namespace Emby.Server.Implementations.Library
         {
             return new UserPolicy
             {
-                EnableSync = true
+                EnableContentDownloading = true,
+                EnableSyncTranscoding = true
             };
         }
 
@@ -964,7 +965,7 @@ namespace Emby.Server.Implementations.Library
 
             var path = GetPolifyFilePath(user);
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
             lock (_policySyncLock)
             {
@@ -1051,7 +1052,7 @@ namespace Emby.Server.Implementations.Library
                 config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
             }
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
             lock (_configSyncLock)
             {

+ 9 - 19
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 {
                     if (!string.IsNullOrWhiteSpace(epgChannel.Name))
                     {
-                        tunerChannel.Name = epgChannel.Name;
+                        //tunerChannel.Name = epgChannel.Name;
                     }
                     if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
                     {
@@ -1231,7 +1231,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     RequiresOpening = false,
                     RequiresClosing = false,
                     Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
-                    BufferMs = 0
+                    BufferMs = 0,
+                    IgnoreDts = true
                 };
 
                 var isAudio = false;
@@ -1496,7 +1497,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
                 _libraryManager.RegisterIgnoredPath(recordPath);
                 _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath));
                 activeRecordingInfo.Path = recordPath;
 
                 var duration = recordingEndDate - DateTime.UtcNow;
@@ -1516,8 +1517,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     EnforceKeepUpTo(timer, seriesPath);
                 };
 
-                await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken)
-                        .ConfigureAwait(false);
+                await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
 
                 recordingStatus = RecordingStatus.Completed;
                 _logger.Info("Recording completed: {0}", recordPath);
@@ -1725,7 +1725,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             while (FileExists(path, timerId))
             {
-                var parent = Path.GetDirectoryName(originalPath);
+                var parent = _fileSystem.GetDirectoryName(originalPath);
                 var name = Path.GetFileNameWithoutExtension(originalPath);
                 name += "-" + index.ToString(CultureInfo.InvariantCulture);
 
@@ -1765,7 +1765,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
                 if (regInfo.IsValid)
                 {
-                    return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory);
+                    return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory, _config);
                 }
             }
 
@@ -1892,7 +1892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 return;
             }
 
-            var imageSavePath = Path.Combine(Path.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
+            var imageSavePath = Path.Combine(_fileSystem.GetDirectoryName(recordingPath), imageSaveFilenameWithoutExtension);
 
             // preserve original image extension
             imageSavePath = Path.ChangeExtension(imageSavePath, Path.GetExtension(image.Path));
@@ -2155,11 +2155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                         writer.WriteElementString("mpaa", item.OfficialRating);
                     }
 
-                    if (!string.IsNullOrEmpty(item.OfficialRatingDescription))
-                    {
-                        writer.WriteElementString("mpaadescription", item.OfficialRatingDescription);
-                    }
-
                     var overview = (item.Overview ?? string.Empty)
                         .StripHtml()
                         .Replace("&quot;", "'");
@@ -2251,11 +2246,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                         writer.WriteElementString("criticrating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture));
                     }
 
-                    if (!string.IsNullOrEmpty(item.CriticRatingSummary))
-                    {
-                        writer.WriteElementString("criticratingsummary", item.CriticRatingSummary);
-                    }
-
                     if (!string.IsNullOrWhiteSpace(item.Tagline))
                     {
                         writer.WriteElementString("tagline", item.Tagline);
@@ -2550,7 +2540,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData)
         {
             var path = GetChannelEpgCachePath(channelId);
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
             lock (_epgLock)
             {
                 _jsonSerializer.SerializeToFile(epgData, path);

+ 57 - 28
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -11,14 +11,16 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
+using MediaBrowser.Common.Configuration;
 
 namespace Emby.Server.Implementations.LiveTv.EmbyTV
 {
@@ -37,8 +39,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private readonly IProcessFactory _processFactory;
         private readonly IJsonSerializer _json;
         private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
+        private readonly IServerConfigurationManager _config;
 
-        public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient, IProcessFactory processFactory)
+        public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config)
         {
             _logger = logger;
             _fileSystem = fileSystem;
@@ -48,6 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _liveTvOptions = liveTvOptions;
             _httpClient = httpClient;
             _processFactory = processFactory;
+            _config = config;
         }
 
         private string OutputFormat
@@ -76,23 +80,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
         {
-            return Path.ChangeExtension(targetFile, "." + OutputFormat);
+            var extension = OutputFormat;
+
+            if (string.Equals(extension, "mpegts", StringComparison.OrdinalIgnoreCase))
+            {
+                extension = "ts";
+            }
+
+            return Path.ChangeExtension(targetFile, "." + extension);
         }
 
         public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
-            var durationToken = new CancellationTokenSource(duration);
-            cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+            //var durationToken = new CancellationTokenSource(duration);
+            //cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
 
             await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
 
             _logger.Info("Recording completed to file {0}", targetFile);
         }
 
+        private EncodingOptions GetEncodingOptions()
+        {
+            return _config.GetConfiguration<EncodingOptions>("encoding");
+        }
+
         private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
             _targetPath = targetFile;
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile));
 
             var process = _processFactory.Create(new ProcessOptions
             {
@@ -118,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _logger.Info(commandLineLogMessage);
 
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(logFilePath));
 
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
             _logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
@@ -162,28 +178,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
 
             var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
-            var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
-            var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"";
-
-            long startTimeTicks = 0;
-            //if (mediaSource.DateLiveStreamOpened.HasValue)
-            //{
-            //    var elapsed = DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value;
-            //    elapsed -= TimeSpan.FromSeconds(10);
-            //    if (elapsed.TotalSeconds >= 0)
-            //    {
-            //        startTimeTicks = elapsed.Ticks + startTimeTicks;
-            //    }
-            //}
 
-            if (mediaSource.ReadAtNativeFramerate)
+            var flags = new List<string>();
+            if (mediaSource.IgnoreDts)
             {
-                inputModifiers += " -re";
+                flags.Add("+igndts");
+            }
+            if (mediaSource.IgnoreIndex)
+            {
+                flags.Add("+ignidx");
             }
 
-            if (startTimeTicks > 0)
+            var inputModifiers = "-async 1 -vsync -1";
+
+            if (flags.Count > 0)
+            {
+                inputModifiers += " -fflags " + string.Join("", flags.ToArray());
+            }
+
+            if (!string.IsNullOrWhiteSpace(GetEncodingOptions().HardwareAccelerationType))
+            {
+                inputModifiers += " -hwaccel auto";
+            }
+
+            if (mediaSource.ReadAtNativeFramerate)
             {
-                inputModifiers = "-ss " + _mediaEncoder.GetTimeParameter(startTimeTicks) + " " + inputModifiers;
+                inputModifiers += " -re";
             }
 
             var analyzeDurationSeconds = 5;
@@ -193,11 +213,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
 
-            var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
-                " -f mp4 -movflags frag_keyframe+empty_moov" :
-                string.Empty;
+            //var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
+            //    " -f mp4 -movflags frag_keyframe+empty_moov" :
+            //    string.Empty;
+
+            var outputParam = string.Empty;
 
-            commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam);
+            var commandLineArgs = string.Format("-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"", 
+                inputTempFile, 
+                targetFile, 
+                videoArgs, 
+                GetAudioArgs(mediaSource), 
+                subtitleArgs, 
+                durationParam, 
+                outputParam);
 
             return inputModifiers + " " + commandLineArgs;
         }

+ 1 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs

@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             }
 
             var file = _dataPath + ".json";
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(file));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(file));
 
             lock (_fileDataLock)
             {

+ 1 - 1
Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs

@@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
             }).ConfigureAwait(false);
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFile));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFile));
 
             using (var stream = _fileSystem.OpenRead(tempFile))
             {

+ 4 - 3
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -857,7 +857,8 @@ namespace Emby.Server.Implementations.LiveTv
                 _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
                 {
                     MetadataRefreshMode = metadataRefreshMode
-                });
+
+                }, RefreshPriority.Normal);
             }
 
             return item.Id;
@@ -1395,11 +1396,11 @@ namespace Emby.Server.Implementations.LiveTv
 
                     foreach (var program in newPrograms)
                     {
-                        _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
+                        _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low);
                     }
                     foreach (var program in updatedPrograms)
                     {
-                        _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem));
+                        _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low);
                     }
 
                     currentChannel.IsMovie = isMovie;

+ 2 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -420,7 +420,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 SupportsDirectPlay = false,
                 SupportsDirectStream = true,
                 SupportsTranscoding = true,
-                IsInfiniteStream = true
+                IsInfiniteStream = true,
+                IgnoreDts = true
             };
 
             mediaSource.InferTotalBitrate();

+ 5 - 0
Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs

@@ -29,6 +29,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
             byte[] buffer = new byte[BufferSize];
 
+            if (source == null)
+            {
+                throw new ArgumentNullException("source");
+            }
+
             while (!cancellationToken.IsCancellationRequested)
             {
                 var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);

+ 5 - 3
Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs

@@ -29,15 +29,17 @@ namespace Emby.Server.Implementations.Logging
             _logManager.Flush();
 
             var path = Path.Combine(_appPaths.LogDirectoryPath, "unhandled_" + Guid.NewGuid() + ".txt");
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
             var builder = LogHelper.GetLogMessage(ex);
 
             // Write to console just in case file logging fails
             _console.WriteLine("UnhandledException");
-            _console.WriteLine(builder.ToString());
 
-            _fileSystem.WriteAllText(path, builder.ToString());
+            var logMessage = builder.ToString();
+            _console.WriteLine(logMessage);
+
+            _fileSystem.WriteAllText(path, logMessage);
         }
     }
 }

+ 1 - 1
Emby.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.MediaEncoder
 
                             var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames);
 
-                            _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+                            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
                             var container = video.Container;
 

+ 4 - 2
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -201,7 +201,8 @@ namespace Emby.Server.Implementations.Playlists
             _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem)
             {
                 ForceSave = true
-            });
+
+            }, RefreshPriority.High);
         }
 
         public async Task RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
@@ -228,7 +229,8 @@ namespace Emby.Server.Implementations.Playlists
             _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem)
             {
                 ForceSave = true
-            });
+
+            }, RefreshPriority.High);
         }
 
         public async Task MoveItem(string playlistId, string entryId, int newIndex)

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                     {
                         previouslyFailedImages.Add(key);
 
-                        var parentPath = Path.GetDirectoryName(failHistoryPath);
+                        var parentPath = _fileSystem.GetDirectoryName(failHistoryPath);
 
                         _fileSystem.CreateDirectory(parentPath);
 

+ 1 - 1
Emby.Server.Implementations/Security/MBLicenseFile.cs

@@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.Security
             }
 
             var licenseFile = Filename;
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(licenseFile));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(licenseFile));
             lock (_fileLock)
             {
                 _fileSystem.WriteAllLines(licenseFile, lines);

+ 1 - 1
Emby.Server.Implementations/Session/HttpSessionController.cs

@@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Session
 
         public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
         {
-            return Task.FromResult(true);
+            return SendMessage("LibraryChanged", info, cancellationToken);
         }
 
         public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken)

+ 2 - 1
Emby.Server.Implementations/Session/SessionManager.cs

@@ -715,7 +715,8 @@ namespace Emby.Server.Implementations.Session
                 ClientName = session.Client,
                 DeviceId = session.DeviceId,
                 IsPaused = info.IsPaused,
-                PlaySessionId = info.PlaySessionId
+                PlaySessionId = info.PlaySessionId,
+                IsAutomated = isAutomated
 
             }, _logger);
 

+ 1 - 1
Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs

@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Sorting
         {
             var hasSeries = item as IHasSeries;
 
-            return hasSeries != null ? hasSeries.SeriesSortName : null;
+            return hasSeries != null ? hasSeries.FindSeriesSortName() : null;
         }
 
         /// <summary>

+ 2 - 2
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -250,7 +250,7 @@ namespace Emby.Server.Implementations.Updates
 
                 }).ConfigureAwait(false);
 
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(PackageCachePath));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(PackageCachePath));
 
                 _fileSystem.CopyFile(tempFile, PackageCachePath, true);
                 _lastPackageUpdateTime = DateTime.UtcNow;
@@ -627,7 +627,7 @@ namespace Emby.Server.Implementations.Updates
             // Success - move it to the real target 
             try
             {
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(target));
+                _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(target));
                 _fileSystem.CopyFile(tempFile, target, true);
                 //If it is an archive - write out a version file so we know what it is
                 if (isArchive)

+ 1 - 1
MediaBrowser.Api/ApiEntryPoint.cs

@@ -633,7 +633,7 @@ namespace MediaBrowser.Api
         /// <param name="outputFilePath">The output file path.</param>
         private void DeleteHlsPartialStreamFiles(string outputFilePath)
         {
-            var directory = Path.GetDirectoryName(outputFilePath);
+            var directory = _fileSystem.GetDirectoryName(outputFilePath);
             var name = Path.GetFileNameWithoutExtension(outputFilePath);
 
             var filesToDelete = _fileSystem.GetFilePaths(directory)

+ 2 - 2
MediaBrowser.Api/BaseApiService.cs

@@ -66,7 +66,7 @@ namespace MediaBrowser.Api
             return ResultFactory.GetOptimizedResult(Request, result);
         }
 
-        protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, string userId)
+        protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, string userId, bool restrictUserPreferences)
         {
             var auth = authContext.GetAuthorizationInfo(Request);
 
@@ -80,7 +80,7 @@ namespace MediaBrowser.Api
                     throw new SecurityException("Unauthorized access.");
                 }
             }
-            else
+            else if (restrictUserPreferences)
             {
                 if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
                 {

+ 2 - 2
MediaBrowser.Api/ChannelService.cs

@@ -98,7 +98,7 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string SortBy { get; set; }
 
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
         
         /// <summary>
@@ -145,7 +145,7 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Filters { get; set; }
 
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "ChannelIds", Description = "Optional. Specify one or more channel id's, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]

+ 1 - 1
MediaBrowser.Api/EnvironmentService.cs

@@ -278,7 +278,7 @@ namespace MediaBrowser.Api
 
         public object Get(GetParentPath request)
         {
-            var parent = Path.GetDirectoryName(request.Path);
+            var parent = _fileSystem.GetDirectoryName(request.Path);
 
             if (string.IsNullOrEmpty(parent))
             {

+ 1 - 1
MediaBrowser.Api/Images/ImageByNameService.cs

@@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Images
 
         private string GetThemeName(string path, string rootImagePath)
         {
-            var parentName = Path.GetDirectoryName(path);
+            var parentName = _fileSystem.GetDirectoryName(path);
 
             if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase))
             {

+ 2 - 2
MediaBrowser.Api/Images/ImageService.cs

@@ -427,7 +427,7 @@ namespace MediaBrowser.Api.Images
         public void Post(PostUserImage request)
         {
             var userId = GetPathValue(1);
-            AssertCanUpdateUser(_authContext, _userManager, userId);
+            AssertCanUpdateUser(_authContext, _userManager, userId, true);
 
             request.Type = (ImageType)Enum.Parse(typeof(ImageType), GetPathValue(3), true);
 
@@ -462,7 +462,7 @@ namespace MediaBrowser.Api.Images
         public void Delete(DeleteUserImage request)
         {
             var userId = request.Id;
-            AssertCanUpdateUser(_authContext, _userManager, userId);
+            AssertCanUpdateUser(_authContext, _userManager, userId, true);
 
             var item = _userManager.GetUserById(userId);
 

+ 2 - 2
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -278,7 +278,7 @@ namespace MediaBrowser.Api.Images
 
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+			_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fullCachePath));
             using (var stream = result.Content)
             {
                 using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
@@ -287,7 +287,7 @@ namespace MediaBrowser.Api.Images
                 }
             }
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+			_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(pointerCachePath));
             _fileSystem.WriteAllText(pointerCachePath, fullCachePath);
         }
 

+ 2 - 2
MediaBrowser.Api/ItemLookupService.cs

@@ -299,7 +299,7 @@ namespace MediaBrowser.Api
 
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fullCachePath));
             using (var stream = result.Content)
             {
                 using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
@@ -308,7 +308,7 @@ namespace MediaBrowser.Api
                 }
             }
 
-            _fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+            _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(pointerCachePath));
             _fileSystem.WriteAllText(pointerCachePath, fullCachePath);
         }
 

+ 1 - 8
MediaBrowser.Api/ItemRefreshService.cs

@@ -62,14 +62,7 @@ namespace MediaBrowser.Api
 
             var options = GetRefreshOptions(request);
 
-            if (item is Folder)
-            {
-                _providerManager.QueueRefresh(item.Id, options);
-            }
-            else
-            {
-                _providerManager.RefreshFullItem(item, options, CancellationToken.None);
-            }
+            _providerManager.QueueRefresh(item.Id, options, RefreshPriority.High);
         }
 
         private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)

+ 0 - 1
MediaBrowser.Api/ItemUpdateService.cs

@@ -240,7 +240,6 @@ namespace MediaBrowser.Api
             item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle;
 
             item.CriticRating = request.CriticRating;
-            item.CriticRatingSummary = request.CriticRatingSummary;
 
             item.DisplayMediaType = request.DisplayMediaType;
             item.CommunityRating = request.CommunityRating;

+ 5 - 5
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -98,7 +98,7 @@ namespace MediaBrowser.Api.LiveTv
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -189,7 +189,7 @@ namespace MediaBrowser.Api.LiveTv
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         public bool EnableTotalRecordCount { get; set; }
@@ -251,7 +251,7 @@ namespace MediaBrowser.Api.LiveTv
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         public bool EnableTotalRecordCount { get; set; }
@@ -399,7 +399,7 @@ namespace MediaBrowser.Api.LiveTv
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         public GetPrograms()
@@ -459,7 +459,7 @@ namespace MediaBrowser.Api.LiveTv
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]

+ 16 - 122
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -198,7 +198,7 @@ namespace MediaBrowser.Api.Playback
             CancellationTokenSource cancellationTokenSource,
             string workingDirectory = null)
         {
-            FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
+            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
 
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
 
@@ -263,7 +263,7 @@ namespace MediaBrowser.Api.Playback
             }
 
             var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
-            FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
+            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath));
 
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
             state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
@@ -315,8 +315,6 @@ namespace MediaBrowser.Api.Playback
                 StartThrottler(state, transcodingJob);
             }
 
-            ReportUsage(state);
-
             return transcodingJob;
         }
 
@@ -677,7 +675,8 @@ namespace MediaBrowser.Api.Playback
             {
                 Request = request,
                 RequestedUrl = url,
-                UserAgent = Request.UserAgent
+                UserAgent = Request.UserAgent,
+                EnableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params)
             };
 
             var auth = AuthorizationContext.GetAuthorizationInfo(Request);
@@ -720,6 +719,13 @@ namespace MediaBrowser.Api.Playback
 
             state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
 
+            //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
+            //             item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
+            //if (primaryImage != null)
+            //{
+            //    state.AlbumCoverPath = primaryImage.Path;
+            //}
+
             MediaSourceInfo mediaSource = null;
             if (string.IsNullOrWhiteSpace(request.LiveStreamId))
             {
@@ -903,123 +909,6 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        private async void ReportUsage(StreamState state)
-        {
-            try
-            {
-                await ReportUsageInternal(state).ConfigureAwait(false);
-            }
-            catch
-            {
-
-            }
-        }
-
-        private Task ReportUsageInternal(StreamState state)
-        {
-            if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
-            {
-                return Task.FromResult(true);
-            }
-
-            if (!MediaEncoder.IsDefaultEncoderPath)
-            {
-                return Task.FromResult(true);
-            }
-            return Task.FromResult(true);
-
-            //var dict = new Dictionary<string, string>();
-
-            //var outputAudio = GetAudioEncoder(state);
-            //if (!string.IsNullOrWhiteSpace(outputAudio))
-            //{
-            //    dict["outputAudio"] = outputAudio;
-            //}
-
-            //var outputVideo = GetVideoEncoder(state);
-            //if (!string.IsNullOrWhiteSpace(outputVideo))
-            //{
-            //    dict["outputVideo"] = outputVideo;
-            //}
-
-            //if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
-            //    ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-            //{
-            //    return Task.FromResult(true);
-            //}
-
-            //dict["id"] = AppHost.SystemId;
-            //dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
-
-            //var audioStream = state.AudioStream;
-            //if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
-            //{
-            //    dict["inputAudio"] = audioStream.Codec;
-            //}
-
-            //var videoStream = state.VideoStream;
-            //if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
-            //{
-            //    dict["inputVideo"] = videoStream.Codec;
-            //}
-
-            //var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
-            //if (cert != null)
-            //{
-            //    dict["assemblySig"] = cert.GetCertHashString();
-            //    dict["certSubject"] = cert.Subject ?? string.Empty;
-            //    dict["certIssuer"] = cert.Issuer ?? string.Empty;
-            //}
-            //else
-            //{
-            //    return Task.FromResult(true);
-            //}
-
-            //if (state.SupportedAudioCodecs.Count > 0)
-            //{
-            //    dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
-            //}
-
-            //var auth = AuthorizationContext.GetAuthorizationInfo(Request);
-
-            //dict["appName"] = auth.Client ?? string.Empty;
-            //dict["appVersion"] = auth.Version ?? string.Empty;
-            //dict["device"] = auth.Device ?? string.Empty;
-            //dict["deviceId"] = auth.DeviceId ?? string.Empty;
-            //dict["context"] = "streaming";
-
-            ////Logger.Info(JsonSerializer.SerializeToString(dict));
-            //if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-            //{
-            //    var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
-            //    list.Add(outputAudio);
-            //    ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
-            //}
-
-            //if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-            //{
-            //    var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
-            //    list.Add(outputVideo);
-            //    ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
-            //}
-
-            //ServerConfigurationManager.SaveConfiguration();
-
-            ////Logger.Info(JsonSerializer.SerializeToString(dict));
-            //var options = new HttpRequestOptions()
-            //{
-            //    Url = "https://mb3admin.com/admin/service/transcoding/report",
-            //    CancellationToken = CancellationToken.None,
-            //    LogRequest = false,
-            //    LogErrors = false,
-            //    BufferContent = false
-            //};
-            //options.RequestContent = JsonSerializer.SerializeToString(dict);
-            //options.RequestContentType = "application/json";
-
-            //return HttpClient.Post(options);
-        }
-
         /// <summary>
         /// Adds the dlna headers.
         /// </summary>
@@ -1029,6 +918,11 @@ namespace MediaBrowser.Api.Playback
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
         {
+            if (!state.EnableDlnaHeaders)
+            {
+                return;
+            }
+
             var profile = state.DeviceProfile;
 
             var transferMode = GetHeader("transferMode.dlna.org");

+ 1 - 4
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -13,10 +13,7 @@ using System.IO;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -271,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var useGenericSegmenter = true;
             if (useGenericSegmenter)
             {
-                var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
+                var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
 
                 var timeDeltaParam = String.Empty;
 

+ 5 - 4
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -89,6 +89,7 @@ namespace MediaBrowser.Api.Playback.Hls
         public string SegmentId { get; set; }
     }
 
+    [Authenticated]
     public class DynamicHlsService : BaseHlsService
     {
 
@@ -378,7 +379,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem)
         {
-            var folder = Path.GetDirectoryName(playlist);
+            var folder = fileSystem.GetDirectoryName(playlist);
 
             var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty;
 
@@ -415,7 +416,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
         private string GetSegmentPath(StreamState state, string playlist, int index)
         {
-            var folder = Path.GetDirectoryName(playlist);
+            var folder = FileSystem.GetDirectoryName(playlist);
 
             var filename = Path.GetFileNameWithoutExtension(playlist);
 
@@ -807,7 +808,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                return "-codec:a:0 copy";
+                return "-codec:a:0 copy -copypriorss:a:0 0";
             }
 
             var args = "-codec:a:0 " + codec;
@@ -925,7 +926,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (useGenericSegmenter)
             {
-                var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
+                var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
 
                 var timeDeltaParam = String.Empty;
 

+ 6 - 0
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -15,6 +15,8 @@ namespace MediaBrowser.Api.Playback.Hls
     /// <summary>
     /// Class GetHlsAudioSegment
     /// </summary>
+    // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
+    //[Authenticated]
     [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")]
     [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")]
     public class GetHlsAudioSegmentLegacy
@@ -38,6 +40,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// Class GetHlsVideoSegment
     /// </summary>
     [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
+    [Authenticated]
     public class GetHlsPlaylistLegacy
     {
         // TODO: Deprecate with new iOS app
@@ -52,6 +55,7 @@ namespace MediaBrowser.Api.Playback.Hls
     }
 
     [Route("/Videos/ActiveEncodings", "DELETE")]
+    [Authenticated]
     public class StopEncodingProcess
     {
         [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -64,6 +68,8 @@ namespace MediaBrowser.Api.Playback.Hls
     /// <summary>
     /// Class GetHlsVideoSegment
     /// </summary>
+    // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
+    //[Authenticated]
     [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
     public class GetHlsVideoSegmentLegacy : VideoStreamRequest
     {

+ 1 - 0
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -20,6 +20,7 @@ namespace MediaBrowser.Api.Playback.Hls
     /// <summary>
     /// Class VideoHlsService
     /// </summary>
+    [Authenticated]
     public class VideoHlsService : BaseHlsService
     {
         public object Get(GetLiveHlsStream request)

+ 1 - 36
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -59,42 +59,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
             var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
 
-            var audioTranscodeParams = new List<string>();
-
-            var bitrate = state.OutputAudioBitrate;
-
-            if (bitrate.HasValue)
-            {
-                audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture));
-            }
-
-            if (state.OutputAudioChannels.HasValue)
-            {
-                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
-            }
-
-            // opus will fail on 44100
-            if (!string.Equals(state.OutputAudioCodec, "opus", global::System.StringComparison.OrdinalIgnoreCase))
-            {
-                if (state.OutputAudioSampleRate.HasValue)
-                {
-                    audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
-                }
-            }
-
-            const string vn = " -vn";
-
-            var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
-
-            var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
-
-            return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
-                inputModifier,
-                EncodingHelper.GetInputArgument(state, encodingOptions),
-                threads,
-                vn,
-                string.Join(" ", audioTranscodeParams.ToArray()),
-                outputPath).Trim();
+            return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
         }
 
         public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor)

+ 2 - 0
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -62,6 +62,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// <summary>
     /// Class VideoService
     /// </summary>
+    // TODO: In order to autheneticate this in the future, Dlna playback will require updating
+    //[Authenticated]
     public class VideoService : BaseProgressiveStreamingService
     {
         public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor)

+ 2 - 0
MediaBrowser.Api/Playback/StreamState.cs

@@ -138,6 +138,8 @@ namespace MediaBrowser.Api.Playback
             return MimeTypes.GetMimeType(outputPath);
         }
 
+        public bool EnableDlnaHeaders { get; set; }
+
         public void Dispose()
         {
             DisposeTranscodingThrottler();

+ 1 - 1
MediaBrowser.Api/PlaylistService.cs

@@ -102,7 +102,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]

+ 0 - 5
MediaBrowser.Api/Reports/ReportsService.cs

@@ -300,11 +300,6 @@ namespace MediaBrowser.Api.Reports
                 }
             }
 
-            if (!string.IsNullOrEmpty(request.LocationTypes))
-            {
-                query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
-            }
-
             // Min official rating
             if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
             {

+ 44 - 4
MediaBrowser.Api/SearchService.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Search;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Services;
 
 namespace MediaBrowser.Api
@@ -66,6 +67,23 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string IncludeItemTypes { get; set; }
 
+        public string ParentId { get; set; }
+
+        [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsMovie { get; set; }
+
+        [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsSeries { get; set; }
+
+        [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsNews { get; set; }
+
+        [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsKids { get; set; }
+
+        [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
+        public bool? IsSports { get; set; }
+
         public GetSearchHints()
         {
             IncludeArtists = true;
@@ -135,7 +153,14 @@ namespace MediaBrowser.Api
                 IncludeStudios = request.IncludeStudios,
                 StartIndex = request.StartIndex,
                 UserId = request.UserId,
-                IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray()
+                IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
+                ParentId = request.ParentId,
+
+                IsKids = request.IsKids,
+                IsMovie = request.IsMovie,
+                IsNews = request.IsNews,
+                IsSeries = request.IsSeries,
+                IsSports = request.IsSports
 
             }).ConfigureAwait(false);
 
@@ -167,11 +192,11 @@ namespace MediaBrowser.Api
                 MatchedTerm = hintInfo.MatchedTerm,
                 DisplayMediaType = item.DisplayMediaType,
                 RunTimeTicks = item.RunTimeTicks,
-                ProductionYear = item.ProductionYear
+                ProductionYear = item.ProductionYear,
+                ChannelId = item.ChannelId,
+                EndDate = item.EndDate
             };
 
-            result.ChannelId = item.ChannelId;
-
             var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
 
             if (primaryImageTag != null)
@@ -183,12 +208,27 @@ namespace MediaBrowser.Api
             SetThumbImageInfo(result, item);
             SetBackdropImageInfo(result, item);
 
+            var program = item as LiveTvProgram;
+            if (program != null)
+            {
+                result.StartDate = program.StartDate;
+            }
+
             var hasSeries = item as IHasSeries;
             if (hasSeries != null)
             {
                 result.Series = hasSeries.SeriesName;
             }
 
+            var series = item as Series;
+            if (series != null)
+            {
+                if (series.Status.HasValue)
+                {
+                    result.Status = series.Status.Value.ToString();
+                }
+            }
+
             var album = item as MusicAlbum;
 
             if (album != null)

+ 1 - 1
MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs

@@ -58,7 +58,7 @@ namespace MediaBrowser.Api.Session
 
         void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
         {
-            SendData(false);
+            SendData(!e.IsAutomated);
         }
 
         void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)

+ 1 - 1
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -61,7 +61,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
     }
 

+ 1 - 1
MediaBrowser.Api/StartupWizardService.cs

@@ -95,12 +95,12 @@ namespace MediaBrowser.Api
             config.EnableStandaloneMusicKeys = true;
             config.EnableCaseSensitiveItemIds = true;
             config.SkipDeserializationForBasicTypes = true;
-            config.SkipDeserializationForPrograms = true;
             config.SkipDeserializationForAudio = true;
             config.EnableSeriesPresentationUniqueKey = true;
             config.EnableLocalizedGuids = true;
             config.EnableSimpleArtistDetection = true;
             config.EnableNormalizedItemByNameIds = true;
+            config.DisableLiveTvChannelUserDataName = true;
         }
 
         public void Post(UpdateStartupConfiguration request)

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

@@ -288,7 +288,7 @@ namespace MediaBrowser.Api.Subtitles
                     await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
                         .ConfigureAwait(false);
 
-                    _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(_fileSystem));
+                    _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High);
                 }
                 catch (Exception ex)
                 {

+ 5 - 5
MediaBrowser.Api/TvShowsService.cs

@@ -48,7 +48,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "SeriesId", Description = "Optional. Filter by series id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -108,7 +108,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         /// <summary>
@@ -150,7 +150,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -215,7 +215,7 @@ namespace MediaBrowser.Api
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -497,7 +497,7 @@ namespace MediaBrowser.Api
                 }
                 else
                 {
-                    episodes = series.GetSeasonEpisodes(season, user);
+                    episodes = season.GetEpisodes(user);
                 }
             }
             else

+ 1 - 1
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -146,7 +146,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// Fields to return within the items, in addition to basic information
         /// </summary>
         /// <value>The fields.</value>
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         /// <summary>

+ 9 - 1
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -328,7 +328,15 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (!string.IsNullOrEmpty(request.LocationTypes))
             {
-                query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
+                var requestedLocationTypes =
+                    request.LocationTypes.Split(',')
+                        .Select(d => (LocationType) Enum.Parse(typeof (LocationType), d, true))
+                        .ToList();
+
+                if (requestedLocationTypes.Count > 0 && requestedLocationTypes.Count < 4)
+                {
+                    query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual);
+                }
             }
 
             // Min official rating

+ 1 - 1
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -225,7 +225,7 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ParentId { get; set; }
 
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]

+ 4 - 4
MediaBrowser.Api/UserService.cs

@@ -444,7 +444,7 @@ namespace MediaBrowser.Api
 
         public async Task PostAsync(UpdateUserPassword request)
         {
-            AssertCanUpdateUser(_authContext, _userManager, request.Id);
+            AssertCanUpdateUser(_authContext, _userManager, request.Id, true);
 
             var user = _userManager.GetUserById(request.Id);
 
@@ -482,7 +482,7 @@ namespace MediaBrowser.Api
         
         public async Task PostAsync(UpdateUserEasyPassword request)
         {
-            AssertCanUpdateUser(_authContext, _userManager, request.Id);
+            AssertCanUpdateUser(_authContext, _userManager, request.Id, true);
             
             var user = _userManager.GetUserById(request.Id);
 
@@ -518,7 +518,7 @@ namespace MediaBrowser.Api
             // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
             var id = GetPathValue(1);
 
-            AssertCanUpdateUser(_authContext, _userManager, id);
+            AssertCanUpdateUser(_authContext, _userManager, id, false);
 
             var dtoUser = request;
 
@@ -568,7 +568,7 @@ namespace MediaBrowser.Api
 
         public void Post(UpdateUserConfiguration request)
         {
-            AssertCanUpdateUser(_authContext, _userManager, request.Id);
+            AssertCanUpdateUser(_authContext, _userManager, request.Id, false);
 
             var task = _userManager.UpdateConfiguration(request.Id, request);
 

+ 0 - 3
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -9,7 +9,6 @@ using System.Globalization;
 using System.Linq;
 using System.Threading;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Channels;
 using MediaBrowser.Model.Serialization;
 
 namespace MediaBrowser.Controller.Entities.Audio
@@ -24,8 +23,6 @@ namespace MediaBrowser.Controller.Entities.Audio
         IHasLookupInfo<SongInfo>,
         IHasMediaSources
     {
-        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
         /// <summary>
         /// Gets or sets the artist.
         /// </summary>

+ 1 - 1
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -112,7 +112,7 @@ namespace MediaBrowser.Controller.Entities.Audio
 
         public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
         {
-            query.Genres = new[] { Name };
+            query.GenreIds = new[] { Id.ToString("N") };
             query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
 
             return LibraryManager.GetItemList(query);

+ 1 - 3
MediaBrowser.Controller/Entities/AudioBook.cs

@@ -31,12 +31,10 @@ namespace MediaBrowser.Controller.Entities
         public string SeriesName { get; set; }
         [IgnoreDataMember]
         public Guid? SeriesId { get; set; }
-        [IgnoreDataMember]
-        public string SeriesSortName { get; set; }
 
         public string FindSeriesSortName()
         {
-            return SeriesSortName;
+            return SeriesName;
         }
         public string FindSeriesName()
         {

+ 5 - 28
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -84,6 +84,7 @@ namespace MediaBrowser.Controller.Entities
 
         public long? Size { get; set; }
         public string Container { get; set; }
+
         [IgnoreDataMember]
         public string Tagline { get; set; }
 
@@ -288,7 +289,7 @@ namespace MediaBrowser.Controller.Entities
                     return Path;
                 }
 
-                return System.IO.Path.GetDirectoryName(Path);
+                return FileSystem.GetDirectoryName(Path);
             }
         }
 
@@ -834,20 +835,6 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public float? CriticRating { get; set; }
 
-        /// <summary>
-        /// Gets or sets the critic rating summary.
-        /// </summary>
-        /// <value>The critic rating summary.</value>
-        [IgnoreDataMember]
-        public string CriticRatingSummary { get; set; }
-
-        /// <summary>
-        /// Gets or sets the official rating description.
-        /// </summary>
-        /// <value>The official rating description.</value>
-        [IgnoreDataMember]
-        public string OfficialRatingDescription { get; set; }
-
         /// <summary>
         /// Gets or sets the custom rating.
         /// </summary>
@@ -1824,7 +1811,7 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         public virtual Task ChangedExternally()
         {
-            ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(FileSystem));
+            ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(FileSystem), RefreshPriority.High);
             return Task.FromResult(true);
         }
 
@@ -1924,7 +1911,7 @@ namespace MediaBrowser.Controller.Entities
         {
             var allFiles = ImageInfos
                 .Where(i => i.IsLocalFile)
-                .Select(i => System.IO.Path.GetDirectoryName(i.Path))
+                .Select(i => FileSystem.GetDirectoryName(i.Path))
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .SelectMany(directoryService.GetFilePaths)
                 .ToList();
@@ -2099,7 +2086,7 @@ namespace MediaBrowser.Controller.Entities
             var extensions = new[] { ".nfo", ".xml", ".srt" }.ToList();
             extensions.AddRange(SupportedImageExtensionsList);
 
-            return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path), extensions.ToArray(), false, false)
+            return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(), false, false)
                 .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase))
                 .ToList();
         }
@@ -2298,16 +2285,6 @@ namespace MediaBrowser.Controller.Entities
                     ownedItem.CustomRating = item.CustomRating;
                     newOptions.ForceSave = true;
                 }
-                if (!string.Equals(item.CriticRatingSummary, ownedItem.CriticRatingSummary, StringComparison.Ordinal))
-                {
-                    ownedItem.CriticRatingSummary = item.CriticRatingSummary;
-                    newOptions.ForceSave = true;
-                }
-                if (!string.Equals(item.OfficialRatingDescription, ownedItem.OfficialRatingDescription, StringComparison.Ordinal))
-                {
-                    ownedItem.OfficialRatingDescription = item.OfficialRatingDescription;
-                    newOptions.ForceSave = true;
-                }
             }
 
             return ownedItem.RefreshMetadata(newOptions, cancellationToken);

+ 1 - 3
MediaBrowser.Controller/Entities/Book.cs

@@ -24,12 +24,10 @@ namespace MediaBrowser.Controller.Entities
         public string SeriesName { get; set; }
         [IgnoreDataMember]
         public Guid? SeriesId { get; set; }
-        [IgnoreDataMember]
-        public string SeriesSortName { get; set; }
 
         public string FindSeriesSortName()
         {
-            return SeriesSortName;
+            return SeriesName;
         }
         public string FindSeriesName()
         {

+ 3 - 3
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
                 .SelectMany(c => c.LinkedChildren)
                 .ToList();
 
-            var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer());
+            var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer(FileSystem));
 
             LinkedChildren = linkedChildren;
 
@@ -332,13 +332,13 @@ namespace MediaBrowser.Controller.Entities
                 .OfType<Folder>()
                 .ToList();
 
-            return PhysicalLocations.Where(i => !string.Equals(i, Path, StringComparison.OrdinalIgnoreCase)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id);
+            return PhysicalLocations.Where(i => !FileSystem.AreEqual(i, Path)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id);
         }
 
         private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
         {
             var result = rootChildren
-                .Where(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase))
+                .Where(i => FileSystem.AreEqual(i.Path, path))
                 .ToList();
 
             if (result.Count == 0)

+ 20 - 7
MediaBrowser.Controller/Entities/Folder.cs

@@ -640,7 +640,7 @@ namespace MediaBrowser.Controller.Entities
                     return true;
                 }
 
-                path = System.IO.Path.GetDirectoryName(path);
+                path = FileSystem.GetDirectoryName(path);
             }
 
             return allLibraryPaths.Any(i => ContainsPath(i, originalPath));
@@ -1206,11 +1206,17 @@ namespace MediaBrowser.Controller.Entities
                 return GetLinkedChildren();
             }
 
-            var locations = user.RootFolder
-                .Children
+            if (LinkedChildren.Count == 0)
+            {
+                return new List<BaseItem>();
+            }
+
+            var allUserRootChildren = user.RootFolder.Children.OfType<Folder>().ToList();
+
+            var collectionFolderIds = allUserRootChildren
                 .OfType<CollectionFolder>()
                 .Where(i => i.IsVisible(user))
-                .SelectMany(i => i.PhysicalLocations)
+                .Select(i => i.Id)
                 .ToList();
 
             return LinkedChildren
@@ -1228,9 +1234,16 @@ namespace MediaBrowser.Controller.Entities
                                 return null;
                             }
                         }
-                        else if (childLocationType == LocationType.FileSystem && !locations.Any(l => FileSystem.ContainsSubPath(l, child.Path)))
+                        else if (childLocationType == LocationType.FileSystem)
                         {
-                            return null;
+                            var itemCollectionFolderIds =
+                                LibraryManager.GetCollectionFolders(child, allUserRootChildren)
+                                .Select(f => f.Id).ToList();
+
+                            if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains))
+                            {
+                                return null;
+                            }
                         }
                     }
 
@@ -1323,7 +1336,7 @@ namespace MediaBrowser.Controller.Entities
             }
             else { newShortcutLinks = new List<LinkedChild>(); }
 
-            if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer()))
+            if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer(FileSystem)))
             {
                 Logger.Info("Shortcut links have changed for {0}", Path);
 

+ 1 - 1
MediaBrowser.Controller/Entities/Game.cs

@@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Entities
                 return new[] {
                     new FileSystemMetadata
                     {
-                        FullName = System.IO.Path.GetDirectoryName(Path),
+                        FullName = FileSystem.GetDirectoryName(Path),
                         IsDirectory = true
                     }
                 };

+ 1 - 1
MediaBrowser.Controller/Entities/GameGenre.cs

@@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities
 
         public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
         {
-            query.Genres = new[] { Name };
+            query.GenreIds = new[] { Id.ToString("N") };
             query.IncludeItemTypes = new[] { typeof(Game).Name };
 
             return LibraryManager.GetItemList(query);

+ 1 - 1
MediaBrowser.Controller/Entities/Genre.cs

@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities
 
         public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
         {
-            query.Genres = new[] { Name };
+            query.GenreIds = new[] { Id.ToString("N") };
             query.ExcludeItemTypes = new[] { typeof(Game).Name, typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
 
             return LibraryManager.GetItemList(query);

+ 0 - 1
MediaBrowser.Controller/Entities/IHasSeries.cs

@@ -11,7 +11,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The name of the series.</value>
         string SeriesName { get; set; }
         string FindSeriesName();
-        string SeriesSortName { get; set; }
         string FindSeriesSortName();
         Guid? SeriesId { get; set; }
         Guid? FindSeriesId();

+ 0 - 4
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -129,7 +129,6 @@ namespace MediaBrowser.Controller.Entities
         public string[] AncestorIds { get; set; }
         public string[] TopParentIds { get; set; }
 
-        public LocationType[] LocationTypes { get; set; }
         public string[] PresetViews { get; set; }
         public SourceType[] SourceTypes { get; set; }
         public SourceType[] ExcludeSourceTypes { get; set; }
@@ -176,7 +175,6 @@ namespace MediaBrowser.Controller.Entities
                 case ItemFields.DateCreated:
                 case ItemFields.SortName:
                 case ItemFields.Overview:
-                case ItemFields.OfficialRatingDescription:
                 case ItemFields.HomePageUrl:
                 case ItemFields.VoteCount:
                 case ItemFields.DisplayMediaType:
@@ -187,7 +185,6 @@ namespace MediaBrowser.Controller.Entities
                 case ItemFields.OriginalTitle:
                 case ItemFields.Tags:
                 case ItemFields.DateLastMediaAdded:
-                case ItemFields.CriticRatingSummary:
                     return fields.Count == 0 || fields.Contains(name);
                 default:
                     return true;
@@ -230,7 +227,6 @@ namespace MediaBrowser.Controller.Entities
             TopParentIds = new string[] { };
             ExcludeTags = new string[] { };
             ExcludeInheritedTags = new string[] { };
-            LocationTypes = new LocationType[] { };
             PresetViews = new string[] { };
             SourceTypes = new SourceType[] { };
             ExcludeSourceTypes = new SourceType[] { };

+ 9 - 1
MediaBrowser.Controller/Entities/LinkedChild.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 
 namespace MediaBrowser.Controller.Entities
@@ -40,11 +41,18 @@ namespace MediaBrowser.Controller.Entities
 
     public class LinkedChildComparer : IEqualityComparer<LinkedChild>
     {
+        private readonly IFileSystem _fileSystem;
+
+        public LinkedChildComparer(IFileSystem fileSystem)
+        {
+            _fileSystem = fileSystem;
+        }
+
         public bool Equals(LinkedChild x, LinkedChild y)
         {
             if (x.Type == y.Type)
             {
-                return string.Equals(x.Path, y.Path, StringComparison.OrdinalIgnoreCase);
+                return _fileSystem.AreEqual(x.Path, y.Path);
             }
             return false;
         }

+ 2 - 4
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -152,9 +152,7 @@ namespace MediaBrowser.Controller.Entities.Movies
             var currentOfficialRating = OfficialRating;
 
             // Gather all possible ratings
-            var ratings = GetRecursiveChildren()
-                .Concat(GetLinkedChildren())
-                .Where(i => i is Movie || i is Series || i is MusicAlbum || i is Game)
+            var ratings = GetRecursiveChildren(i => i is Movie || i is Series || i is MusicAlbum || i is Game)
                 .Select(i => i.OfficialRating)
                 .Where(i => !string.IsNullOrEmpty(i))
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -205,7 +203,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
             if (base.IsVisible(user))
             {
-                return GetChildren(user, true).Any();
+                return base.GetChildren(user, true).Any();
             }
 
             return false;

+ 1 - 4
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -57,13 +57,10 @@ namespace MediaBrowser.Controller.Entities.TV
         /// <value>The index number.</value>
         public int? IndexNumberEnd { get; set; }
 
-        [IgnoreDataMember]
-        public string SeriesSortName { get; set; }
-
         public string FindSeriesSortName()
         {
             var series = Series;
-            return series == null ? SeriesSortName : series.SortName;
+            return series == null ? SeriesName : series.SortName;
         }
 
         [IgnoreDataMember]

+ 2 - 5
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -51,9 +51,6 @@ namespace MediaBrowser.Controller.Entities.TV
             get { return SeriesId; }
         }
 
-        [IgnoreDataMember]
-        public string SeriesSortName { get; set; }
-
         public override double? GetDefaultPrimaryImageAspectRatio()
         {
             double value = 2;
@@ -65,7 +62,7 @@ namespace MediaBrowser.Controller.Entities.TV
         public string FindSeriesSortName()
         {
             var series = Series;
-            return series == null ? SeriesSortName : series.SortName;
+            return series == null ? SeriesName : series.SortName;
         }
 
         // Genre, Rating and Stuido will all be the same
@@ -125,7 +122,7 @@ namespace MediaBrowser.Controller.Entities.TV
                     return series.Path;
                 }
 
-                return System.IO.Path.GetDirectoryName(Path);
+                return FileSystem.GetDirectoryName(Path);
             }
         }
 

+ 7 - 22
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -586,7 +586,7 @@ namespace MediaBrowser.Controller.Entities
         {
             query.Recursive = true;
             query.ParentId = queryParent.Id;
-            query.Genres = new[] { displayParent.Name };
+            query.GenreIds = new[] { displayParent.Id.ToString("N") };
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
@@ -729,7 +729,7 @@ namespace MediaBrowser.Controller.Entities
         {
             query.Recursive = true;
             query.ParentId = queryParent.Id;
-            query.Genres = new[] { displayParent.Name };
+            query.GenreIds = new[] { displayParent.Id.ToString("N") };
             query.SetUser(user);
 
             query.IncludeItemTypes = new[] { typeof(Series).Name };
@@ -905,6 +905,11 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
+            if (request.GenreIds.Length > 0)
+            {
+                return false;
+            }
+
             if (request.HasImdbId.HasValue)
             {
                 return false;
@@ -1768,26 +1773,6 @@ namespace MediaBrowser.Controller.Entities
             return new List<Folder> { parent };
         }
 
-        private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, IEnumerable<string> viewTypes)
-        {
-            if (parent == null || parent is UserView)
-            {
-                if (user == null)
-                {
-                    return GetMediaFolders(null, viewTypes).SelectMany(i => i.GetRecursiveChildren());
-                }
-
-                return GetMediaFolders(user, viewTypes).SelectMany(i => i.GetRecursiveChildren(user));
-            }
-
-            if (user == null)
-            {
-                return parent.GetRecursiveChildren();
-            }
-
-            return parent.GetRecursiveChildren(user);
-        }
-
         private async Task<QueryResult<BaseItem>> GetLiveTvView(Folder queryParent, User user, InternalItemsQuery query)
         {
             if (query.Recursive)

+ 29 - 26
MediaBrowser.Controller/Entities/Video.cs

@@ -33,7 +33,6 @@ namespace MediaBrowser.Controller.Entities
         public List<string> AdditionalParts { get; set; }
         public List<string> LocalAlternateVersions { get; set; }
         public List<LinkedChild> LinkedAlternateVersions { get; set; }
-        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
 
         [IgnoreDataMember]
         public override bool SupportsPlayedStatus
@@ -158,7 +157,6 @@ namespace MediaBrowser.Controller.Entities
             PlayableStreamFileNames = new List<string>();
             AdditionalParts = new List<string>();
             LocalAlternateVersions = new List<string>();
-            Tags = new List<string>();
             SubtitleFiles = new List<string>();
             LinkedAlternateVersions = new List<LinkedChild>();
         }
@@ -313,7 +311,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 if (IsStacked)
                 {
-                    return System.IO.Path.GetDirectoryName(Path);
+                    return FileSystem.GetDirectoryName(Path);
                 }
 
                 if (!IsPlaceHolder)
@@ -591,41 +589,46 @@ namespace MediaBrowser.Controller.Entities
             .ToList();
         }
 
-        private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type)
+        private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video media, MediaSourceType type)
         {
-            var mediaStreams = MediaSourceManager.GetMediaStreams(i.Id)
+            if (media == null)
+            {
+                throw new ArgumentNullException("media");
+            }
+
+            var mediaStreams = MediaSourceManager.GetMediaStreams(media.Id)
                 .ToList();
 
-            var locationType = i.LocationType;
+            var locationType = media.LocationType;
 
             var info = new MediaSourceInfo
             {
-                Id = i.Id.ToString("N"),
-                IsoType = i.IsoType,
+                Id = media.Id.ToString("N"),
+                IsoType = media.IsoType,
                 Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
                 MediaStreams = mediaStreams,
-                Name = GetMediaSourceName(i, mediaStreams),
-                Path = enablePathSubstitution ? GetMappedPath(i, i.Path, locationType) : i.Path,
-                RunTimeTicks = i.RunTimeTicks,
-                Video3DFormat = i.Video3DFormat,
-                VideoType = i.VideoType,
-                Container = i.Container,
-                Size = i.Size,
-                Timestamp = i.Timestamp,
+                Name = GetMediaSourceName(media, mediaStreams),
+                Path = enablePathSubstitution ? GetMappedPath(media, media.Path, locationType) : media.Path,
+                RunTimeTicks = media.RunTimeTicks,
+                Video3DFormat = media.Video3DFormat,
+                VideoType = media.VideoType,
+                Container = media.Container,
+                Size = media.Size,
+                Timestamp = media.Timestamp,
                 Type = type,
-                PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
-                SupportsDirectStream = i.VideoType == VideoType.VideoFile,
-                IsRemote = i.IsShortcut
+                PlayableStreamFileNames = media.PlayableStreamFileNames.ToList(),
+                SupportsDirectStream = media.VideoType == VideoType.VideoFile,
+                IsRemote = media.IsShortcut
             };
 
             if (info.Protocol == MediaProtocol.File)
             {
-                info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
+                info.ETag = media.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
             }
 
-            if (i.IsShortcut)
+            if (media.IsShortcut)
             {
-                info.Path = i.ShortcutPath;
+                info.Path = media.ShortcutPath;
 
                 if (info.Path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
                 {
@@ -647,16 +650,16 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrEmpty(info.Container))
             {
-                if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
+                if (media.VideoType == VideoType.VideoFile || media.VideoType == VideoType.Iso)
                 {
-                    if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+                    if (!string.IsNullOrWhiteSpace(media.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
                     {
-                        info.Container = System.IO.Path.GetExtension(i.Path).TrimStart('.');
+                        info.Container = System.IO.Path.GetExtension(media.Path).TrimStart('.');
                     }
                 }
             }
 
-            info.Bitrate = i.TotalBitrate;
+            info.Bitrate = media.TotalBitrate;
             info.InferTotalBitrate();
 
             return info;

+ 2 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -456,6 +456,8 @@ namespace MediaBrowser.Controller.Library
         /// <returns>IEnumerable&lt;Folder&gt;.</returns>
         List<Folder> GetCollectionFolders(BaseItem item);
 
+        List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren);
+
         LibraryOptions GetLibraryOptions(BaseItem item);
 
         /// <summary>

部分文件因文件數量過多而無法顯示