diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs index 123d1afe5..d86648364 100644 --- a/BDInfo/BDROM.cs +++ b/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; diff --git a/Emby.Common.Implementations/Devices/DeviceId.cs b/Emby.Common.Implementations/Devices/DeviceId.cs index 3d23ab872..1de76456c 100644 --- a/Emby.Common.Implementations/Devices/DeviceId.cs +++ b/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) { diff --git a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs index ad6e35700..27fc642f1 100644 --- a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs @@ -131,4 +131,4 @@ namespace Emby.Common.Implementations.EnvironmentInfo Environment.SetEnvironmentVariable(name, value); } } -} +} \ No newline at end of file diff --git a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs index 23f33f06c..eb9bc1bd0 100644 --- a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/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) { diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index ba73c1ba2..3ed4f650f 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/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); diff --git a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs b/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs index c1e429dc9..0a407d64f 100644 --- a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs +++ b/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) diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs index 3721709e6..82e7e9b00 100644 --- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs +++ b/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(); diff --git a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index f0518f69e..ac1c55b6b 100644 --- a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/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); } diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 77482d56b..500f57aad 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/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))) { diff --git a/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs b/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs index 14fb0ddf1..58c6cfe82 100644 --- a/Emby.Drawing.ImageMagick/PlayedIndicatorDrawer.cs +++ b/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 { diff --git a/Emby.Drawing.Net/GDIImageEncoder.cs b/Emby.Drawing.Net/GDIImageEncoder.cs index 831a57979..638415afd 100644 --- a/Emby.Drawing.Net/GDIImageEncoder.cs +++ b/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)) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index f19e3e037..bee0e9b69 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/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); diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index b7309de66..78bdc1189 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/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 _certificateGenerator; + private readonly Action _certificateGenerator; private readonly Func _defaultUserNameFactory; /// @@ -274,7 +274,7 @@ namespace Emby.Server.Core ISystemEvents systemEvents, IMemoryStreamFactory memoryStreamFactory, INetworkManager networkManager, - Action certificateGenerator, + Action certificateGenerator, Func defaultUsernameFactory) : base(applicationPaths, logManager, @@ -609,8 +609,8 @@ namespace Emby.Server.Core RegisterSingleInstance(() => 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()); } - private string CertificatePath { get; set; } + private CertificateInfo CertificateInfo { get; set; } private ICertificate Certificate { get; set; } private IEnumerable 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 + }; } /// @@ -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; } } } diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs index ae7b66597..0f6a2e9a2 100644 --- a/Emby.Server.Core/IO/LibraryMonitor.cs +++ b/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; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8f03fa7a4..0cdd934b7 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/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> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken) { - IEnumerable results = new List(); - 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 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); } } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index d0bd76c35..9c26655fc 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/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 { diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 0096f2284..e25955782 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/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 progress) @@ -115,115 +114,6 @@ namespace Emby.Server.Implementations.Data progress.Report(100); } - private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress 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); - } - } - /// /// Creates the triggers that define when the task will run /// diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 51f91acf3..61dce9bba 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/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)) { - if (type == typeof(LiveTvProgram)) - { - return false; - } + return false; } + if (type == typeof(Book)) + { + 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("(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("(Select SortName from TypedBaseItems where B.Guid=A.SeriesId)", false); + } return new Tuple(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"); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7c0baf9c2..78d76fd76 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/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) { diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 0a9c67285..86c8c5f68 100644 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/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(); } - var episodePaths = series.GetRecursiveChildren() + var episodePaths = series.GetRecursiveChildren(i => i is Episode) .OfType() .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); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index dbaf97b1e..8cb7b5dbf 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/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); } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 310161d41..687bd62b0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/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) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index e88994bec..7c967949b 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/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); diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 6d9d7d921..57eef5db0 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/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); } /// diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index c64672685..033cbd8b0 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/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) diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 38908c2bd..2677f7b2a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/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 CreateCollage(IHasImages primaryItem, List items, string outputPath, int width, int height) { - FileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath)); var options = new ImageCollageOptions { diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index d782f5b88..64f025d93 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/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)) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 16a73f392..685c794b7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1962,8 +1962,34 @@ namespace Emby.Server.Implementations.Library return new List(); } - return GetUserRootFolder().Children - .OfType() + return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType().ToList()); + } + + public List GetCollectionFolders(BaseItem item, List allUserRootChildren) + { + while (item != null) + { + var parent = item.GetParent(); + + if (parent == null || parent is AggregateFolder) + { + break; + } + + item = parent; + } + + if (item == null) + { + return new List(); + } + + return GetCollectionFoldersInternal(item, allUserRootChildren); + } + + private List GetCollectionFoldersInternal(BaseItem item, List 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; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 9d07837c6..b15c01125 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/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,53 +28,17 @@ namespace Emby.Server.Implementations.Library return list.Concat(GetInstantMixFromGenres(item.Genres, user)); } - public IEnumerable /// The fields. - [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 /// /// The fields. - [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")] diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 699c4bbb2..daec00e10 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/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(); - - //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); - } - /// /// Adds the dlna headers. /// @@ -1029,6 +918,11 @@ namespace MediaBrowser.Api.Playback /// true if XXXX, false otherwise protected void AddDlnaHeaders(StreamState state, IDictionary responseHeaders, bool isStaticallyStreamed) { + if (!state.EnableDlnaHeaders) + { + return; + } + var profile = state.DeviceProfile; var transferMode = GetHeader("transferMode.dlna.org"); diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 53813860a..fcf57cebe 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/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; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 038d76245..0fe1e533d 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/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; diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index ae049a83a..0ff52e63f 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Class GetHlsAudioSegment /// + // 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 /// [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 /// /// Class GetHlsVideoSegment /// + // 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 { diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index a813d7a87..22c6202e4 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Class VideoHlsService /// + [Authenticated] public class VideoHlsService : BaseHlsService { public object Get(GetLiveHlsStream request) diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index a64523e40..f0386d5ba 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -59,42 +59,7 @@ namespace MediaBrowser.Api.Playback.Progressive { var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - var audioTranscodeParams = new List(); - - 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) diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 8394e016c..c36a27690 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -62,6 +62,8 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// Class VideoService /// + // 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) diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 7f146466d..4b1687d68 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/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(); diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index bb2bc449b..9f37bb70a 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Api /// Fields to return within the items, in addition to basic information /// /// The fields. - [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")] diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs index 5e13c1653..9490c301d 100644 --- a/MediaBrowser.Api/Reports/ReportsService.cs +++ b/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)) { diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index d6fa4030d..3df815cda 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/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) diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index b90a71852..e133a88a1 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/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) diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 892c9f698..5463ae982 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Api /// Fields to return within the items, in addition to basic information /// /// The fields. - [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; } } diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 5a1074b7b..7e13512aa 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/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) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 47d442e79..798004a5e 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/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) { diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 126f1c753..4b279031e 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Api /// Fields to return within the items, in addition to basic information /// /// The fields. - [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 /// /// The fields. - [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; } /// @@ -150,7 +150,7 @@ namespace MediaBrowser.Api /// Fields to return within the items, in addition to basic information /// /// The fields. - [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 /// /// The fields. - [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 diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 1acbce6db..55c23841c 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Api.UserLibrary /// Fields to return within the items, in addition to basic information /// /// The fields. - [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; } /// diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 5d267d059..f8580d328 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/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 diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 32f3a1f00..b1f196f15 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/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)] diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index fde03e1f2..49b7f6c15 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/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); diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 59b5a3869..92d8d95bc 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/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, IHasMediaSources { - public List ChannelMediaSources { get; set; } - /// /// Gets or sets the artist. /// diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index e26e0dfce..2f99e530e 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -112,7 +112,7 @@ namespace MediaBrowser.Controller.Entities.Audio public IEnumerable 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); diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index 8b1c338f1..1bdcfb881 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/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() { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index c8ea4c506..999f6db3f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/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; } - /// - /// Gets or sets the critic rating summary. - /// - /// The critic rating summary. - [IgnoreDataMember] - public string CriticRatingSummary { get; set; } - - /// - /// Gets or sets the official rating description. - /// - /// The official rating description. - [IgnoreDataMember] - public string OfficialRatingDescription { get; set; } - /// /// Gets or sets the custom rating. /// @@ -1824,7 +1811,7 @@ namespace MediaBrowser.Controller.Entities /// Task. 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); diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index a6da389f0..7cb242589 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/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() { diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 62ea21a79..24474ba55 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/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() .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 GetPhysicalParents(string path, List 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) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 268fefbd3..edac27f99 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/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(); + } + + var allUserRootChildren = user.RootFolder.Children.OfType().ToList(); + + var collectionFolderIds = allUserRootChildren .OfType() .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(); } - if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer())) + if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer(FileSystem))) { Logger.Info("Shortcut links have changed for {0}", Path); diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index d19552c07..baefc9dfa 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/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 } }; diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 4187167b9..093b191b6 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities public IEnumerable GetTaggedItems(InternalItemsQuery query) { - query.Genres = new[] { Name }; + query.GenreIds = new[] { Id.ToString("N") }; query.IncludeItemTypes = new[] { typeof(Game).Name }; return LibraryManager.GetItemList(query); diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 9769efdd0..6569a1e6c 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities public IEnumerable 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); diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 203be93e8..20efdc2b8 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -11,7 +11,6 @@ namespace MediaBrowser.Controller.Entities /// The name of the series. string SeriesName { get; set; } string FindSeriesName(); - string SeriesSortName { get; set; } string FindSeriesSortName(); Guid? SeriesId { get; set; } Guid? FindSeriesId(); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index ea4d60a44..092461c84 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/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[] { }; diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 4d3c13c6e..6031a2448 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/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 { + 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; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 030831717..3f733ce03 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/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; diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 31bf8d28b..c2f7a6168 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -57,13 +57,10 @@ namespace MediaBrowser.Controller.Entities.TV /// The index number. 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] diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index be268782d..ed04b5ddc 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/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); } } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index f879d0fd8..8da069f62 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/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 { parent }; } - private IEnumerable GetRecursiveChildren(Folder parent, User user, IEnumerable 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> GetLiveTvView(Folder queryParent, User user, InternalItemsQuery query) { if (query.Recursive) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 0618fc489..90aa3690a 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -33,7 +33,6 @@ namespace MediaBrowser.Controller.Entities public List AdditionalParts { get; set; } public List LocalAlternateVersions { get; set; } public List LinkedAlternateVersions { get; set; } - public List ChannelMediaSources { get; set; } [IgnoreDataMember] public override bool SupportsPlayedStatus @@ -158,7 +157,6 @@ namespace MediaBrowser.Controller.Entities PlayableStreamFileNames = new List(); AdditionalParts = new List(); LocalAlternateVersions = new List(); - Tags = new List(); SubtitleFiles = new List(); LinkedAlternateVersions = new List(); } @@ -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; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index ebebe71a3..dd2379940 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -456,6 +456,8 @@ namespace MediaBrowser.Controller.Library /// IEnumerable<Folder>. List GetCollectionFolders(BaseItem item); + List GetCollectionFolders(BaseItem item, List allUserRootChildren); + LibraryOptions GetLibraryOptions(BaseItem item); /// diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 763d27eba..3aa4d4ee2 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -114,7 +114,7 @@ namespace MediaBrowser.Controller.Library return false; } - var parentDir = System.IO.Path.GetDirectoryName(Path) ?? string.Empty; + var parentDir = BaseItem.FileSystem.GetDirectoryName(Path) ?? string.Empty; return parentDir.Length > _appPaths.RootFolderPath.Length && parentDir.StartsWith(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase); @@ -130,7 +130,7 @@ namespace MediaBrowser.Controller.Library { get { - return IsDirectory && string.Equals(Path, _appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase); + return IsDirectory && BaseItem.FileSystem.AreEqual(Path, _appPaths.RootFolderPath); } } @@ -300,7 +300,7 @@ namespace MediaBrowser.Controller.Library if (args != null) { if (args.Path == null && Path == null) return true; - return args.Path != null && args.Path.Equals(Path, StringComparison.OrdinalIgnoreCase); + return args.Path != null && BaseItem.FileSystem.AreEqual(args.Path, Path); } return false; } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index bcf39558e..0644719b6 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -16,6 +16,7 @@ namespace MediaBrowser.Controller.Library public BaseItemInfo MediaInfo { get; set; } public string MediaSourceId { get; set; } public bool IsPaused { get; set; } + public bool IsAutomated { get; set; } public string DeviceId { get; set; } public string DeviceName { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index fede81faf..6e2fe2495 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -16,7 +16,11 @@ namespace MediaBrowser.Controller.LiveTv { var list = base.GetUserDataKeys(); - list.Insert(0, GetClientTypeName() + "-" + Name); + if (!ConfigurationManager.Configuration.DisableLiveTvChannelUserDataName) + { + list.Insert(0, GetClientTypeName() + "-" + Name); + } + return list; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 22d09f34a..ca0b97a9f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -732,7 +732,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { - param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none"; + param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none"; } if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && @@ -1517,12 +1517,6 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest); inputModifier = inputModifier.Trim(); - //inputModifier += " -fflags +genpts+ignidx+igndts"; - //if (state.IsVideoRequest && genPts) - //{ - // inputModifier += " -fflags +genpts"; - //} - if (!string.IsNullOrEmpty(state.InputAudioSync)) { inputModifier += " -async " + state.InputAudioSync; @@ -1538,6 +1532,21 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " -re"; } + var flags = new List(); + if (state.IgnoreDts) + { + flags.Add("+igndts"); + } + if (state.IgnoreIndex) + { + flags.Add("+ignidx"); + } + + if (flags.Count > 0) + { + inputModifier += " -fflags " + string.Join("", flags.ToArray()); + } + var videoDecoder = GetVideoDecoder(state, encodingOptions); if (!string.IsNullOrWhiteSpace(videoDecoder)) { @@ -1725,6 +1734,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) { + if (!string.IsNullOrWhiteSpace(encodingOptions.HardwareAccelerationType)) + { + return "-hwaccel auto"; + } + if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { switch (state.MediaSource.VideoStream.Codec.ToLower()) @@ -1974,5 +1988,65 @@ namespace MediaBrowser.Controller.MediaEncoding return args; } + + public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath) + { + var audioTranscodeParams = new List(); + + 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", StringComparison.OrdinalIgnoreCase)) + { + if (state.OutputAudioSampleRate.HasValue) + { + audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture)); + } + } + + var albumCoverInput = string.Empty; + var mapArgs = string.Empty; + var metadata = string.Empty; + var vn = string.Empty; + + var hasArt = !string.IsNullOrWhiteSpace(state.AlbumCoverPath); + + if (hasArt) + { + albumCoverInput = " -i \"" + state.AlbumCoverPath + "\""; + mapArgs = " -map 0:a -map 1:v -c:1:v copy"; + metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\""; + } + else + { + vn = " -vn"; + } + + var threads = GetNumberOfThreads(state, encodingOptions, false); + + var inputModifier = GetInputModifier(state, encodingOptions); + + return string.Format("{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", + inputModifier, + GetInputArgument(state, encodingOptions), + threads, + vn, + string.Join(" ", audioTranscodeParams.ToArray()), + outputPath, + metadata, + albumCoverInput, + mapArgs).Trim(); + } + } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index f3e6280aa..28ada9dae 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -39,6 +39,16 @@ namespace MediaBrowser.Controller.MediaEncoding public bool ReadInputAtNativeFramerate { get; set; } + public bool IgnoreDts + { + get { return MediaSource.IgnoreDts; } + } + + public bool IgnoreIndex + { + get { return MediaSource.IgnoreIndex; } + } + public string OutputContainer { get; set; } public string OutputVideoSync @@ -56,6 +66,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public string AlbumCoverPath { get; set; } + public string InputAudioSync { get; set; } public string InputVideoSync { get; set; } public TransportStreamTimestamp InputTimestamp { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 78ed1dc59..bac9807a9 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -126,7 +126,6 @@ namespace MediaBrowser.Controller.MediaEncoding Task UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); - bool IsDefaultEncoderPath { get; } void SetLogFilename(string name); void ClearLogFilename(); diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 0eb435375..fb1410f4a 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -134,34 +134,27 @@ namespace MediaBrowser.Controller.Playlists var musicGenre = item as MusicGenre; if (musicGenre != null) { - var items = LibraryManager.GetItemList(new InternalItemsQuery(user) + return LibraryManager.GetItemList(new InternalItemsQuery(user) { Recursive = true, IncludeItemTypes = new[] { typeof(Audio).Name }, - Genres = new[] { musicGenre.Name } + GenreIds = new[] { musicGenre.Id.ToString("N") }, + SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, + SortOrder = SortOrder.Ascending }); - - return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending); } var musicArtist = item as MusicArtist; if (musicArtist != null) { - Func filter = i => + return LibraryManager.GetItemList(new InternalItemsQuery(user) { - var audio = i as Audio; - return audio != null && audio.HasAnyArtist(musicArtist.Name); - }; - - var items = user == null - ? LibraryManager.RootFolder.GetRecursiveChildren(filter) - : user.RootFolder.GetRecursiveChildren(user, new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(Audio).Name }, - ArtistIds = new[] { musicArtist.Id.ToString("N") } - }); - - return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending); + Recursive = true, + IncludeItemTypes = new[] { typeof(Audio).Name }, + ArtistIds = new[] { musicArtist.Id.ToString("N") }, + SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, + SortOrder = SortOrder.Ascending + }); } var folder = item as Folder; diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 40093df3a..62db007b9 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -63,11 +63,11 @@ namespace MediaBrowser.Controller.Providers //_logger.Debug("Getting files for " + path); entries = new Dictionary(StringComparer.OrdinalIgnoreCase); - + try { // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks) - var list = _fileSystem.GetFileSystemEntries(path) + var list = _fileSystem.GetFileSystemEntries(path) .ToList(); // Seeing dupes on some users file system for some reason @@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Providers { } - //var group = entries.ToLookup(i => Path.GetDirectoryName(i.FullName)).ToList(); + //var group = entries.ToLookup(i => _fileSystem.GetDirectoryName(i.FullName)).ToList(); _cache.TryAdd(path, entries); } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index f4d45c7e0..c0bc90214 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -20,9 +20,7 @@ namespace MediaBrowser.Controller.Providers /// /// Queues the refresh. /// - /// The item identifier. - /// The options. - void QueueRefresh(Guid itemId, MetadataRefreshOptions options); + void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority); /// /// Refreshes the full item. @@ -161,4 +159,11 @@ namespace MediaBrowser.Controller.Providers /// Task{HttpResponseInfo}. Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken); } + + public enum RefreshPriority + { + High = 0, + Normal = 1, + Low = 2 + } } \ No newline at end of file diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index e005ad224..bd33dece8 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.LocalMetadata.Images public List GetImages(IHasImages item, IDirectoryService directoryService) { - var parentPath = Path.GetDirectoryName(item.Path); + var parentPath = _fileSystem.GetDirectoryName(item.Path); var parentPathFiles = directoryService.GetFileSystemEntries(parentPath) .ToList(); diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index e56bfb49c..2500ee482 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -246,18 +246,6 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } - case "CriticRatingSummary": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) - { - item.CriticRatingSummary = val; - } - - break; - } - case "Language": { var val = reader.ReadElementContentAsString(); @@ -377,17 +365,6 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } - case "MPAADescription": - { - var rating = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(rating)) - { - item.OfficialRatingDescription = rating; - } - break; - } - case "CustomRating": { var val = reader.ReadElementContentAsString(); diff --git a/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs index 9adc4e5a9..69d799b18 100644 --- a/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.LocalMetadata.Parsers // even though it's actually using the metadata folder. filename = Path.GetFileName(filename); - var parentFolder = Path.GetDirectoryName(_xmlPath); + var parentFolder = _fileSystem.GetDirectoryName(_xmlPath); filename = Path.Combine(parentFolder, filename); var file = _fileSystem.GetFileInfo(filename); diff --git a/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs index 7e0f1707f..fda8ea6d0 100644 --- a/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Providers protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) { - var metadataPath = Path.GetDirectoryName(info.Path); + var metadataPath = FileSystem.GetDirectoryName(info.Path); metadataPath = Path.Combine(metadataPath, "metadata"); var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml")); diff --git a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs index 888eff416..8ed18df64 100644 --- a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.LocalMetadata.Providers var specificFile = Path.ChangeExtension(info.Path, ".xml"); var file = FileSystem.GetFileInfo(specificFile); - return info.IsInMixedFolder || file.Exists ? file : FileSystem.GetFileInfo(Path.Combine(Path.GetDirectoryName(info.Path), "game.xml")); + return info.IsInMixedFolder || file.Exists ? file : FileSystem.GetFileInfo(Path.Combine(FileSystem.GetDirectoryName(info.Path), "game.xml")); } } } diff --git a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs index 0e59db75f..2d4059a1a 100644 --- a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Providers { var fileInfo = fileSystem.GetFileSystemInfo(info.Path); - var directoryInfo = fileInfo.IsDirectory ? fileInfo : fileSystem.GetDirectoryInfo(Path.GetDirectoryName(info.Path)); + var directoryInfo = fileInfo.IsDirectory ? fileInfo : fileSystem.GetDirectoryInfo(fileSystem.GetDirectoryName(info.Path)); var directoryPath = directoryInfo.FullName; diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 105e685f4..a3a55176c 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -46,7 +46,6 @@ namespace MediaBrowser.LocalMetadata.Savers "Countries", "CustomRating", "CriticRating", - "CriticRatingSummary", "DeathDate", "DisplayOrder", "EndDate", @@ -72,8 +71,6 @@ namespace MediaBrowser.LocalMetadata.Savers // Deprecated. No longer saving in this field. "MPAARating", - "MPAADescription", - "MusicBrainzArtistId", "MusicBrainzAlbumArtistId", "MusicBrainzAlbumId", @@ -210,7 +207,7 @@ namespace MediaBrowser.LocalMetadata.Savers private void SaveToFile(Stream stream, string path) { - FileSystem.CreateDirectory(Path.GetDirectoryName(path)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); var file = FileSystem.GetFileInfo(path); @@ -309,11 +306,6 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("ContentRating", item.OfficialRating); } - if (!string.IsNullOrEmpty(item.OfficialRatingDescription)) - { - writer.WriteElementString("MPAADescription", item.OfficialRatingDescription); - } - writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G")); writer.WriteElementString("LockData", item.IsLocked.ToString().ToLower()); @@ -333,11 +325,6 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(UsCulture)); } - if (!string.IsNullOrEmpty(item.CriticRatingSummary)) - { - writer.WriteElementString("CriticRatingSummary", item.CriticRatingSummary); - } - if (!string.IsNullOrEmpty(item.Overview)) { writer.WriteElementString("Overview", item.Overview); diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index 05b3ca5fc..566e7946d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -5,8 +5,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; -using System.Threading.Tasks; using MediaBrowser.Model.Diagnostics; namespace MediaBrowser.MediaEncoding.Encoder @@ -19,66 +17,9 @@ namespace MediaBrowser.MediaEncoding.Encoder protected override string GetCommandLineArguments(EncodingJob state) { - var audioTranscodeParams = new List(); - - 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", StringComparison.OrdinalIgnoreCase)) - { - if (state.OutputAudioSampleRate.HasValue) - { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture)); - } - } - var encodingOptions = GetEncodingOptions(); - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - var albumCoverInput = string.Empty; - var mapArgs = string.Empty; - var metadata = string.Empty; - var vn = string.Empty; - - var hasArt = !string.IsNullOrWhiteSpace(state.AlbumCoverPath); - hasArt = false; - - if (hasArt) - { - albumCoverInput = " -i \"" + state.AlbumCoverPath + "\""; - mapArgs = " -map 0:a -map 1:v -c:v copy"; - metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\""; - } - else - { - vn = " -vn"; - } - - var result = string.Format("{0} {1}{6}{7} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{8} -y \"{5}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, GetEncodingOptions()), - threads, - vn, - string.Join(" ", audioTranscodeParams.ToArray()), - state.OutputFilePath, - albumCoverInput, - mapArgs, - metadata).Trim(); - - return result; + return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, state.OutputFilePath); } protected override string GetOutputFileExtension(EncodingJob state) diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index e402c2bac..3672e4e84 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.MediaEncoding.Encoder .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); - FileSystem.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(encodingJob.OutputFilePath)); encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate; @@ -108,7 +108,7 @@ namespace MediaBrowser.MediaEncoding.Encoder Logger.Info(commandLineLogMessage); var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "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. encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index f74dbce98..9d518c431 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public string ItemType { get; set; } - public string AlbumCoverPath { get; set; } - public string GetMimeType(string outputPath) { if (!string.IsNullOrEmpty(MimeType)) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index 498df214f..dc3cb5f5e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -45,6 +45,11 @@ namespace MediaBrowser.MediaEncoding.Encoder /// System.String. private static string GetFileInputArgument(string path) { + if (path.IndexOf("://") != -1) + { + return string.Format("\"{0}\"", path); + } + // Quotes are valid path characters in linux and they need to be escaped here with a leading \ path = NormalizePath(path); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 816f14f82..c58a18b0f 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -49,11 +49,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - /// - /// The FF probe resource pool - /// - private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2); - public string FFMpegPath { get; private set; } public string FFProbePath { get; private set; } @@ -192,18 +187,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - public bool IsDefaultEncoderPath - { - get - { - var path = FFMpegPath; - - var parentPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg", "20160410"); - - return FileSystem.ContainsSubPath(parentPath, path); - } - } - private bool IsSystemInstalledPath(string path) { if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) @@ -227,12 +210,11 @@ namespace MediaBrowser.MediaEncoding.Encoder if (EnableEncoderFontFile) { - var directory = Path.GetDirectoryName(FFMpegPath); + var directory = FileSystem.GetDirectoryName(FFMpegPath); if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) { - await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, - FileSystem).DownloadFonts(directory).ConfigureAwait(false); + await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false); } } } @@ -433,7 +415,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProbePathFromEncoderPath(string appPath) { - return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath)) + return FileSystem.GetFilePaths(FileSystem.GetDirectoryName(appPath)) .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } @@ -591,20 +573,7 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var processWrapper = new ProcessWrapper(process, this, _logger)) { - await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - StartProcess(processWrapper); - } - catch (Exception ex) - { - _ffProbeResourcePool.Release(); - - _logger.ErrorException("Error starting ffprobe", ex); - - throw; - } + StartProcess(processWrapper); try { @@ -655,10 +624,6 @@ namespace MediaBrowser.MediaEncoding.Encoder throw; } - finally - { - _ffProbeResourcePool.Release(); - } } } @@ -739,7 +704,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); - FileSystem.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 231a2ae85..1e983ca15 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -412,7 +412,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } - _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false); @@ -524,8 +524,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!_fileSystem.FileExists(outputPath)) { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, - outputCodec, outputPath, cancellationToken).ConfigureAwait(false); + await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); } } finally @@ -547,7 +546,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } - _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, subtitleStreamIndex, outputCodec, outputPath); diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 9d795cfab..838111a38 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -68,6 +68,8 @@ namespace MediaBrowser.Model.Configuration /// true if [enable case sensitive item ids]; otherwise, false. public bool EnableCaseSensitiveItemIds { get; set; } + public bool DisableLiveTvChannelUserDataName { get; set; } + /// /// Gets or sets the metadata path. /// @@ -162,7 +164,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableAutomaticRestart { get; set; } public bool SkipDeserializationForBasicTypes { get; set; } - public bool SkipDeserializationForPrograms { get; set; } public bool SkipDeserializationForAudio { get; set; } public string ServerName { get; set; } diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index b0760d91f..950d3680d 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -12,7 +12,9 @@ namespace MediaBrowser.Model.Dlna new ResolutionConfiguration(426, 320000), new ResolutionConfiguration(640, 400000), new ResolutionConfiguration(720, 950000), - new ResolutionConfiguration(1280, 2500000) + new ResolutionConfiguration(1280, 2500000), + new ResolutionConfiguration(1920, 4000000), + new ResolutionConfiguration(3840, 35000000) }; public static ResolutionOptions Normalize(int? inputBitrate, @@ -35,19 +37,15 @@ namespace MediaBrowser.Model.Dlna } } - foreach (var config in Configurations) + var resolutionConfig = GetResolutionConfiguration(outputBitrate); + if (resolutionConfig != null) { - if (outputBitrate <= config.MaxBitrate) + var originvalValue = maxWidth; + + maxWidth = Math.Min(resolutionConfig.MaxWidth, maxWidth ?? resolutionConfig.MaxWidth); + if (!originvalValue.HasValue || originvalValue.Value != maxWidth.Value) { - var originvalValue = maxWidth; - - maxWidth = Math.Min(config.MaxWidth, maxWidth ?? config.MaxWidth); - if (!originvalValue.HasValue || originvalValue.Value != maxWidth.Value) - { - maxHeight = null; - } - - break; + maxHeight = null; } } @@ -58,6 +56,19 @@ namespace MediaBrowser.Model.Dlna }; } + private static ResolutionConfiguration GetResolutionConfiguration(int outputBitrate) + { + foreach (var config in Configurations) + { + if (outputBitrate <= config.MaxBitrate) + { + return config; + } + } + + return null; + } + private static double GetVideoBitrateScaleFactor(string codec) { if (StringHelper.EqualsIgnoreCase(codec, "h265") || diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index ae7d13868..1a752892e 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -165,12 +165,6 @@ namespace MediaBrowser.Model.Dto public string[] ProductionLocations { get; set; } - /// - /// Gets or sets the critic rating summary. - /// - /// The critic rating summary. - public string CriticRatingSummary { get; set; } - public List MultiPartGameFiles { get; set; } /// diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 131583024..d0655d90b 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -29,6 +29,8 @@ namespace MediaBrowser.Model.Dto public string ETag { get; set; } public long? RunTimeTicks { get; set; } public bool ReadAtNativeFramerate { get; set; } + public bool IgnoreDts { get; set; } + public bool IgnoreIndex { get; set; } public bool SupportsTranscoding { get; set; } public bool SupportsDirectStream { get; set; } public bool SupportsDirectPlay { get; set; } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 6773acbfa..26de9332e 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -146,6 +146,8 @@ namespace MediaBrowser.Model.IO /// System.String. string NormalizePath(string path); + string GetDirectoryName(string path); + /// /// Gets the file name without extension. /// diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 0caf64538..75ba09b60 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -42,11 +42,6 @@ ChildCount, - /// - /// The critic rating summary - /// - CriticRatingSummary, - /// /// The cumulative run time ticks /// @@ -122,8 +117,6 @@ /// MediaSources, - OfficialRatingDescription, - OriginalTitle, /// diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index cea15a2a7..3ca0eafe6 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,4 +1,6 @@ -namespace MediaBrowser.Model.Search +using System; + +namespace MediaBrowser.Model.Search { /// /// Class SearchHintResult @@ -94,13 +96,18 @@ /// /// The display type of the media. public string DisplayMediaType { get; set; } - + + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + /// /// Gets or sets the series. /// /// The series. public string Series { get; set; } + public string Status { get; set; } + /// /// Gets or sets the album. /// diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 678dfd39d..897a9db52 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -34,6 +34,17 @@ namespace MediaBrowser.Model.Search public bool IncludeArtists { get; set; } public string[] IncludeItemTypes { get; set; } + public string ParentId { get; set; } + + public bool? IsMovie { get; set; } + + public bool? IsSeries { get; set; } + + public bool? IsNews { get; set; } + + public bool? IsKids { get; set; } + + public bool? IsSports { get; set; } public SearchQuery() { diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs index 6cceb162a..1a9651559 100644 --- a/MediaBrowser.Model/Session/GeneralCommandType.cs +++ b/MediaBrowser.Model/Session/GeneralCommandType.cs @@ -37,6 +37,7 @@ SetRepeatMode = 29, ChannelUp = 30, ChannelDown = 31, - SetMaxStreamingBitrate = 31 + SetMaxStreamingBitrate = 31, + Guide = 32 } } \ No newline at end of file diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 3917b1662..2a987ceb1 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -50,7 +50,6 @@ namespace MediaBrowser.Model.Users /// Gets or sets a value indicating whether [enable synchronize]. /// /// true if [enable synchronize]; otherwise, false. - public bool EnableSync { get; set; } public bool EnableSyncTranscoding { get; set; } public string[] EnabledDevices { get; set; } @@ -71,7 +70,6 @@ namespace MediaBrowser.Model.Users public UserPolicy() { - EnableSync = true; EnableSyncTranscoding = true; EnableMediaPlayback = true; diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index 547420092..6175c3622 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.BoxSets var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); - _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(dataFilePath)); _json.SerializeToFile(mainResult, dataFilePath); } diff --git a/MediaBrowser.Providers/ImagesByName/ImageUtils.cs b/MediaBrowser.Providers/ImagesByName/ImageUtils.cs index bbcbbda90..566378dd0 100644 --- a/MediaBrowser.Providers/ImagesByName/ImageUtils.cs +++ b/MediaBrowser.Providers/ImagesByName/ImageUtils.cs @@ -6,8 +6,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.ImagesByName @@ -21,36 +19,37 @@ namespace MediaBrowser.Providers.ImagesByName /// The file. /// The HTTP client. /// The file system. - /// The semaphore. /// The cancellation token. /// Task. - public static async Task EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, SemaphoreSlim semaphore, CancellationToken cancellationToken) + public static async Task EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, CancellationToken cancellationToken) { var fileInfo = fileSystem.GetFileInfo(file); if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) { - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + var temp = await httpClient.GetTempFile(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Progress = new Progress(), + Url = url + + }).ConfigureAwait(false); + + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(file)); try { - var temp = await httpClient.GetTempFile(new HttpRequestOptions - { - CancellationToken = cancellationToken, - Progress = new Progress(), - Url = url - - }).ConfigureAwait(false); - - fileSystem.CreateDirectory(Path.GetDirectoryName(file)); - - fileSystem.CopyFile(temp, file, true); + fileSystem.CopyFile(temp, file, true); } - finally + catch { - semaphore.Release(); + } + + return temp; } + + return file; } public static string FindMatch(IHasImages item, IEnumerable images) diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs new file mode 100644 index 000000000..03bb0f68c --- /dev/null +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs @@ -0,0 +1,429 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A copy of StablePriorityQueue which also has generic priority-type + /// + /// The values in the queue. Must extend the GenericPriorityQueue class + /// The priority-type. Must extend IComparable<TPriority> + public sealed class GenericPriorityQueue : IFixedSizePriorityQueue + where TItem : GenericPriorityQueueNode + where TPriority : IComparable + { + private int _numNodes; + private TItem[] _nodes; + private long _numNodesEverEnqueued; + + /// + /// Instantiate a new Priority Queue + /// + /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) + public GenericPriorityQueue(int maxNodes) + { +#if DEBUG + if (maxNodes <= 0) + { + throw new InvalidOperationException("New queue size cannot be smaller than 1"); + } +#endif + + _numNodes = 0; + _nodes = new TItem[maxNodes + 1]; + _numNodesEverEnqueued = 0; + } + + /// + /// Returns the number of nodes in the queue. + /// O(1) + /// + public int Count + { + get + { + return _numNodes; + } + } + + /// + /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), + /// attempting to enqueue another item will cause undefined behavior. O(1) + /// + public int MaxSize + { + get + { + return _nodes.Length - 1; + } + } + + /// + /// Removes every node from the queue. + /// O(n) (So, don't do this often!) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Clear() + { + Array.Clear(_nodes, 1, _numNodes); + _numNodes = 0; + } + + /// + /// Returns (in O(1)!) whether the given node is in the queue. O(1) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public bool Contains(TItem node) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) + { + throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?"); + } +#endif + + return (_nodes[node.QueueIndex] == node); + } + + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// If the queue is full, the result is undefined. + /// If the node is already enqueued, the result is undefined. + /// O(log n) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Enqueue(TItem node, TPriority priority) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (_numNodes >= _nodes.Length - 1) + { + throw new InvalidOperationException("Queue is full - node cannot be added: " + node); + } + if (Contains(node)) + { + throw new InvalidOperationException("Node is already enqueued: " + node); + } +#endif + + node.Priority = priority; + _numNodes++; + _nodes[_numNodes] = node; + node.QueueIndex = _numNodes; + node.InsertionIndex = _numNodesEverEnqueued++; + CascadeUp(_nodes[_numNodes]); + } + +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void Swap(TItem node1, TItem node2) + { + //Swap the nodes + _nodes[node1.QueueIndex] = node2; + _nodes[node2.QueueIndex] = node1; + + //Swap their indicies + int temp = node1.QueueIndex; + node1.QueueIndex = node2.QueueIndex; + node2.QueueIndex = temp; + } + + //Performance appears to be slightly better when this is NOT inlined o_O + private void CascadeUp(TItem node) + { + //aka Heapify-up + int parent = node.QueueIndex / 2; + while (parent >= 1) + { + TItem parentNode = _nodes[parent]; + if (HasHigherPriority(parentNode, node)) + break; + + //Node has lower priority value, so move it up the heap + Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown() + + parent = node.QueueIndex / 2; + } + } + +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CascadeDown(TItem node) + { + //aka Heapify-down + TItem newParent; + int finalQueueIndex = node.QueueIndex; + while (true) + { + newParent = node; + int childLeftIndex = 2 * finalQueueIndex; + + //Check if the left-child is higher-priority than the current node + if (childLeftIndex > _numNodes) + { + //This could be placed outside the loop, but then we'd have to check newParent != node twice + node.QueueIndex = finalQueueIndex; + _nodes[finalQueueIndex] = node; + break; + } + + TItem childLeft = _nodes[childLeftIndex]; + if (HasHigherPriority(childLeft, newParent)) + { + newParent = childLeft; + } + + //Check if the right-child is higher-priority than either the current node or the left child + int childRightIndex = childLeftIndex + 1; + if (childRightIndex <= _numNodes) + { + TItem childRight = _nodes[childRightIndex]; + if (HasHigherPriority(childRight, newParent)) + { + newParent = childRight; + } + } + + //If either of the children has higher (smaller) priority, swap and continue cascading + if (newParent != node) + { + //Move new parent to its new index. node will be moved once, at the end + //Doing it this way is one less assignment operation than calling Swap() + _nodes[finalQueueIndex] = newParent; + + int temp = newParent.QueueIndex; + newParent.QueueIndex = finalQueueIndex; + finalQueueIndex = temp; + } + else + { + //See note above + node.QueueIndex = finalQueueIndex; + _nodes[finalQueueIndex] = node; + break; + } + } + } + + /// + /// Returns true if 'higher' has higher priority than 'lower', false otherwise. + /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private bool HasHigherPriority(TItem higher, TItem lower) + { + var cmp = higher.Priority.CompareTo(lower.Priority); + return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); + } + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// If queue is empty, result is undefined + /// O(log n) + /// + public bool TryDequeue(out TItem item) + { + if (_numNodes <= 0) + { + item = default(TItem); + return false; + } + +#if DEBUG + + if (!IsValidQueue()) + { + throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + + "Or add the same node to two different queues?)"); + } +#endif + + TItem returnMe = _nodes[1]; + Remove(returnMe); + item = returnMe; + return true; + } + + /// + /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. + /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior + /// O(n) + /// + public void Resize(int maxNodes) + { +#if DEBUG + if (maxNodes <= 0) + { + throw new InvalidOperationException("Queue size cannot be smaller than 1"); + } + + if (maxNodes < _numNodes) + { + throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); + } +#endif + + TItem[] newArray = new TItem[maxNodes + 1]; + int highestIndexToCopy = Math.Min(maxNodes, _numNodes); + for (int i = 1; i <= highestIndexToCopy; i++) + { + newArray[i] = _nodes[i]; + } + _nodes = newArray; + } + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// If the queue is empty, behavior is undefined. + /// O(1) + /// + public TItem First + { + get + { +#if DEBUG + if (_numNodes <= 0) + { + throw new InvalidOperationException("Cannot call .First on an empty queue"); + } +#endif + + return _nodes[1]; + } + } + + /// + /// This method must be called on a node every time its priority changes while it is in the queue. + /// Forgetting to call this method will result in a corrupted queue! + /// Calling this method on a node not in the queue results in undefined behavior + /// O(log n) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void UpdatePriority(TItem node, TPriority priority) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (!Contains(node)) + { + throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); + } +#endif + + node.Priority = priority; + OnNodeUpdated(node); + } + + private void OnNodeUpdated(TItem node) + { + //Bubble the updated node up or down as appropriate + int parentIndex = node.QueueIndex / 2; + TItem parentNode = _nodes[parentIndex]; + + if (parentIndex > 0 && HasHigherPriority(node, parentNode)) + { + CascadeUp(node); + } + else + { + //Note that CascadeDown will be called if parentNode == node (that is, node is the root) + CascadeDown(node); + } + } + + /// + /// Removes a node from the queue. The node does not need to be the head of the queue. + /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first + /// O(log n) + /// + public void Remove(TItem node) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (!Contains(node)) + { + throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); + } +#endif + + //If the node is already the last node, we can remove it immediately + if (node.QueueIndex == _numNodes) + { + _nodes[_numNodes] = null; + _numNodes--; + return; + } + + //Swap the node with the last node + TItem formerLastNode = _nodes[_numNodes]; + Swap(node, formerLastNode); + _nodes[_numNodes] = null; + _numNodes--; + + //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate + OnNodeUpdated(formerLastNode); + } + + public IEnumerator GetEnumerator() + { + for (int i = 1; i <= _numNodes; i++) + yield return _nodes[i]; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Should not be called in production code. + /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. + /// + public bool IsValidQueue() + { + for (int i = 1; i < _nodes.Length; i++) + { + if (_nodes[i] != null) + { + int childLeftIndex = 2 * i; + if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) + return false; + + int childRightIndex = childLeftIndex + 1; + if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) + return false; + } + } + return true; + } + } +} diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs new file mode 100644 index 000000000..e6e93e443 --- /dev/null +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + public class GenericPriorityQueueNode + { + /// + /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue (ideally just once, in the node's constructor). + /// Should not be manually edited once the node has been enqueued - use queue.UpdatePriority() instead + /// + public TPriority Priority { get; protected internal set; } + + /// + /// Represents the current position in the queue + /// + public int QueueIndex { get; internal set; } + + /// + /// Represents the order the node was inserted in + /// + public long InsertionIndex { get; internal set; } + } +} diff --git a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs new file mode 100644 index 000000000..8da88e1c6 --- /dev/null +++ b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A helper-interface only needed to make writing unit tests a bit easier (hence the 'internal' access modifier) + /// + internal interface IFixedSizePriorityQueue : IPriorityQueue + where TPriority : IComparable + { + /// + /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. + /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior + /// + void Resize(int maxNodes); + + /// + /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), + /// attempting to enqueue another item will cause undefined behavior. + /// + int MaxSize { get; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Providers/Manager/IPriorityQueue.cs b/MediaBrowser.Providers/Manager/IPriorityQueue.cs new file mode 100644 index 000000000..425992b18 --- /dev/null +++ b/MediaBrowser.Providers/Manager/IPriorityQueue.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// The IPriorityQueue interface. This is mainly here for purists, and in case I decide to add more implementations later. + /// For speed purposes, it is actually recommended that you *don't* access the priority queue through this interface, since the JIT can + /// (theoretically?) optimize method calls from concrete-types slightly better. + /// + public interface IPriorityQueue : IEnumerable + where TPriority : IComparable + { + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// See implementation for how duplicates are handled. + /// + void Enqueue(TItem node, TPriority priority); + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// + bool TryDequeue(out TItem item); + + /// + /// Removes every node from the queue. + /// + void Clear(); + + /// + /// Returns whether the given node is in the queue. + /// + bool Contains(TItem node); + + /// + /// Removes a node from the queue. The node does not need to be the head of the queue. + /// + void Remove(TItem node); + + /// + /// Call this method to change the priority of a node. + /// + void UpdatePriority(TItem node, TPriority priority); + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// + TItem First { get; } + + /// + /// Returns the number of nodes in the queue. + /// + int Count { get; } + } +} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 7c797133f..1d8275c26 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -238,7 +238,7 @@ namespace MediaBrowser.Providers.Manager { _logger.Debug("Saving image to {0}", path); - var parentFolder = Path.GetDirectoryName(path); + var parentFolder = _fileSystem.GetDirectoryName(path); await _imageSaveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -247,7 +247,7 @@ namespace MediaBrowser.Providers.Manager _libraryMonitor.ReportFileSystemChangeBeginning(path); _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); // If the file is currently hidden we'll have to remove that or the save will fail var file = _fileSystem.GetFileInfo(path); @@ -449,7 +449,7 @@ namespace MediaBrowser.Providers.Manager { if (type == ImageType.Primary && item is Episode) { - path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); + path = Path.Combine(_fileSystem.GetDirectoryName(item.Path), "metadata", filename + extension); } else if (item.DetectIsInMixedFolder()) @@ -581,7 +581,7 @@ namespace MediaBrowser.Providers.Manager if (item is Episode) { - var seasonFolder = Path.GetDirectoryName(item.Path); + var seasonFolder = _fileSystem.GetDirectoryName(item.Path); var imageFilename = _fileSystem.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; @@ -629,7 +629,7 @@ namespace MediaBrowser.Providers.Manager { imageFilename = "poster"; } - var folder = Path.GetDirectoryName(item.Path); + var folder = _fileSystem.GetDirectoryName(item.Path); return Path.Combine(folder, _fileSystem.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 0b8dca2eb..7ff018c7b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -24,6 +24,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; +using Priority_Queue; namespace MediaBrowser.Providers.Manager { @@ -577,7 +578,6 @@ namespace MediaBrowser.Providers.Manager return SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase))); } - private readonly SemaphoreSlim _saveLock = new SemaphoreSlim(1, 1); /// /// Saves the metadata. /// @@ -607,8 +607,6 @@ namespace MediaBrowser.Providers.Manager continue; } - await _saveLock.WaitAsync().ConfigureAwait(false); - try { _libraryMonitor.ReportFileSystemChangeBeginning(path); @@ -620,7 +618,6 @@ namespace MediaBrowser.Providers.Manager } finally { - _saveLock.Release(); _libraryMonitor.ReportFileSystemChangeComplete(path, false); } } @@ -851,20 +848,20 @@ namespace MediaBrowser.Providers.Manager }); } - private readonly ConcurrentQueue> _refreshQueue = - new ConcurrentQueue>(); + private readonly SimplePriorityQueue> _refreshQueue = + new SimplePriorityQueue>(); private readonly object _refreshQueueLock = new object(); private bool _isProcessingRefreshQueue; - public void QueueRefresh(Guid id, MetadataRefreshOptions options) + public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority) { if (_disposed) { return; } - _refreshQueue.Enqueue(new Tuple(id, options)); + _refreshQueue.Enqueue(new Tuple(id, options), (int)priority); lock (_refreshQueueLock) { diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 34cf63350..f544c09dc 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -4,6 +4,8 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Extensions; namespace MediaBrowser.Providers.Manager { @@ -89,11 +91,6 @@ namespace MediaBrowser.Providers.Manager } } - if (replaceData || string.IsNullOrEmpty(target.OfficialRatingDescription)) - { - target.OfficialRatingDescription = source.OfficialRatingDescription; - } - if (replaceData || string.IsNullOrEmpty(target.CustomRating)) { target.CustomRating = source.CustomRating; @@ -122,6 +119,11 @@ namespace MediaBrowser.Providers.Manager if (replaceData || targetResult.People == null || targetResult.People.Count == 0) { targetResult.People = sourceResult.People; + + } + else if (targetResult.People != null && sourceResult.People != null) + { + MergePeople(sourceResult.People, targetResult.People); } } @@ -205,6 +207,31 @@ namespace MediaBrowser.Providers.Manager } } + private static void MergePeople(List source, List target) + { + foreach (var person in target) + { + var normalizedName = person.Name.RemoveDiacritics(); + var personInSource = source.FirstOrDefault(i => string.Equals(i.Name.RemoveDiacritics(), normalizedName, StringComparison.OrdinalIgnoreCase)); + + if (personInSource != null) + { + foreach (var providerId in personInSource.ProviderIds) + { + if (!person.ProviderIds.ContainsKey(providerId.Key)) + { + person.ProviderIds[providerId.Key] = providerId.Value; + } + } + + if (string.IsNullOrWhiteSpace(person.ImageUrl)) + { + person.ImageUrl = personInSource.ImageUrl; + } + } + } + } + public static void MergeMetadataSettings(BaseItem source, BaseItem target) { @@ -265,11 +292,6 @@ namespace MediaBrowser.Providers.Manager { target.CriticRating = source.CriticRating; } - - if (replaceData || string.IsNullOrEmpty(target.CriticRatingSummary)) - { - target.CriticRatingSummary = source.CriticRatingSummary; - } } private static void MergeTrailers(BaseItem source, BaseItem target, List lockedFields, bool replaceData) diff --git a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs new file mode 100644 index 000000000..f4c261a81 --- /dev/null +++ b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than + /// FastPriorityQueue + /// + /// The type to enqueue + /// The priority-type to use for nodes. Must extend IComparable<TPriority> + public class SimplePriorityQueue : IPriorityQueue + where TPriority : IComparable + { + private class SimpleNode : GenericPriorityQueueNode + { + public TItem Data { get; private set; } + + public SimpleNode(TItem data) + { + Data = data; + } + } + + private const int INITIAL_QUEUE_SIZE = 10; + private readonly GenericPriorityQueue _queue; + + public SimplePriorityQueue() + { + _queue = new GenericPriorityQueue(INITIAL_QUEUE_SIZE); + } + + /// + /// Given an item of type T, returns the exist SimpleNode in the queue + /// + private SimpleNode GetExistingNode(TItem item) + { + var comparer = EqualityComparer.Default; + foreach (var node in _queue) + { + if (comparer.Equals(node.Data, item)) + { + return node; + } + } + throw new InvalidOperationException("Item cannot be found in queue: " + item); + } + + /// + /// Returns the number of nodes in the queue. + /// O(1) + /// + public int Count + { + get + { + lock (_queue) + { + return _queue.Count; + } + } + } + + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// Throws an exception when the queue is empty. + /// O(1) + /// + public TItem First + { + get + { + lock (_queue) + { + if (_queue.Count <= 0) + { + throw new InvalidOperationException("Cannot call .First on an empty queue"); + } + + SimpleNode first = _queue.First; + return (first != null ? first.Data : default(TItem)); + } + } + } + + /// + /// Removes every node from the queue. + /// O(n) + /// + public void Clear() + { + lock (_queue) + { + _queue.Clear(); + } + } + + /// + /// Returns whether the given item is in the queue. + /// O(n) + /// + public bool Contains(TItem item) + { + lock (_queue) + { + var comparer = EqualityComparer.Default; + foreach (var node in _queue) + { + if (comparer.Equals(node.Data, item)) + { + return true; + } + } + return false; + } + } + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// If queue is empty, throws an exception + /// O(log n) + /// + public bool TryDequeue(out TItem item) + { + lock (_queue) + { + if (_queue.Count <= 0) + { + item = default(TItem); + return false; + } + + SimpleNode node; + if (_queue.TryDequeue(out node)) + { + item = node.Data; + return true; + } + + item = default(TItem); + return false; + } + } + + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'. + /// Duplicates are allowed. + /// O(log n) + /// + public void Enqueue(TItem item, TPriority priority) + { + lock (_queue) + { + SimpleNode node = new SimpleNode(item); + if (_queue.Count == _queue.MaxSize) + { + _queue.Resize(_queue.MaxSize * 2 + 1); + } + _queue.Enqueue(node, priority); + } + } + + /// + /// Removes an item from the queue. The item does not need to be the head of the queue. + /// If the item is not in the queue, an exception is thrown. If unsure, check Contains() first. + /// If multiple copies of the item are enqueued, only the first one is removed. + /// O(n) + /// + public void Remove(TItem item) + { + lock (_queue) + { + try + { + _queue.Remove(GetExistingNode(item)); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex); + } + } + } + + /// + /// Call this method to change the priority of an item. + /// Calling this method on a item not in the queue will throw an exception. + /// If the item is enqueued multiple times, only the first one will be updated. + /// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able + /// to update all of them, please wrap your items in a wrapper class so they can be distinguished). + /// O(n) + /// + public void UpdatePriority(TItem item, TPriority priority) + { + lock (_queue) + { + try + { + SimpleNode updateMe = GetExistingNode(item); + _queue.UpdatePriority(updateMe, priority); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item, ex); + } + } + } + + public IEnumerator GetEnumerator() + { + List queueData = new List(); + lock (_queue) + { + //Copy to a separate list because we don't want to 'yield return' inside a lock + foreach (var node in _queue) + { + queueData.Add(node.Data); + } + } + + return queueData.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool IsValidQueue() + { + lock (_queue) + { + return _queue.IsValidQueue(); + } + } + } + + /// + /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than + /// FastPriorityQueue + /// This class is kept here for backwards compatibility. It's recommended you use Simple + /// + /// The type to enqueue + public class SimplePriorityQueue : SimplePriorityQueue { } +} \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index fe554545f..9d20ec423 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -65,10 +65,15 @@ + + + + + diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index d65084287..cc5e7aef9 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!_fileSystem.FileExists(path)) { - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index afcf4b226..04e549526 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.MediaInfo }, cancellationToken).ConfigureAwait(false); - //Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); + //Directory.CreateDirectory(_fileSystem.GetDirectoryName(cachePath)); //_json.SerializeToFile(result, cachePath); return result; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 0aaa56bab..c686e1d2e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -150,7 +150,7 @@ namespace MediaBrowser.Providers.MediaInfo }, cancellationToken).ConfigureAwait(false); - //Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); + //Directory.CreateDirectory(_fileSystem.GetDirectoryName(cachePath)); //_json.SerializeToFile(result, cachePath); return result; @@ -356,11 +356,6 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (!string.IsNullOrWhiteSpace(data.OfficialRatingDescription) || isFullRefresh) - { - video.OfficialRatingDescription = data.OfficialRatingDescription; - } - if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Genres)) { if (video.Genres.Count == 0 || isFullRefresh) diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index dd2cad1f9..84d8d20ad 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -277,14 +277,13 @@ namespace MediaBrowser.Providers.Movies var path = GetFanartJsonPath(id); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); try { using (var response = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = FanartArtistProvider.Current.FanArtResourcePool, CancellationToken = cancellationToken, BufferContent = true diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index 0101478b8..670215479 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.Movies tmdbId = movieInfo.id.ToString(_usCulture); dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language); - _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(dataFilePath)); _jsonSerializer.SerializeToFile(movieInfo, dataFilePath); } } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 8e4b86519..d6aef5d1c 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -30,8 +30,6 @@ namespace MediaBrowser.Providers.Movies /// public class MovieDbProvider : IRemoteMetadataProvider, IDisposable, IHasOrder { - internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1); - internal static MovieDbProvider Current { get; private set; } private readonly IJsonSerializer _jsonSerializer; @@ -137,10 +135,6 @@ namespace MediaBrowser.Providers.Movies /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - if (dispose) - { - MovieDbResourcePool.Dispose(); - } } /// @@ -214,7 +208,7 @@ namespace MediaBrowser.Providers.Movies var dataFilePath = GetDataFilePath(id, preferredMetadataLanguage); - _fileSystem.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(dataFilePath)); _jsonSerializer.SerializeToFile(mainResult, dataFilePath); } @@ -431,7 +425,6 @@ namespace MediaBrowser.Providers.Movies await Task.Delay(Convert.ToInt32(delayMs)).ConfigureAwait(false); } - options.ResourcePool = MovieDbResourcePool; _lastRequestTicks = DateTime.UtcNow.Ticks; options.BufferContent = true; diff --git a/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs index 7abe9cc07..0cb1a7ff1 100644 --- a/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs @@ -20,8 +20,7 @@ namespace MediaBrowser.Providers.Music { var album = (MusicAlbum)item; - var image = album.GetRecursiveChildren() - .OfType