aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library
diff options
context:
space:
mode:
authorWWWesten <4700006+WWWesten@users.noreply.github.com>2021-11-01 23:43:29 +0500
committerGitHub <noreply@github.com>2021-11-01 23:43:29 +0500
commit0a14279e2a21bcb9654a06a2d49e1e4f0cc5329c (patch)
treee1b1bd603b011ca98e5793e356326bf4a35a7050 /Emby.Server.Implementations/Library
parentf2817fef743eeb75a00782ceea363b2d3e7dc9f2 (diff)
parent76eeb8f655424d295e73ced8349c6fefee6ddb12 (diff)
Merge branch 'jellyfin:master' into master
Diffstat (limited to 'Emby.Server.Implementations/Library')
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs120
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs104
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs35
-rw-r--r--Emby.Server.Implementations/Library/IgnorePatterns.cs123
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs1414
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs50
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs272
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs51
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs39
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs98
-rw-r--r--Emby.Server.Implementations/Library/ResolverHelper.cs64
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs39
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs139
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs45
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs97
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs35
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs26
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs27
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs (renamed from Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs)4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs20
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs11
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs353
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs27
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs68
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs69
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs12
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs44
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs41
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs81
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs71
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs100
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs1220
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs101
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs13
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs30
-rw-r--r--Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs15
-rw-r--r--Emby.Server.Implementations/Library/Validators/GenresValidator.cs19
-rw-r--r--Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs14
-rw-r--r--Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs19
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs33
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs15
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosValidator.cs37
42 files changed, 2134 insertions, 3061 deletions
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index c644d13ea..bc5b4499f 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,126 +1,62 @@
using System;
using System.IO;
-using System.Linq;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Provides the core resolver ignore rules
+ /// Provides the core resolver ignore rules.
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
private readonly ILibraryManager _libraryManager;
-
- private bool _ignoreDotPrefix;
+ private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary>
- /// Any folder named in this list will be ignored - can be added to at runtime for extensibility
+ /// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary>
- public static readonly string[] IgnoreFolders =
- {
- "metadata",
- "ps3_update",
- "ps3_vprm",
- "extrafanart",
- "extrathumbs",
- ".actors",
- ".wd_tv",
-
- // Synology
- "@eaDir",
- "eaDir",
- "#recycle",
-
- // Qnap
- "@Recycle",
- ".@__thumb",
- "$RECYCLE.BIN",
- "System Volume Information",
- ".grab",
-
- // macos
- ".AppleDouble"
-
- };
-
- public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="serverApplicationPaths">The server application paths.</param>
+ public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
{
_libraryManager = libraryManager;
-
- _ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
+ _serverApplicationPaths = serverApplicationPaths;
}
- /// <summary>
- /// Shoulds the ignore.
- /// </summary>
- /// <param name="fileInfo">The file information.</param>
- /// <param name="parent">The parent.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
{
+ // Don't ignore application folders
+ if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))
+ {
+ return false;
+ }
+
// Don't ignore top level folders
if (fileInfo.IsDirectory && parent is AggregateFolder)
{
return false;
}
- var filename = fileInfo.Name;
- var path = fileInfo.FullName;
-
- // Handle mac .DS_Store
- // https://github.com/MediaBrowser/MediaBrowser/issues/427
- if (_ignoreDotPrefix)
+ if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
{
- if (filename.IndexOf('.') == 0)
- {
- return true;
- }
+ return true;
}
- // Ignore hidden files and folders
- //if (fileInfo.IsHidden)
- //{
- // if (parent == null)
- // {
- // var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path));
-
- // if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
- // {
- // return false;
- // }
- // if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
- // {
- // return false;
- // }
- // }
-
- // // Sometimes these are marked hidden
- // if (_fileSystem.IsRootPath(path))
- // {
- // return false;
- // }
-
- // return true;
- //}
+ var filename = fileInfo.Name;
if (fileInfo.IsDirectory)
{
- // Ignore any folders in our list
- if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
-
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
- if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
- !(parent is AggregateFolder) && !(parent is UserRootFolder))
+ if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
+ && !(parent is AggregateFolder)
+ && !(parent is UserRootFolder))
{
return true;
}
@@ -141,22 +77,12 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// Don't resolve these into audio files
- if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
+ if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
+ && _libraryManager.IsAudioFile(filename))
{
return true;
}
}
-
- // Ignore samples
- var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("-", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("_", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("!", " ", StringComparison.OrdinalIgnoreCase);
-
- if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
}
return false;
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
deleted file mode 100644
index 4013ac0c8..000000000
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Cryptography;
-
-namespace Emby.Server.Implementations.Library
-{
- public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
- {
- private readonly ICryptoProvider _cryptographyProvider;
- public DefaultAuthenticationProvider(ICryptoProvider crypto)
- {
- _cryptographyProvider = crypto;
- }
-
- public string Name => "Default";
-
- public bool IsEnabled => true;
-
- public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
- {
- throw new NotImplementedException();
- }
-
- public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
- {
- if (resolvedUser == null)
- {
- throw new Exception("Invalid username or password");
- }
-
- var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
-
- if (!success)
- {
- throw new Exception("Invalid username or password");
- }
-
- return Task.FromResult(new ProviderAuthenticationResult
- {
- Username = username
- });
- }
-
- public Task<bool> HasPassword(User user)
- {
- var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
- return Task.FromResult(hasConfiguredPassword);
- }
-
- private bool IsPasswordEmpty(User user, string passwordHash)
- {
- return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase);
- }
-
- public Task ChangePassword(User user, string newPassword)
- {
- string newPasswordHash = null;
-
- if (newPassword != null)
- {
- newPasswordHash = GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.Password = newPasswordHash;
-
- return Task.CompletedTask;
- }
-
- public string GetPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.Password)
- ? GetEmptyHashedString(user)
- : user.Password;
- }
-
- public string GetEmptyHashedString(User user)
- {
- return GetHashedString(user, string.Empty);
- }
-
- /// <summary>
- /// Gets the hashed string.
- /// </summary>
- public string GetHashedString(User user, string str)
- {
- var salt = user.Salt;
- if (salt != null)
- {
- // return BCrypt.HashPassword(str, salt);
- }
-
- // legacy
- return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 45a33a296..868071a99 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -1,4 +1,10 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
+using System.Globalization;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
@@ -8,30 +14,37 @@ namespace Emby.Server.Implementations.Library
{
public class ExclusiveLiveStream : ILiveStream
{
+ private readonly Func<Task> _closeFn;
+
+ public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
+ {
+ MediaSource = mediaSource;
+ EnableStreamSharing = false;
+ _closeFn = closeFn;
+ ConsumerCount = 1;
+ UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ }
+
public int ConsumerCount { get; set; }
+
public string OriginalStreamId { get; set; }
public string TunerHostId => null;
public bool EnableStreamSharing { get; set; }
- public MediaSourceInfo MediaSource { get; set; }
- public string UniqueId { get; private set; }
+ public MediaSourceInfo MediaSource { get; set; }
- private Func<Task> _closeFn;
+ public string UniqueId { get; }
- public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
+ public Task Close()
{
- MediaSource = mediaSource;
- EnableStreamSharing = false;
- _closeFn = closeFn;
- ConsumerCount = 1;
- UniqueId = Guid.NewGuid().ToString("N");
+ return _closeFn();
}
- public Task Close()
+ public Stream GetStream()
{
- return _closeFn();
+ throw new NotSupportedException();
}
public Task Open(CancellationToken openCancellationToken)
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs
new file mode 100644
index 000000000..5384c04b3
--- /dev/null
+++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Linq;
+using DotNet.Globbing;
+
+namespace Emby.Server.Implementations.Library
+{
+ /// <summary>
+ /// Glob patterns for files to ignore.
+ /// </summary>
+ public static class IgnorePatterns
+ {
+ /// <summary>
+ /// Files matching these glob patterns will be ignored.
+ /// </summary>
+ private static readonly string[] _patterns =
+ {
+ "**/small.jpg",
+ "**/albumart.jpg",
+
+ // We have neither non-greedy matching or character group repetitions, working around that here.
+ // https://github.com/dazinator/DotNet.Glob#patterns
+ // .*/sample\..{1,5}
+ "**/sample.?",
+ "**/sample.??",
+ "**/sample.???", // Matches sample.mkv
+ "**/sample.????", // Matches sample.webm
+ "**/sample.?????",
+ "**/*.sample.?",
+ "**/*.sample.??",
+ "**/*.sample.???",
+ "**/*.sample.????",
+ "**/*.sample.?????",
+ "**/sample/*",
+
+ // Directories
+ "**/metadata/**",
+ "**/metadata",
+ "**/ps3_update/**",
+ "**/ps3_update",
+ "**/ps3_vprm/**",
+ "**/ps3_vprm",
+ "**/extrafanart/**",
+ "**/extrafanart",
+ "**/extrathumbs/**",
+ "**/extrathumbs",
+ "**/.actors/**",
+ "**/.actors",
+ "**/.wd_tv/**",
+ "**/.wd_tv",
+ "**/lost+found/**",
+ "**/lost+found",
+
+ // WMC temp recording directories that will constantly be written to
+ "**/TempRec/**",
+ "**/TempRec",
+ "**/TempSBE/**",
+ "**/TempSBE",
+
+ // Synology
+ "**/eaDir/**",
+ "**/eaDir",
+ "**/@eaDir/**",
+ "**/@eaDir",
+ "**/#recycle/**",
+ "**/#recycle",
+
+ // Qnap
+ "**/@Recycle/**",
+ "**/@Recycle",
+ "**/.@__thumb/**",
+ "**/.@__thumb",
+ "**/$RECYCLE.BIN/**",
+ "**/$RECYCLE.BIN",
+ "**/System Volume Information/**",
+ "**/System Volume Information",
+ "**/.grab/**",
+ "**/.grab",
+
+ // Unix hidden files
+ "**/.*",
+
+ // Mac - if you ever remove the above.
+ // "**/._*",
+ // "**/.DS_Store",
+
+ // thumbs.db
+ "**/thumbs.db",
+
+ // bts sync files
+ "**/*.bts",
+ "**/*.sync",
+ };
+
+ private static readonly GlobOptions _globOptions = new GlobOptions
+ {
+ Evaluation =
+ {
+ CaseInsensitive = true
+ }
+ };
+
+ private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
+
+ /// <summary>
+ /// Returns true if the supplied path should be ignored.
+ /// </summary>
+ /// <param name="path">The path to test.</param>
+ /// <returns>Whether to ignore the path.</returns>
+ public static bool ShouldIgnore(ReadOnlySpan<char> path)
+ {
+ int len = _globs.Length;
+ for (int i = 0; i < len; i++)
+ {
+ if (_globs[i].IsMatch(path))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 3c2272b56..1326f60fe 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1,10 +1,14 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio;
@@ -15,78 +19,138 @@ using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
-using SortOrder = MediaBrowser.Model.Entities.SortOrder;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
using VideoResolver = Emby.Naming.Video.VideoResolver;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Class LibraryManager
+ /// Class LibraryManager.
/// </summary>
public class LibraryManager : ILibraryManager
{
- /// <summary>
- /// Gets or sets the postscan tasks.
- /// </summary>
- /// <value>The postscan tasks.</value>
- private ILibraryPostScanTask[] PostscanTasks { get; set; }
+ private const string ShortcutFileExtension = ".mblink";
- /// <summary>
- /// Gets the intro providers.
- /// </summary>
- /// <value>The intro providers.</value>
- private IIntroProvider[] IntroProviders { get; set; }
+ private readonly ILogger<LibraryManager> _logger;
+ private readonly IMemoryCache _memoryCache;
+ private readonly ITaskManager _taskManager;
+ private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataRepository;
+ private readonly IServerConfigurationManager _configurationManager;
+ private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory;
+ private readonly Lazy<IProviderManager> _providerManagerFactory;
+ private readonly Lazy<IUserViewManager> _userviewManagerFactory;
+ private readonly IServerApplicationHost _appHost;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IFileSystem _fileSystem;
+ private readonly IItemRepository _itemRepository;
+ private readonly IImageProcessor _imageProcessor;
/// <summary>
- /// Gets the list of entity resolution ignore rules
+ /// The _root folder sync lock.
/// </summary>
- /// <value>The entity resolution ignore rules.</value>
- private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
+ private readonly object _rootFolderSyncLock = new object();
+ private readonly object _userRootFolderSyncLock = new object();
- /// <summary>
- /// Gets the list of currently registered entity resolvers
- /// </summary>
- /// <value>The entity resolvers enumerable.</value>
- private IItemResolver[] EntityResolvers { get; set; }
- private IMultiItemResolver[] MultiItemResolvers { get; set; }
+ private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
+
+ private NamingOptions _namingOptions;
+ private string[] _videoFileExtensions;
/// <summary>
- /// Gets or sets the comparers.
+ /// The _root folder.
/// </summary>
- /// <value>The comparers.</value>
- private IBaseItemComparer[] Comparers { get; set; }
+ private volatile AggregateFolder _rootFolder;
+ private volatile UserRootFolder _userRootFolder;
+
+ private bool _wizardCompleted;
/// <summary>
- /// Gets the active item repository
+ /// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary>
- /// <value>The item repository.</value>
- public IItemRepository ItemRepository { get; set; }
+ /// <param name="appHost">The application host.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="taskManager">The task manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="userDataRepository">The user data repository.</param>
+ /// <param name="libraryMonitorFactory">The library monitor.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="providerManagerFactory">The provider manager.</param>
+ /// <param name="userviewManagerFactory">The userview manager.</param>
+ /// <param name="mediaEncoder">The media encoder.</param>
+ /// <param name="itemRepository">The item repository.</param>
+ /// <param name="imageProcessor">The image processor.</param>
+ /// <param name="memoryCache">The memory cache.</param>
+ public LibraryManager(
+ IServerApplicationHost appHost,
+ ILogger<LibraryManager> logger,
+ ITaskManager taskManager,
+ IUserManager userManager,
+ IServerConfigurationManager configurationManager,
+ IUserDataManager userDataRepository,
+ Lazy<ILibraryMonitor> libraryMonitorFactory,
+ IFileSystem fileSystem,
+ Lazy<IProviderManager> providerManagerFactory,
+ Lazy<IUserViewManager> userviewManagerFactory,
+ IMediaEncoder mediaEncoder,
+ IItemRepository itemRepository,
+ IImageProcessor imageProcessor,
+ IMemoryCache memoryCache)
+ {
+ _appHost = appHost;
+ _logger = logger;
+ _taskManager = taskManager;
+ _userManager = userManager;
+ _configurationManager = configurationManager;
+ _userDataRepository = userDataRepository;
+ _libraryMonitorFactory = libraryMonitorFactory;
+ _fileSystem = fileSystem;
+ _providerManagerFactory = providerManagerFactory;
+ _userviewManagerFactory = userviewManagerFactory;
+ _mediaEncoder = mediaEncoder;
+ _itemRepository = itemRepository;
+ _imageProcessor = imageProcessor;
+ _memoryCache = memoryCache;
+
+ _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
+
+ RecordConfigurationValues(configurationManager.Configuration);
+ }
/// <summary>
/// Occurs when [item added].
@@ -104,85 +168,64 @@ namespace Emby.Server.Implementations.Library
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
/// <summary>
- /// The _logger
+ /// Gets the root folder.
/// </summary>
- private readonly ILogger _logger;
+ /// <value>The root folder.</value>
+ public AggregateFolder RootFolder
+ {
+ get
+ {
+ if (_rootFolder == null)
+ {
+ lock (_rootFolderSyncLock)
+ {
+ _rootFolder ??= CreateRootFolder();
+ }
+ }
- /// <summary>
- /// The _task manager
- /// </summary>
- private readonly ITaskManager _taskManager;
+ return _rootFolder;
+ }
+ }
- /// <summary>
- /// The _user manager
- /// </summary>
- private readonly IUserManager _userManager;
+ private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
+
+ private IProviderManager ProviderManager => _providerManagerFactory.Value;
+
+ private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
/// <summary>
- /// The _user data repository
+ /// Gets or sets the postscan tasks.
/// </summary>
- private readonly IUserDataManager _userDataRepository;
+ /// <value>The postscan tasks.</value>
+ private ILibraryPostScanTask[] PostscanTasks { get; set; } = Array.Empty<ILibraryPostScanTask>();
/// <summary>
- /// Gets or sets the configuration manager.
+ /// Gets or sets the intro providers.
/// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
-
- private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
- private readonly Func<IProviderManager> _providerManagerFactory;
- private readonly Func<IUserViewManager> _userviewManager;
- public bool IsScanRunning { get; private set; }
- private IServerApplicationHost _appHost;
+ /// <value>The intro providers.</value>
+ private IIntroProvider[] IntroProviders { get; set; } = Array.Empty<IIntroProvider>();
/// <summary>
- /// The _library items cache
+ /// Gets or sets the list of entity resolution ignore rules.
/// </summary>
- private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
+ /// <value>The entity resolution ignore rules.</value>
+ private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty<IResolverIgnoreRule>();
+
/// <summary>
- /// Gets the library items cache.
+ /// Gets or sets the list of currently registered entity resolvers.
/// </summary>
- /// <value>The library items cache.</value>
- private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache;
+ /// <value>The entity resolvers enumerable.</value>
+ private IItemResolver[] EntityResolvers { get; set; } = Array.Empty<IItemResolver>();
- private readonly IFileSystem _fileSystem;
+ private IMultiItemResolver[] MultiItemResolvers { get; set; } = Array.Empty<IMultiItemResolver>();
/// <summary>
- /// Initializes a new instance of the <see cref="LibraryManager" /> class.
+ /// Gets or sets the comparers.
/// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="taskManager">The task manager.</param>
- /// <param name="userManager">The user manager.</param>
- /// <param name="configurationManager">The configuration manager.</param>
- /// <param name="userDataRepository">The user data repository.</param>
- public LibraryManager(
- IServerApplicationHost appHost,
- ILoggerFactory loggerFactory,
- ITaskManager taskManager,
- IUserManager userManager,
- IServerConfigurationManager configurationManager,
- IUserDataManager userDataRepository,
- Func<ILibraryMonitor> libraryMonitorFactory,
- IFileSystem fileSystem,
- Func<IProviderManager> providerManagerFactory,
- Func<IUserViewManager> userviewManager)
- {
- _logger = loggerFactory.CreateLogger(nameof(LibraryManager));
- _taskManager = taskManager;
- _userManager = userManager;
- ConfigurationManager = configurationManager;
- _userDataRepository = userDataRepository;
- _libraryMonitorFactory = libraryMonitorFactory;
- _fileSystem = fileSystem;
- _providerManagerFactory = providerManagerFactory;
- _userviewManager = userviewManager;
- _appHost = appHost;
- _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
-
- ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
+ /// <value>The comparers.</value>
+ private IBaseItemComparer[] Comparers { get; set; } = Array.Empty<IBaseItemComparer>();
- RecordConfigurationValues(configurationManager.Configuration);
- }
+ public bool IsScanRunning { get; private set; }
/// <summary>
/// Adds the parts.
@@ -191,8 +234,9 @@ namespace Emby.Server.Implementations.Library
/// <param name="resolvers">The resolvers.</param>
/// <param name="introProviders">The intro providers.</param>
/// <param name="itemComparers">The item comparers.</param>
- /// <param name="postscanTasks">The postscan tasks.</param>
- public void AddParts(IEnumerable<IResolverIgnoreRule> rules,
+ /// <param name="postscanTasks">The post scan tasks.</param>
+ public void AddParts(
+ IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IItemResolver> resolvers,
IEnumerable<IIntroProvider> introProviders,
IEnumerable<IBaseItemComparer> itemComparers,
@@ -203,47 +247,9 @@ namespace Emby.Server.Implementations.Library
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
IntroProviders = introProviders.ToArray();
Comparers = itemComparers.ToArray();
-
- PostscanTasks = postscanTasks.OrderBy(i =>
- {
- var hasOrder = i as IHasOrder;
-
- return hasOrder == null ? 0 : hasOrder.Order;
-
- }).ToArray();
- }
-
- /// <summary>
- /// The _root folder
- /// </summary>
- private volatile AggregateFolder _rootFolder;
- /// <summary>
- /// The _root folder sync lock
- /// </summary>
- private readonly object _rootFolderSyncLock = new object();
- /// <summary>
- /// Gets the root folder.
- /// </summary>
- /// <value>The root folder.</value>
- public AggregateFolder RootFolder
- {
- get
- {
- if (_rootFolder == null)
- {
- lock (_rootFolderSyncLock)
- {
- if (_rootFolder == null)
- {
- _rootFolder = CreateRootFolder();
- }
- }
- }
- return _rootFolder;
- }
+ PostscanTasks = postscanTasks.ToArray();
}
- private bool _wizardCompleted;
/// <summary>
/// Records the configuration values.
/// </summary>
@@ -258,9 +264,9 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
- void ConfigurationUpdated(object sender, EventArgs e)
+ private void ConfigurationUpdated(object sender, EventArgs e)
{
- var config = ConfigurationManager.Configuration;
+ var config = _configurationManager.Configuration;
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
@@ -278,33 +284,23 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(item));
}
+
if (item is IItemByName)
{
- if (!(item is MusicArtist))
+ if (item is not MusicArtist)
{
return;
}
}
-
- else if (item.IsFolder)
- {
- //if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder))
- //{
- // if (item.SourceType != SourceType.Library)
- // {
- // return;
- // }
- //}
- }
- else
+ else if (!item.IsFolder)
{
- if (!(item is Video) && !(item is LiveTvChannel))
+ if (item is not Video && item is not LiveTvChannel)
{
return;
}
}
- LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
+ _memoryCache.Set(item.Id, item);
}
public void DeleteItem(BaseItem item, DeleteOptions options)
@@ -345,12 +341,14 @@ namespace Emby.Server.Implementations.Library
// channel no longer installed
}
}
+
options.DeleteFileLocation = false;
}
if (item is LiveTvProgram)
{
- _logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ _logger.LogDebug(
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -358,7 +356,8 @@ namespace Emby.Server.Implementations.Library
}
else
{
- _logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ _logger.LogInformation(
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -371,19 +370,25 @@ namespace Emby.Server.Implementations.Library
foreach (var metadataPath in GetMetadataPaths(item, children))
{
- _logger.LogDebug("Deleting path {0}", metadataPath);
+ if (!Directory.Exists(metadataPath))
+ {
+ continue;
+ }
+
+ _logger.LogDebug(
+ "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ metadataPath,
+ item.Id);
try
{
Directory.Delete(metadataPath, true);
}
- catch (IOException)
- {
-
- }
catch (Exception ex)
{
- _logger.LogError(ex, "Error deleting {metadataPath}", metadataPath);
+ _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath);
}
}
@@ -393,13 +398,19 @@ namespace Emby.Server.Implementations.Library
// Add this flag to GetDeletePaths if required in the future
var isRequiredForDelete = true;
- foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
+ foreach (var fileSystemInfo in item.GetDeletePaths())
{
- if (File.Exists(fileSystemInfo.FullName))
+ if (Directory.Exists(fileSystemInfo.FullName) || File.Exists(fileSystemInfo.FullName))
{
try
{
- _logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
+ _logger.LogInformation(
+ "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+
if (fileSystemInfo.IsDirectory)
{
Directory.Delete(fileSystemInfo.FullName, true);
@@ -431,13 +442,13 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
- ItemRepository.DeleteItem(item.Id, CancellationToken.None);
+ _itemRepository.DeleteItem(item.Id);
foreach (var child in children)
{
- ItemRepository.DeleteItem(child.Id, CancellationToken.None);
+ _itemRepository.DeleteItem(child.Id);
}
- _libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
+ _memoryCache.Remove(item.Id);
ReportItemRemoved(item, parent);
}
@@ -497,20 +508,22 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(key));
}
+
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
- if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath))
+ string programDataPath = _configurationManager.ApplicationPaths.ProgramDataPath;
+ if (key.StartsWith(programDataPath, StringComparison.Ordinal))
{
// Try to normalize paths located underneath program-data in an attempt to make them more portable
- key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
- .TrimStart(new[] { '/', '\\' })
- .Replace("/", "\\");
+ key = key.Substring(programDataPath.Length)
+ .TrimStart('/', '\\')
+ .Replace('/', '\\');
}
- if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+ if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLowerInvariant();
}
@@ -520,13 +533,11 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
- public BaseItem ResolvePath(FileSystemMetadata fileInfo,
- Folder parent = null)
- {
- return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent);
- }
+ public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
+ => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
- private BaseItem ResolvePath(FileSystemMetadata fileInfo,
+ private BaseItem ResolvePath(
+ FileSystemMetadata fileInfo,
IDirectoryService directoryService,
IItemResolver[] resolvers,
Folder parent = null,
@@ -545,10 +556,9 @@ namespace Emby.Server.Implementations.Library
collectionType = GetContentTypeOverride(fullPath, true);
}
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{
Parent = parent,
- Path = fullPath,
FileInfo = fileInfo,
CollectionType = collectionType,
LibraryOptions = libraryOptions
@@ -581,7 +591,7 @@ namespace Emby.Server.Implementations.Library
{
_logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf);
- files = new FileSystemMetadata[] { };
+ files = Array.Empty<FileSystemMetadata>();
}
else
{
@@ -609,13 +619,7 @@ namespace Emby.Server.Implementations.Library
}
public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
- {
- if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)))
- {
- return true;
- }
- return false;
- }
+ => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
{
@@ -640,10 +644,10 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Determines whether a path should be ignored based on its contents - called after the contents have been read
+ /// Determines whether a path should be ignored based on its contents - called after the contents have been read.
/// </summary>
/// <param name="args">The args.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
private static bool ShouldResolvePathContents(ItemResolveArgs args)
{
// Ignore any folders containing a file called .ignore
@@ -655,7 +659,8 @@ namespace Emby.Server.Implementations.Library
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
}
- public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
+ public IEnumerable<BaseItem> ResolvePaths(
+ IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
Folder parent,
LibraryOptions libraryOptions,
@@ -679,8 +684,9 @@ namespace Emby.Server.Implementations.Library
foreach (var item in items)
{
- ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService);
+ ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
}
+
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
return items;
}
@@ -690,39 +696,49 @@ namespace Emby.Server.Implementations.Library
return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions);
}
- private IEnumerable<BaseItem> ResolveFileList(IEnumerable<FileSystemMetadata> fileList,
+ private IEnumerable<BaseItem> ResolveFileList(
+ IReadOnlyList<FileSystemMetadata> fileList,
IDirectoryService directoryService,
Folder parent,
string collectionType,
IItemResolver[] resolvers,
LibraryOptions libraryOptions)
{
- return fileList.Select(f =>
+ // Given that fileList is a list we can save enumerator allocations by indexing
+ for (var i = 0; i < fileList.Count; i++)
{
+ var file = fileList[i];
+ BaseItem result = null;
try
{
- return ResolvePath(f, directoryService, resolvers, parent, collectionType, libraryOptions);
+ result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error resolving path {path}", f.FullName);
- return null;
+ _logger.LogError(ex, "Error resolving path {Path}", file.FullName);
}
- }).Where(i => i != null);
+
+ if (result != null)
+ {
+ yield return result;
+ }
+ }
}
/// <summary>
- /// Creates the root media folder
+ /// Creates the root media folder.
/// </summary>
/// <returns>AggregateFolder.</returns>
- /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded</exception>
+ /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
public AggregateFolder CreateRootFolder()
{
- var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath;
Directory.CreateDirectory(rootFolderPath);
- var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))).DeepCopy<Folder, AggregateFolder>();
+ var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
+ ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
+ .DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved
if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
@@ -732,7 +748,7 @@ namespace Emby.Server.Implementations.Library
}
// Add in the plug-in folders
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
+ var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists");
Directory.CreateDirectory(path);
@@ -763,7 +779,7 @@ namespace Emby.Server.Implementations.Library
if (folder.ParentId != rootFolder.Id)
{
folder.ParentId = rootFolder.Id;
- folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+ folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
}
rootFolder.AddVirtualChild(folder);
@@ -773,24 +789,33 @@ namespace Emby.Server.Implementations.Library
return rootFolder;
}
- private volatile UserRootFolder _userRootFolder;
- private readonly object _syncLock = new object();
public Folder GetUserRootFolder()
{
if (_userRootFolder == null)
{
- lock (_syncLock)
+ lock (_userRootFolderSyncLock)
{
if (_userRootFolder == null)
{
- var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
+ _logger.LogDebug("Creating userRootPath at {path}", userRootPath);
Directory.CreateDirectory(userRootPath);
- var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
+ var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
+ UserRootFolder tmpItem = null;
+ try
+ {
+ tmpItem = GetItemById(newItemId) as UserRootFolder;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating UserRootFolder {path}", newItemId);
+ }
if (tmpItem == null)
{
+ _logger.LogDebug("Creating new userRootFolder with DeepCopy");
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
}
@@ -802,6 +827,7 @@ namespace Emby.Server.Implementations.Library
}
_userRootFolder = tmpItem;
+ _logger.LogDebug("Setting userRootFolder: {folder}", _userRootFolder);
}
}
}
@@ -813,19 +839,16 @@ namespace Emby.Server.Implementations.Library
{
// If this returns multiple items it could be tricky figuring out which one is correct.
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
-
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
- //_logger.LogInformation("FindByPath {0}", path);
-
var query = new InternalItemsQuery
{
Path = path,
IsFolder = isFolder,
- OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(),
+ OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
Limit = 1,
DtoOptions = new DtoOptions(true)
};
@@ -835,17 +858,31 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Person
+ /// Gets the person.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Person}.</returns>
public Person GetPerson(string name)
{
- return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true));
+ var path = Person.GetPath(name);
+ var id = GetItemByNameId<Person>(path);
+ if (GetItemById(id) is not Person item)
+ {
+ item = new Person
+ {
+ Name = name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow,
+ Path = path
+ };
+ }
+
+ return item;
}
/// <summary>
- /// Gets a Studio
+ /// Gets the studio.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Studio}.</returns>
@@ -856,21 +893,21 @@ namespace Emby.Server.Implementations.Library
public Guid GetStudioId(string name)
{
- return GetItemByNameId<Studio>(Studio.GetPath, name);
+ return GetItemByNameId<Studio>(Studio.GetPath(name));
}
public Guid GetGenreId(string name)
{
- return GetItemByNameId<Genre>(Genre.GetPath, name);
+ return GetItemByNameId<Genre>(Genre.GetPath(name));
}
public Guid GetMusicGenreId(string name)
{
- return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name);
+ return GetItemByNameId<MusicGenre>(MusicGenre.GetPath(name));
}
/// <summary>
- /// Gets a Genre
+ /// Gets the genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -880,7 +917,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets the genre.
+ /// Gets the music genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{MusicGenre}.</returns>
@@ -890,11 +927,10 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Year
+ /// Gets the year.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Task{Year}.</returns>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
public Year GetYear(int value)
{
if (value <= 0)
@@ -908,7 +944,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Genre
+ /// Gets a Genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -929,10 +965,9 @@ namespace Emby.Server.Implementations.Library
{
var existing = GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(T).Name },
+ IncludeItemTypes = new[] { nameof(MusicArtist) },
Name = name,
DtoOptions = options
-
}).Cast<MusicArtist>()
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
.Cast<T>()
@@ -944,13 +979,11 @@ namespace Emby.Server.Implementations.Library
}
}
- var id = GetItemByNameId<T>(getPathFn, name);
-
+ var path = getPathFn(name);
+ var id = GetItemByNameId<T>(path);
var item = GetItemById(id) as T;
-
if (item == null)
{
- var path = getPathFn(name);
item = new T
{
Name = name,
@@ -966,11 +999,10 @@ namespace Emby.Server.Implementations.Library
return item;
}
- private Guid GetItemByNameId<T>(Func<string, string> getPathFn, string name)
+ private Guid GetItemByNameId<T>(string path)
where T : BaseItem, new()
{
- var path = getPathFn(name);
- var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+ var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds;
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
}
@@ -984,13 +1016,13 @@ namespace Emby.Server.Implementations.Library
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
// Ensure the location is available.
- Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
+ Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
}
/// <summary>
- /// Reloads the root media folder
+ /// Reloads the root media folder.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -1021,7 +1053,7 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
IsScanRunning = true;
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
@@ -1029,27 +1061,32 @@ namespace Emby.Server.Implementations.Library
}
finally
{
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
IsScanRunning = false;
}
}
private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken)
{
- var rootChildren = RootFolder.Children.ToList();
- rootChildren = GetUserRootFolder().Children.ToList();
-
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
// Start by just validating the children of the root, but go no further
- await RootFolder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false);
+ await RootFolder.ValidateChildren(
+ new SimpleProgress<double>(),
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
+ recursive: false,
+ cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
- await GetUserRootFolder().ValidateChildren(new SimpleProgress<double>(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false);
+ await GetUserRootFolder().ValidateChildren(
+ new SimpleProgress<double>(),
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
+ recursive: false,
+ cancellationToken).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes
- foreach (var folder in GetUserRootFolder().Children.OfType<Folder>().ToList())
+ foreach (var folder in GetUserRootFolder().Children.OfType<Folder>())
{
await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
@@ -1063,10 +1100,10 @@ namespace Emby.Server.Implementations.Library
var innerProgress = new ActionableProgress<double>();
- innerProgress.RegisterAction(pct => progress.Report(pct * .96));
+ innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
- // Now validate the entire media library
- await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: true).ConfigureAwait(false);
+ // Validate the entire media library
+ await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken).ConfigureAwait(false);
progress.Report(96);
@@ -1074,7 +1111,6 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
- // Run post-scan tasks
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
progress.Report(100);
@@ -1125,7 +1161,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error running postscan task");
+ _logger.LogError(ex, "Error running post-scan task");
}
numComplete++;
@@ -1134,7 +1170,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
- ItemRepository.UpdateInheritedValues(cancellationToken);
+ _itemRepository.UpdateInheritedValues();
progress.Report(100);
}
@@ -1150,13 +1186,14 @@ namespace Emby.Server.Implementations.Library
public List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState)
{
+ _logger.LogDebug("Getting topLibraryFolders");
var topLibraryFolders = GetUserRootFolder().Children.ToList();
- var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
+ _logger.LogDebug("Getting refreshQueue");
+ var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null;
- return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
+ return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath)
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
- .OrderBy(i => i.Name)
.ToList();
}
@@ -1191,12 +1228,12 @@ namespace Emby.Server.Implementations.Library
if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary))
{
- info.PrimaryImageItemId = libraryFolder.Id.ToString("N");
+ info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
}
if (libraryFolder != null)
{
- info.ItemId = libraryFolder.Id.ToString("N");
+ info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
info.LibraryOptions = GetLibraryOptions(libraryFolder);
if (refreshQueue != null)
@@ -1210,11 +1247,18 @@ namespace Emby.Server.Implementations.Library
return info;
}
- private string GetCollectionType(string path)
+ private CollectionTypeOptions? GetCollectionType(string path)
{
- return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
- .Select(i => Path.GetFileNameWithoutExtension(i))
- .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false);
+ foreach (ReadOnlySpan<char> file in files)
+ {
+ if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res))
+ {
+ return res;
+ }
+ }
+
+ return null;
}
/// <summary>
@@ -1222,15 +1266,15 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
- /// <exception cref="ArgumentNullException">id</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
public BaseItem GetItemById(Guid id)
{
if (id == Guid.Empty)
{
- throw new ArgumentException(nameof(id), "Guid can't be empty");
+ throw new ArgumentException("Guid can't be empty", nameof(id));
}
- if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
+ if (_memoryCache.TryGetValue(id, out BaseItem item))
{
return item;
}
@@ -1247,7 +1291,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{
- if (query.Recursive && !query.ParentId.Equals(Guid.Empty))
+ if (query.Recursive && query.ParentId != Guid.Empty)
{
var parent = GetItemById(query.ParentId);
if (parent != null)
@@ -1261,7 +1305,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User, allowExternalContent);
}
- return ItemRepository.GetItemList(query);
+ return _itemRepository.GetItemList(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1285,7 +1329,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetCount(query);
+ return _itemRepository.GetCount(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
@@ -1300,7 +1344,7 @@ namespace Emby.Server.Implementations.Library
}
}
- return ItemRepository.GetItemList(query);
+ return _itemRepository.GetItemList(query);
}
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1312,12 +1356,12 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
- return ItemRepository.GetItems(query);
+ return _itemRepository.GetItems(query);
}
return new QueryResult<BaseItem>
{
- Items = ItemRepository.GetItemList(query).ToArray()
+ Items = _itemRepository.GetItemList(query)
};
}
@@ -1328,7 +1372,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetItemIdsList(query);
+ return _itemRepository.GetItemIdsList(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
@@ -1339,7 +1383,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetStudios(query);
+ return _itemRepository.GetStudios(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
@@ -1350,7 +1394,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetGenres(query);
+ return _itemRepository.GetGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
@@ -1361,7 +1405,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetMusicGenres(query);
+ return _itemRepository.GetMusicGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
@@ -1372,7 +1416,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetAllArtists(query);
+ return _itemRepository.GetAllArtists(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
@@ -1383,39 +1427,36 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetArtists(query);
+ return _itemRepository.GetArtists(query);
}
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
{
- if (query.AncestorIds.Length == 0)
+ var ancestorIds = query.AncestorIds;
+ int len = ancestorIds.Length;
+ if (len == 0)
{
return;
}
- var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
-
- if (parents.All(i =>
+ var parents = new BaseItem[len];
+ for (int i = 0; i < len; i++)
{
- if (i is ICollectionFolder || i is UserView)
+ parents[i] = GetItemById(ancestorIds[i]);
+ if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
{
- return true;
+ return;
}
+ }
- //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
- return false;
+ // Optimize by querying against top level views
+ query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
+ query.AncestorIds = Array.Empty<Guid>();
- }))
+ // Prevent searching in all libraries due to empty filter
+ if (query.TopParentIds.Length == 0)
{
- // Optimize by querying against top level views
- query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
- query.AncestorIds = Array.Empty<Guid>();
-
- // Prevent searching in all libraries due to empty filter
- if (query.TopParentIds.Length == 0)
- {
- query.TopParentIds = new[] { Guid.NewGuid() };
- }
+ query.TopParentIds = new[] { Guid.NewGuid() };
}
}
@@ -1427,7 +1468,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetAlbumArtists(query);
+ return _itemRepository.GetAlbumArtists(query);
}
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1448,30 +1489,18 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
- return ItemRepository.GetItems(query);
+ return _itemRepository.GetItems(query);
}
- var list = ItemRepository.GetItemList(query);
-
return new QueryResult<BaseItem>
{
- Items = list.ToArray()
+ Items = _itemRepository.GetItemList(query)
};
}
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
{
- if (parents.All(i =>
- {
- if (i is ICollectionFolder || i is UserView)
- {
- return true;
- }
-
- //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name);
- return false;
-
- }))
+ if (parents.All(i => i is ICollectionFolder || i is UserView))
{
// Optimize by querying against top level views
query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
@@ -1501,13 +1530,13 @@ namespace Emby.Server.Implementations.Library
{
if (query.AncestorIds.Length == 0 &&
query.ParentId.Equals(Guid.Empty) &&
- query.ChannelIds.Length == 0 &&
+ query.ChannelIds.Count == 0 &&
query.TopParentIds.Length == 0 &&
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
query.ItemIds.Length == 0)
{
- var userViews = _userviewManager().GetUserViews(new UserViewQuery
+ var userViews = UserViewManager.GetUserViews(new UserViewQuery
{
UserId = user.Id,
IncludeHidden = true,
@@ -1520,11 +1549,9 @@ namespace Emby.Server.Implementations.Library
private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user)
{
- var view = item as UserView;
-
- if (view != null)
+ if (item is UserView view)
{
- if (string.Equals(view.ViewType, CollectionType.LiveTv))
+ if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal))
{
return new[] { view.Id };
}
@@ -1537,8 +1564,10 @@ namespace Emby.Server.Implementations.Library
{
return GetTopParentIdsForQuery(displayParent, user);
}
+
return Array.Empty<Guid>();
}
+
if (!view.ParentId.Equals(Guid.Empty))
{
var displayParent = GetItemById(view.ParentId);
@@ -1546,11 +1575,13 @@ namespace Emby.Server.Implementations.Library
{
return GetTopParentIdsForQuery(displayParent, user);
}
+
return Array.Empty<Guid>();
}
// Handle grouping
- if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0)
+ if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
+ && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
{
return GetUserRootFolder()
.GetChildren(user, true)
@@ -1559,11 +1590,11 @@ namespace Emby.Server.Implementations.Library
.Where(i => user.IsFolderGrouped(i.Id))
.SelectMany(i => GetTopParentIdsForQuery(i, user));
}
+
return Array.Empty<Guid>();
}
- var collectionFolder = item as CollectionFolder;
- if (collectionFolder != null)
+ if (item is CollectionFolder collectionFolder)
{
return collectionFolder.PhysicalFolderIds;
}
@@ -1573,6 +1604,7 @@ namespace Emby.Server.Implementations.Library
{
return new[] { topParent.Id };
}
+
return Array.Empty<Guid>();
}
@@ -1585,7 +1617,6 @@ namespace Emby.Server.Implementations.Library
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
{
var tasks = IntroProviders
- .OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1)
.Take(1)
.Select(i => GetIntros(i, item, user));
@@ -1728,22 +1759,20 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList)
+ public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
{
var isFirst = true;
IOrderedEnumerable<BaseItem> orderedItems = null;
- foreach (var orderBy in orderByList)
+ foreach (var (name, sortOrder) in orderBy)
{
- var comparer = GetComparer(orderBy.Item1, user);
+ var comparer = GetComparer(name, user);
if (comparer == null)
{
continue;
}
- var sortOrder = orderBy.Item2;
-
if (isFirst)
{
orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer);
@@ -1769,19 +1798,16 @@ namespace Emby.Server.Implementations.Library
{
var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
- if (comparer != null)
+ // If it requires a user, create a new one, and assign the user
+ if (comparer is IUserBaseItemComparer)
{
- // If it requires a user, create a new one, and assign the user
- if (comparer is IUserBaseItemComparer)
- {
- var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
+ var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType());
- userComparer.User = user;
- userComparer.UserManager = _userManager;
- userComparer.UserDataRepository = _userDataRepository;
+ userComparer.User = user;
+ userComparer.UserManager = _userManager;
+ userComparer.UserDataRepository = _userDataRepository;
- return userComparer;
- }
+ return userComparer;
}
return comparer;
@@ -1792,7 +1818,6 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="item">The item.</param>
/// <param name="parent">The parent item.</param>
- /// <returns>Task.</returns>
public void CreateItem(BaseItem item, BaseItem parent)
{
CreateItems(new[] { item }, parent, CancellationToken.None);
@@ -1802,11 +1827,11 @@ namespace Emby.Server.Implementations.Library
/// Creates the items.
/// </summary>
/// <param name="items">The items.</param>
+ /// <param name="parent">The parent item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
+ public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{
- ItemRepository.SaveItems(items, cancellationToken);
+ _itemRepository.SaveItems(items, cancellationToken);
foreach (var item in items)
{
@@ -1825,11 +1850,13 @@ namespace Emby.Server.Implementations.Library
try
{
- ItemAdded(this, new ItemChangeEventArgs
- {
- Item = item,
- Parent = parent ?? item.GetParent()
- });
+ ItemAdded(
+ this,
+ new ItemChangeEventArgs
+ {
+ Item = item,
+ Parent = parent ?? item.GetParent()
+ });
}
catch (Exception ex)
{
@@ -1839,34 +1866,119 @@ namespace Emby.Server.Implementations.Library
}
}
- public void UpdateImages(BaseItem item)
+ private bool ImageNeedsRefresh(ItemImageInfo image)
{
- ItemRepository.SaveImages(item);
+ if (image.Path != null && image.IsLocalFile)
+ {
+ if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash))
+ {
+ return true;
+ }
- RegisterItem(item);
+ try
+ {
+ return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot get file info for {0}", image.Path);
+ return false;
+ }
+ }
+
+ return image.Path != null && !image.IsLocalFile;
}
- /// <summary>
- /// Updates the item.
- /// </summary>
- public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public async Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false)
{
- foreach (var item in items)
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
+ // Skip image processing if current or live tv source
+ if (outdated.Length == 0 || item.SourceType != SourceType.Library)
+ {
+ RegisterItem(item);
+ return;
+ }
+
+ foreach (var img in outdated)
{
- if (item.IsFileProtocol)
+ var image = img;
+ if (!img.IsLocalFile)
{
- _providerManagerFactory().SaveMetadata(item, updateReason);
+ try
+ {
+ var index = item.GetImageIndex(img);
+ image = await ConvertImageToLocal(item, img, index).ConfigureAwait(false);
+ }
+ catch (ArgumentException)
+ {
+ _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
+ continue;
+ }
+ catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
+ {
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
+ continue;
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode);
+ continue;
+ }
}
- item.DateLastSaved = DateTime.UtcNow;
+ try
+ {
+ ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
+ image.Width = size.Width;
+ image.Height = size.Height;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
+ image.Width = 0;
+ image.Height = 0;
+ continue;
+ }
- RegisterItem(item);
+ try
+ {
+ image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path);
+ image.BlurHash = string.Empty;
+ }
+
+ try
+ {
+ image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path);
+ }
}
- //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name;
- //_logger.LogDebug("Saving {0} to database.", logName);
+ _itemRepository.SaveImages(item);
+ RegisterItem(item);
+ }
+
+ /// <inheritdoc />
+ public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ {
+ foreach (var item in items)
+ {
+ await RunMetadataSavers(item, updateReason).ConfigureAwait(false);
+ }
- ItemRepository.SaveItems(items, cancellationToken);
+ _itemRepository.SaveItems(items, cancellationToken);
if (ItemUpdated != null)
{
@@ -1880,12 +1992,14 @@ namespace Emby.Server.Implementations.Library
try
{
- ItemUpdated(this, new ItemChangeEventArgs
- {
- Item = item,
- Parent = parent,
- UpdateReason = updateReason
- });
+ ItemUpdated(
+ this,
+ new ItemChangeEventArgs
+ {
+ Item = item,
+ Parent = parent,
+ UpdateReason = updateReason
+ });
}
catch (Exception ex)
{
@@ -1895,33 +2009,40 @@ namespace Emby.Server.Implementations.Library
}
}
- /// <summary>
- /// Updates the item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="updateReason">The update reason.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
+
+ public Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
{
- UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
+ if (item.IsFileProtocol)
+ {
+ ProviderManager.SaveMetadata(item, updateReason);
+ }
+
+ item.DateLastSaved = DateTime.UtcNow;
+
+ return UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate);
}
/// <summary>
/// Reports the item removed.
/// </summary>
/// <param name="item">The item.</param>
+ /// <param name="parent">The parent item.</param>
public void ReportItemRemoved(BaseItem item, BaseItem parent)
{
if (ItemRemoved != null)
{
try
{
- ItemRemoved(this, new ItemChangeEventArgs
- {
- Item = item,
- Parent = parent
- });
+ ItemRemoved(
+ this,
+ new ItemChangeEventArgs
+ {
+ Item = item,
+ Parent = parent
+ });
}
catch (Exception ex)
{
@@ -1937,7 +2058,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>BaseItem.</returns>
public BaseItem RetrieveItem(Guid id)
{
- return ItemRepository.RetrieveItem(id);
+ return _itemRepository.RetrieveItem(id);
}
public List<Folder> GetCollectionFolders(BaseItem item)
@@ -1959,7 +2080,7 @@ namespace Emby.Server.Implementations.Library
return new List<Folder>();
}
- return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>().ToList());
+ return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>());
}
public List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren)
@@ -1984,21 +2105,20 @@ namespace Emby.Server.Implementations.Library
return GetCollectionFoldersInternal(item, allUserRootChildren);
}
- private static List<Folder> GetCollectionFoldersInternal(BaseItem item, List<Folder> allUserRootChildren)
+ private static List<Folder> GetCollectionFoldersInternal(BaseItem item, IEnumerable<Folder> allUserRootChildren)
{
return allUserRootChildren
- .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path.AsSpan(), StringComparison.OrdinalIgnoreCase))
.ToList();
}
public LibraryOptions GetLibraryOptions(BaseItem item)
{
- var collectionFolder = item as CollectionFolder;
- if (collectionFolder == null)
+ if (item is not CollectionFolder collectionFolder)
{
+ // List.Find is more performant than FirstOrDefault due to enumerator allocation
collectionFolder = GetCollectionFolders(item)
- .OfType<CollectionFolder>()
- .FirstOrDefault();
+ .Find(folder => folder is CollectionFolder) as CollectionFolder;
}
return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
@@ -2047,8 +2167,7 @@ namespace Emby.Server.Implementations.Library
public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath)
{
- var collectionFolder = item as ICollectionFolder;
- if (collectionFolder != null)
+ if (item is ICollectionFolder collectionFolder)
{
return collectionFolder.CollectionType;
}
@@ -2058,13 +2177,11 @@ namespace Emby.Server.Implementations.Library
private string GetContentTypeOverride(string path, bool inherit)
{
- var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
- if (nameValuePair != null)
- {
- return nameValuePair.Value;
- }
-
- return null;
+ var nameValuePair = _configurationManager.Configuration.ContentTypes
+ .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
+ || (inherit && !string.IsNullOrEmpty(i.Name)
+ && _fileSystem.ContainsSubPath(i.Name, path)));
+ return nameValuePair?.Value;
}
private string GetTopFolderContentType(BaseItem item)
@@ -2081,6 +2198,7 @@ namespace Emby.Server.Implementations.Library
{
break;
}
+
item = parent;
}
@@ -2091,10 +2209,8 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
}
- private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
- //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1);
-
- public UserView GetNamedView(User user,
+ public UserView GetNamedView(
+ User user,
string name,
string viewType,
string sortName)
@@ -2102,13 +2218,15 @@ namespace Emby.Server.Implementations.Library
return GetNamedView(user, name, Guid.Empty, viewType, sortName);
}
- public UserView GetNamedView(string name,
+ public UserView GetNamedView(
+ string name,
string viewType,
string sortName)
{
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
- "views",
- _fileSystem.GetValidFilename(viewType));
+ var path = Path.Combine(
+ _configurationManager.ApplicationPaths.InternalMetadataPath,
+ "views",
+ _fileSystem.GetValidFilename(viewType));
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
@@ -2137,25 +2255,26 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
- _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), RefreshPriority.Normal);
+ item.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
+ ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
return item;
}
- public UserView GetNamedView(User user,
+ public UserView GetNamedView(
+ User user,
string name,
Guid parentId,
string viewType,
string sortName)
{
- var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
- var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
+ var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
+ var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
+ var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2173,11 +2292,10 @@ namespace Emby.Server.Implementations.Library
Name = name,
ViewType = viewType,
ForcedSortName = sortName,
- UserId = user.Id
+ UserId = user.Id,
+ DisplayParentId = parentId
};
- item.DisplayParentId = parentId;
-
CreateItem(item, null);
isNew = true;
@@ -2193,20 +2311,23 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
- {
- // Need to force save to increment DateLastSaved
- ForceSave = true
-
- }, RefreshPriority.Normal);
+ ProviderManager.QueueRefresh(
+ item.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ // Need to force save to increment DateLastSaved
+ ForceSave = true
+ },
+ RefreshPriority.Normal);
}
return item;
}
- public UserView GetShadowView(BaseItem parent,
- string viewType,
- string sortName)
+ public UserView GetShadowView(
+ BaseItem parent,
+ string viewType,
+ string sortName)
{
if (parent == null)
{
@@ -2257,18 +2378,21 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
- {
- // Need to force save to increment DateLastSaved
- ForceSave = true
-
- }, RefreshPriority.Normal);
+ ProviderManager.QueueRefresh(
+ item.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ // Need to force save to increment DateLastSaved
+ ForceSave = true
+ },
+ RefreshPriority.Normal);
}
return item;
}
- public UserView GetNamedView(string name,
+ public UserView GetNamedView(
+ string name,
Guid parentId,
string viewType,
string sortName,
@@ -2279,7 +2403,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(name));
}
- var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
+ var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
if (!string.IsNullOrEmpty(uniqueId))
{
@@ -2288,7 +2412,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
+ var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2318,7 +2442,7 @@ namespace Emby.Server.Implementations.Library
if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
{
item.ViewType = viewType;
- item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+ item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
}
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
@@ -2331,54 +2455,71 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
- {
- // Need to force save to increment DateLastSaved
- ForceSave = true
- }, RefreshPriority.Normal);
+ ProviderManager.QueueRefresh(
+ item.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ // Need to force save to increment DateLastSaved
+ ForceSave = true
+ },
+ RefreshPriority.Normal);
}
return item;
}
- public void AddExternalSubtitleStreams(List<MediaStream> streams,
+ public void AddExternalSubtitleStreams(
+ List<MediaStream> streams,
string videoPath,
string[] files)
{
- new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
+ new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
}
- public bool IsVideoFile(string path, LibraryOptions libraryOptions)
+ public BaseItem GetParentItem(string parentId, Guid? userId)
{
- var resolver = new VideoResolver(GetNamingOptions());
- return resolver.IsVideoFile(path);
+ if (string.IsNullOrEmpty(parentId))
+ {
+ return GetParentItem((Guid?)null, userId);
+ }
+
+ return GetParentItem(new Guid(parentId), userId);
}
- public bool IsVideoFile(string path)
+ public BaseItem GetParentItem(Guid? parentId, Guid? userId)
{
- return IsVideoFile(path, new LibraryOptions());
+ if (parentId.HasValue)
+ {
+ return GetItemById(parentId.Value);
+ }
+
+ if (userId.HasValue && userId != Guid.Empty)
+ {
+ return GetUserRootFolder();
+ }
+
+ return RootFolder;
}
- public bool IsAudioFile(string path, LibraryOptions libraryOptions)
+ /// <inheritdoc />
+ public bool IsVideoFile(string path)
{
- var parser = new AudioFileParser(GetNamingOptions());
- return parser.IsAudioFile(path);
+ return VideoResolver.IsVideoFile(path, GetNamingOptions());
}
+ /// <inheritdoc />
public bool IsAudioFile(string path)
- {
- return IsAudioFile(path, new LibraryOptions());
- }
+ => AudioFileParser.IsAudioFile(path, GetNamingOptions());
+ /// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
- {
- return new SeasonPathParser(GetNamingOptions()).Parse(path, true, true).SeasonNumber;
- }
+ => SeasonPathParser.Parse(path, true, true).SeasonNumber;
+ /// <inheritdoc />
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{
var series = episode.Series;
- bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
+ bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
if (!isAbsoluteNaming.Value)
{
// In other words, no filter applied
@@ -2389,13 +2530,58 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
- var episodeInfo = episode.IsFileProtocol ?
- resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) :
- new Naming.TV.EpisodeInfo();
+ // TODO nullable - what are we trying to do there with empty episodeInfo?
+ EpisodeInfo episodeInfo = null;
+ if (episode.IsFileProtocol)
+ {
+ episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
+ // Resolve from parent folder if it's not the Season folder
+ var parent = episode.GetParent();
+ if (episodeInfo == null && parent.GetType() == typeof(Folder))
+ {
+ episodeInfo = resolver.Resolve(parent.Path, true, null, null, isAbsoluteNaming);
+ if (episodeInfo != null)
+ {
+ // add the container
+ episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.');
+ }
+ }
+ }
- if (episodeInfo == null)
+ episodeInfo ??= new EpisodeInfo(episode.Path);
+
+ try
+ {
+ var libraryOptions = GetLibraryOptions(episode);
+ if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
+ {
+ // Read from metadata
+ var mediaInfo = _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = episode.GetMediaSources(false)[0],
+ MediaType = DlnaProfileType.Video
+ },
+ CancellationToken.None).GetAwaiter().GetResult();
+ if (mediaInfo.ParentIndexNumber > 0)
+ {
+ episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
+ }
+
+ if (mediaInfo.IndexNumber > 0)
+ {
+ episodeInfo.EpisodeNumber = mediaInfo.IndexNumber;
+ }
+
+ if (!string.IsNullOrEmpty(mediaInfo.ShowName))
+ {
+ episodeInfo.SeriesName = mediaInfo.ShowName;
+ }
+ }
+ }
+ catch (Exception ex)
{
- episodeInfo = new Naming.TV.EpisodeInfo();
+ _logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path);
}
var changed = false;
@@ -2445,16 +2631,18 @@ namespace Emby.Server.Implementations.Library
{
changed = true;
}
+
episode.IndexNumber = episodeInfo.EpisodeNumber;
}
if (!episode.IndexNumberEnd.HasValue || forceRefresh)
{
- if (episode.IndexNumberEnd != episodeInfo.EndingEpsiodeNumber)
+ if (episode.IndexNumberEnd != episodeInfo.EndingEpisodeNumber)
{
changed = true;
}
- episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber;
+
+ episode.IndexNumberEnd = episodeInfo.EndingEpisodeNumber;
}
if (!episode.ParentIndexNumber.HasValue || forceRefresh)
@@ -2463,6 +2651,7 @@ namespace Emby.Server.Implementations.Library
{
changed = true;
}
+
episode.ParentIndexNumber = episodeInfo.SeasonNumber;
}
}
@@ -2475,6 +2664,15 @@ namespace Emby.Server.Implementations.Library
{
episode.ParentIndexNumber = season.IndexNumber;
}
+ else
+ {
+ /*
+ Anime series don't generally have a season in their file name, however,
+ tvdb needs a season to correctly get the metadata.
+ Hence, a null season needs to be filled with something. */
+ // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
+ episode.ParentIndexNumber = 1;
+ }
if (episode.ParentIndexNumber.HasValue)
{
@@ -2485,21 +2683,13 @@ namespace Emby.Server.Implementations.Library
return changed;
}
+ /// <inheritdoc />
public NamingOptions GetNamingOptions()
{
- return GetNamingOptionsInternal();
- }
-
- private NamingOptions _namingOptions;
- private string[] _videoFileExtensions;
- private NamingOptions GetNamingOptionsInternal()
- {
if (_namingOptions == null)
{
- var options = new NamingOptions();
-
- _namingOptions = options;
- _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
+ _namingOptions = new NamingOptions();
+ _videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
@@ -2507,14 +2697,12 @@ namespace Emby.Server.Implementations.Library
public ItemLookupInfo ParseName(string name)
{
- var resolver = new VideoResolver(GetNamingOptions());
-
- var result = resolver.CleanDateTime(name);
- var cleanName = resolver.CleanString(result.Name);
+ var namingOptions = GetNamingOptions();
+ var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo
{
- Name = cleanName.Name,
+ Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
@@ -2524,19 +2712,17 @@ namespace Emby.Server.Implementations.Library
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
- var videoListResolver = new VideoListResolver(namingOptions);
-
- var videos = videoListResolver.Resolve(fileSystemChildren);
+ var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
- var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase));
+ var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
{
- files.AddRange(currentVideo.Extras.Where(i => string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+ files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
}
var resolvers = new IItemResolver[]
@@ -2549,9 +2735,7 @@ namespace Emby.Server.Implementations.Library
.Select(video =>
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
- var dbItem = GetItemById(video.Id) as Trailer;
-
- if (dbItem != null)
+ if (GetItemById(video.Id) is Trailer dbItem)
{
video = dbItem;
}
@@ -2567,26 +2751,22 @@ namespace Emby.Server.Implementations.Library
}).OrderBy(i => i.Path);
}
- private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
-
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
- var videoListResolver = new VideoListResolver(namingOptions);
-
- var videos = videoListResolver.Resolve(fileSystemChildren);
+ var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
- var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase));
+ var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null)
{
- files.AddRange(currentVideo.Extras.Where(i => !string.Equals(i.ExtraType, "trailer", StringComparison.OrdinalIgnoreCase)).Select(i => _fileSystem.GetFileInfo(i.Path)));
+ files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
}
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
@@ -2614,6 +2794,7 @@ namespace Emby.Server.Implementations.Library
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
{
+ string newPath;
if (ownerItem != null)
{
var libraryOptions = GetLibraryOptions(ownerItem);
@@ -2621,41 +2802,27 @@ namespace Emby.Server.Implementations.Library
{
foreach (var pathInfo in libraryOptions.PathInfos)
{
- if (string.IsNullOrWhiteSpace(pathInfo.Path) || string.IsNullOrWhiteSpace(pathInfo.NetworkPath))
+ if (path.TryReplaceSubPath(pathInfo.Path, pathInfo.NetworkPath, out newPath))
{
- continue;
- }
-
- var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath);
- if (substitutionResult.Item2)
- {
- return substitutionResult.Item1;
+ return newPath;
}
}
}
}
- var metadataPath = ConfigurationManager.Configuration.MetadataPath;
- var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
+ var metadataPath = _configurationManager.Configuration.MetadataPath;
+ var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
- if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
+ if (path.TryReplaceSubPath(metadataPath, metadataNetworkPath, out newPath))
{
- var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath);
- if (metadataSubstitutionResult.Item2)
- {
- return metadataSubstitutionResult.Item1;
- }
+ return newPath;
}
- foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
+ foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{
- if (!string.IsNullOrWhiteSpace(map.From))
+ if (path.TryReplaceSubPath(map.From, map.To, out newPath))
{
- var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
- if (substitutionResult.Item2)
- {
- return substitutionResult.Item1;
- }
+ return newPath;
}
}
@@ -2664,45 +2831,12 @@ namespace Emby.Server.Implementations.Library
public string SubstitutePath(string path, string from, string to)
{
- return SubstitutePathInternal(path, from, to).Item1;
- }
-
- private Tuple<string, bool> SubstitutePathInternal(string path, string from, string to)
- {
- if (string.IsNullOrWhiteSpace(path))
+ if (path.TryReplaceSubPath(from, to, out var newPath))
{
- throw new ArgumentNullException(nameof(path));
- }
- if (string.IsNullOrWhiteSpace(from))
- {
- throw new ArgumentNullException(nameof(from));
- }
- if (string.IsNullOrWhiteSpace(to))
- {
- throw new ArgumentNullException(nameof(to));
- }
-
- from = from.Trim();
- to = to.Trim();
-
- var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
- var changed = false;
-
- if (!string.Equals(newPath, path))
- {
- if (to.IndexOf('/') != -1)
- {
- newPath = newPath.Replace('\\', '/');
- }
- else
- {
- newPath = newPath.Replace('/', '\\');
- }
-
- changed = true;
+ return newPath;
}
- return new Tuple<string, bool>(newPath, changed);
+ return path;
}
private void SetExtraTypeFromFilename(Video item)
@@ -2711,35 +2845,12 @@ namespace Emby.Server.Implementations.Library
var result = resolver.GetExtraInfo(item.Path);
- if (string.Equals(result.ExtraType, "deletedscene", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.DeletedScene;
- }
- else if (string.Equals(result.ExtraType, "behindthescenes", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.BehindTheScenes;
- }
- else if (string.Equals(result.ExtraType, "interview", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Interview;
- }
- else if (string.Equals(result.ExtraType, "scene", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Scene;
- }
- else if (string.Equals(result.ExtraType, "sample", StringComparison.OrdinalIgnoreCase))
- {
- item.ExtraType = ExtraType.Sample;
- }
- else
- {
- item.ExtraType = ExtraType.Clip;
- }
+ item.ExtraType = result.ExtraType;
}
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
- return ItemRepository.GetPeople(query);
+ return _itemRepository.GetPeople(query);
}
public List<PersonInfo> GetPeople(BaseItem item)
@@ -2762,7 +2873,7 @@ namespace Emby.Server.Implementations.Library
public List<Person> GetPeopleItems(InternalPeopleQuery query)
{
- return ItemRepository.GetPeopleNames(query).Select(i =>
+ return _itemRepository.GetPeopleNames(query).Select(i =>
{
try
{
@@ -2773,23 +2884,30 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person");
return null;
}
-
}).Where(i => i != null).ToList();
}
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
- return ItemRepository.GetPeopleNames(query);
+ return _itemRepository.GetPeopleNames(query);
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
{
+ UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
+ }
+
+ /// <inheritdoc />
+ public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
+ {
if (!item.SupportsPeople)
{
return;
}
- ItemRepository.UpdatePeople(item.Id, people);
+ _itemRepository.UpdatePeople(item.Id, people);
+
+ await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2800,30 +2918,32 @@ namespace Emby.Server.Implementations.Library
{
_logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
- await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
+ await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
- item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+ await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
return item.GetImageInfo(image.Type, imageIndex);
}
- catch (HttpException ex)
+ catch (HttpRequestException ex)
{
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ if (ex.StatusCode.HasValue
+ && (ex.StatusCode.Value == HttpStatusCode.NotFound || ex.StatusCode.Value == HttpStatusCode.Forbidden))
{
continue;
}
+
throw;
}
}
// Remove this image to prevent it from retrying over and over
item.RemoveImage(image);
- item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+ await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
throw new InvalidOperationException();
}
- public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
+ public async Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2832,7 +2952,7 @@ namespace Emby.Server.Implementations.Library
name = _fileSystem.GetValidFilename(name);
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath))
@@ -2851,15 +2971,15 @@ namespace Emby.Server.Implementations.Library
}
}
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
Directory.CreateDirectory(virtualFolderPath);
- if (!string.IsNullOrEmpty(collectionType))
+ if (collectionType != null)
{
- var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+ var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
File.WriteAllBytes(path, Array.Empty<byte>());
}
@@ -2886,9 +3006,61 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
+ }
+ }
+ }
+
+ private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
+ {
+ var personsToSave = new List<BaseItem>();
+
+ foreach (var person in people)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var itemUpdateType = ItemUpdateType.MetadataDownload;
+ var saveEntity = false;
+ var personEntity = GetPerson(person.Name);
+
+ // if PresentationUniqueKey is empty it's likely a new item.
+ if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
+ {
+ personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
+ saveEntity = true;
+ }
+
+ foreach (var id in person.ProviderIds)
+ {
+ if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ personEntity.SetProviderId(id.Key, id.Value);
+ saveEntity = true;
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
+ {
+ personEntity.SetImage(
+ new ItemImageInfo
+ {
+ Path = person.ImageUrl,
+ Type = ImageType.Primary
+ },
+ 0);
+
+ saveEntity = true;
+ itemUpdateType = ItemUpdateType.ImageUpdate;
+ }
+
+ if (saveEntity)
+ {
+ personsToSave.Add(personEntity);
+ await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
+
+ CreateItems(personsToSave, null, CancellationToken.None);
}
private void StartScanInBackground()
@@ -2900,25 +3072,9 @@ namespace Emby.Server.Implementations.Library
});
}
- private static bool ValidateNetworkPath(string path)
+ public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- //if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- //{
- // // We can't validate protocol-based paths, so just allow them
- // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
- // {
- // return Directory.Exists(path);
- // }
- //}
-
- // Without native support for unc, we cannot validate this when running under mono
- return true;
- }
-
- private const string ShortcutFileExtension = ".mblink";
- public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
- {
- AddMediaPathInternal(virtualFolderName, pathInfo, true);
+ AddMediaPathInternal(virtualFolderName, mediaPath, true);
}
private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions)
@@ -2932,7 +3088,7 @@ namespace Emby.Server.Implementations.Library
if (string.IsNullOrWhiteSpace(path))
{
- throw new ArgumentNullException(nameof(path));
+ throw new ArgumentException(nameof(path));
}
if (!Directory.Exists(path))
@@ -2940,12 +3096,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The path does not exist.");
}
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
- }
-
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
@@ -2976,19 +3127,14 @@ namespace Emby.Server.Implementations.Library
}
}
- public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
+ public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath)
{
- if (pathInfo == null)
+ if (mediaPath == null)
{
- throw new ArgumentNullException(nameof(pathInfo));
- }
-
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
+ throw new ArgumentNullException(nameof(mediaPath));
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
@@ -2998,9 +3144,9 @@ namespace Emby.Server.Implementations.Library
var list = libraryOptions.PathInfos.ToList();
foreach (var originalPathInfo in list)
{
- if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal))
+ if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal))
{
- originalPathInfo.NetworkPath = pathInfo.NetworkPath;
+ originalPathInfo.NetworkPath = mediaPath.NetworkPath;
break;
}
}
@@ -3023,10 +3169,7 @@ namespace Emby.Server.Implementations.Library
{
if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal)))
{
- list.Add(new MediaPathInfo
- {
- Path = location
- });
+ list.Add(new MediaPathInfo(location));
}
}
@@ -3041,7 +3184,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(name));
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, name);
@@ -3050,7 +3193,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The media folder does not exist");
}
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
@@ -3070,7 +3213,7 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
}
}
}
@@ -3084,7 +3227,7 @@ namespace Emby.Server.Implementations.Library
var removeList = new List<NameValuePair>();
- foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
+ foreach (var contentType in _configurationManager.Configuration.ContentTypes)
{
if (string.IsNullOrWhiteSpace(contentType.Name))
{
@@ -3099,11 +3242,11 @@ namespace Emby.Server.Implementations.Library
if (removeList.Count > 0)
{
- ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
+ _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
.Except(removeList)
- .ToArray();
+ .ToArray();
- ConfigurationManager.SaveConfiguration();
+ _configurationManager.SaveConfiguration();
}
}
@@ -3114,12 +3257,13 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(mediaPath));
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
if (!Directory.Exists(virtualFolderPath))
{
- throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
+ throw new FileNotFoundException(
+ string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName));
}
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index c3082a78a..83acd8e9f 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -1,18 +1,24 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
@@ -21,15 +27,13 @@ namespace Emby.Server.Implementations.Library
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
+ private readonly IApplicationPaths _appPaths;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- private IJsonSerializer _json;
- private IApplicationPaths _appPaths;
-
- public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
+ public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
{
_mediaEncoder = mediaEncoder;
_logger = logger;
- _json = json;
_appPaths = appPaths;
}
@@ -40,15 +44,16 @@ namespace Emby.Server.Implementations.Library
var now = DateTime.UtcNow;
MediaInfo mediaInfo = null;
- var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
+ var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
if (!string.IsNullOrEmpty(cacheKey))
{
try
{
- mediaInfo = _json.DeserializeFromFile<MediaInfo>(cacheFilePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
+ mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- //_logger.LogDebug("Found cached media info");
+ // _logger.LogDebug("Found cached media info");
}
catch
{
@@ -70,20 +75,22 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000;
- mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- MediaSource = mediaSource,
- MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
- ExtractChapters = false
-
- }, cancellationToken).ConfigureAwait(false);
+ mediaInfo = await _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = mediaSource,
+ MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
+ ExtractChapters = false
+ },
+ cancellationToken).ConfigureAwait(false);
if (cacheFilePath != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- _json.SerializeToFile(mediaInfo, cacheFilePath);
+ await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
- //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+ // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -124,7 +131,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.RunTimeTicks = null;
}
- var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
+ var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
@@ -135,7 +142,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
- var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
+ var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null)
{
if (!videoStream.BitRate.HasValue)
@@ -146,17 +153,14 @@ namespace Emby.Server.Implementations.Library
{
videoStream.BitRate = 30000000;
}
-
else if (width >= 1900)
{
videoStream.BitRate = 20000000;
}
-
else if (width >= 1200)
{
videoStream.BitRate = 8000000;
}
-
else if (width >= 700)
{
videoStream.BitRate = 2000000;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 24ab8e761..351fced34 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -1,10 +1,19 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
@@ -12,32 +21,36 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
{
public class MediaSourceManager : IMediaSourceManager, IDisposable
{
+ // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
+ private const char LiveStreamIdDelimeter = '_';
+
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
+ private readonly ILogger<MediaSourceManager> _logger;
+ private readonly IUserDataManager _userDataManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILocalizationManager _localizationManager;
+ private readonly IApplicationPaths _appPaths;
+
+ private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
+ private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private IMediaSourceProvider[] _providers;
- private readonly ILogger _logger;
- private readonly IUserDataManager _userDataManager;
- private readonly Func<IMediaEncoder> _mediaEncoder;
- private ILocalizationManager _localizationManager;
- private IApplicationPaths _appPaths;
public MediaSourceManager(
IItemRepository itemRepo,
@@ -45,17 +58,15 @@ namespace Emby.Server.Implementations.Library
ILocalizationManager localizationManager,
IUserManager userManager,
ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
- IJsonSerializer jsonSerializer,
+ ILogger<MediaSourceManager> logger,
IFileSystem fileSystem,
IUserDataManager userDataManager,
- Func<IMediaEncoder> mediaEncoder)
+ IMediaEncoder mediaEncoder)
{
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
- _logger = loggerFactory.CreateLogger(nameof(MediaSourceManager));
- _jsonSerializer = jsonSerializer;
+ _logger = logger;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
_mediaEncoder = mediaEncoder;
@@ -128,18 +139,34 @@ namespace Emby.Server.Implementations.Library
return streams;
}
- public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
+ {
+ return _itemRepo.GetMediaAttachments(query);
+ }
+
+ /// <inheritdoc />
+ public List<MediaAttachment> GetMediaAttachments(Guid itemId)
+ {
+ return GetMediaAttachments(new MediaAttachmentQuery
+ {
+ ItemId = itemId
+ });
+ }
+
+ public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)
{
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
{
- await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
- {
- EnableRemoteContentProbe = true,
- MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh
-
- }, cancellationToken).ConfigureAwait(false);
+ await item.RefreshMetadata(
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ EnableRemoteContentProbe = true,
+ MetadataRefreshMode = MetadataRefreshMode.FullRefresh
+ },
+ cancellationToken).ConfigureAwait(false);
mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
}
@@ -172,15 +199,17 @@ namespace Emby.Server.Implementations.Library
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
- if (!user.Policy.EnableAudioPlaybackTranscoding)
- {
- source.SupportsTranscoding = false;
- }
+ source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
+ }
+ else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
+ source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
}
}
}
- return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
+ return SortMediaSources(list);
}
public MediaProtocol GetPathProtocol(string path)
@@ -189,22 +218,27 @@ namespace Emby.Server.Implementations.Library
{
return MediaProtocol.Rtsp;
}
+
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtmp;
}
+
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Http;
}
+
if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtp;
}
+
if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Ftp;
}
+
if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Udp;
@@ -269,7 +303,7 @@ namespace Emby.Server.Implementations.Library
private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
{
- var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter;
+ var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter;
if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
@@ -289,7 +323,7 @@ namespace Emby.Server.Implementations.Library
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
}
- var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
+ var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}
@@ -318,7 +352,7 @@ namespace Emby.Server.Implementations.Library
private string[] NormalizeLanguage(string language)
{
- if (language == null)
+ if (string.IsNullOrEmpty(language))
{
return Array.Empty<string>();
}
@@ -334,7 +368,9 @@ namespace Emby.Server.Implementations.Library
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
+ if (userData.SubtitleStreamIndex.HasValue
+ && user.RememberSubtitleSelections
+ && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
@@ -345,26 +381,25 @@ namespace Emby.Server.Implementations.Library
}
}
- var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
- ? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
+ var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
- source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
+ source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
+ source.MediaStreams,
preferredSubs,
- user.Configuration.SubtitleMode,
+ user.SubtitleMode,
audioLangage);
- MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
- user.Configuration.SubtitleMode, audioLangage);
+ MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);
}
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
+ if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
{
var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid
@@ -375,11 +410,9 @@ namespace Emby.Server.Implementations.Library
}
}
- var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
- ? Array.Empty<string>()
- : NormalizeLanguage(user.Configuration.AudioLanguagePreference);
+ var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);
- source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
+ source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
}
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
@@ -407,7 +440,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
+ private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
@@ -417,20 +450,17 @@ namespace Emby.Server.Implementations.Library
}
return 1;
-
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i =>
{
var stream = i.VideoStream;
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
+ return stream?.Width ?? 0;
})
+ .Where(i => i.Type != MediaSourceType.Placeholder)
.ToList();
}
- private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
- private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
-
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -478,7 +508,7 @@ namespace Emby.Server.Implementations.Library
// hack - these two values were taken from LiveTVMediaSourceProvider
string cacheKey = request.OpenToken;
- await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
+ await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
@@ -490,9 +520,9 @@ namespace Emby.Server.Implementations.Library
}
// TODO: @bond Fix
- var json = _jsonSerializer.SerializeToString(mediaSource);
- _logger.LogInformation("Live stream opened: " + json);
- var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
+ var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
+ _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
+ var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
if (!request.UserId.Equals(Guid.Empty))
{
@@ -503,11 +533,7 @@ namespace Emby.Server.Implementations.Library
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
}
- return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse
- {
- MediaSource = clone
-
- }, liveStream as IDirectStreamProvider);
+ return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);
}
private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)
@@ -520,7 +546,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.RunTimeTicks = null;
}
- var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
+ var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
@@ -531,7 +557,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
- var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
+ var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null)
{
if (!videoStream.BitRate.HasValue)
@@ -542,17 +568,14 @@ namespace Emby.Server.Implementations.Library
{
videoStream.BitRate = 30000000;
}
-
else if (width >= 1900)
{
videoStream.BitRate = 20000000;
}
-
else if (width >= 1200)
{
videoStream.BitRate = 8000000;
}
-
else if (width >= 700)
{
videoStream.BitRate = 2000000;
@@ -564,31 +587,6 @@ namespace Emby.Server.Implementations.Library
mediaSource.InferTotalBitrate();
}
- public async Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
- {
- await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- var info = _openStreams.Values.FirstOrDefault(i =>
- {
- var liveStream = i as ILiveStream;
- if (liveStream != null)
- {
- return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
- }
-
- return false;
- });
-
- return info as IDirectStreamProvider;
- }
- finally
- {
- _liveStreamSemaphore.Release();
- }
- }
-
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
{
var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false);
@@ -597,19 +595,21 @@ namespace Emby.Server.Implementations.Library
public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
{
- var liveStreamInfo = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false);
+ // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
+ var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
var mediaSource = liveStreamInfo.MediaSource;
if (liveStreamInfo is IDirectStreamProvider)
{
- var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
- {
- MediaSource = mediaSource,
- ExtractChapters = false,
- MediaType = DlnaProfileType.Video
-
- }, cancellationToken).ConfigureAwait(false);
+ var info = await _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = mediaSource,
+ ExtractChapters = false,
+ MediaType = DlnaProfileType.Video
+ },
+ cancellationToken).ConfigureAwait(false);
mediaSource.MediaStreams = info.MediaStreams;
mediaSource.Container = info.Container;
@@ -626,15 +626,16 @@ namespace Emby.Server.Implementations.Library
var now = DateTime.UtcNow;
MediaInfo mediaInfo = null;
- var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
+ var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
if (!string.IsNullOrEmpty(cacheKey))
{
try
{
- mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath);
+ await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
+ mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- //_logger.LogDebug("Found cached media info");
+ // _logger.LogDebug("Found cached media info");
}
catch (Exception ex)
{
@@ -656,20 +657,22 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000;
}
- mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
+ mediaInfo = await _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
{
MediaSource = mediaSource,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false
-
- }, cancellationToken).ConfigureAwait(false);
+ },
+ cancellationToken).ConfigureAwait(false);
if (cacheFilePath != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
- _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath);
+ await using FileStream createStream = File.Create(cacheFilePath);
+ await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
- //_logger.LogDebug("Saved media info to {0}", cacheFilePath);
+ // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
}
}
@@ -735,17 +738,14 @@ namespace Emby.Server.Implementations.Library
{
videoStream.BitRate = 30000000;
}
-
else if (width >= 1900)
{
videoStream.BitRate = 20000000;
}
-
else if (width >= 1200)
{
videoStream.BitRate = 8000000;
}
-
else if (width >= 700)
{
videoStream.BitRate = 2000000;
@@ -765,41 +765,37 @@ namespace Emby.Server.Implementations.Library
mediaSource.InferTotalBitrate(true);
}
- public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
+ public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
- var info = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false);
- return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider);
+ // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
+ var info = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
+ return Task.FromResult(new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider));
}
- private async Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken)
+ public ILiveStream GetLiveStreamInfo(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
- await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
+ if (_openStreams.TryGetValue(id, out ILiveStream info))
{
- if (_openStreams.TryGetValue(id, out ILiveStream info))
- {
- return info;
- }
- else
- {
- throw new ResourceNotFoundException();
- }
- }
- finally
- {
- _liveStreamSemaphore.Release();
+ return info;
}
+
+ return null;
+ }
+
+ /// <inheritdoc />
+ public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
+ {
+ return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparison.OrdinalIgnoreCase));
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
@@ -827,7 +823,7 @@ namespace Emby.Server.Implementations.Library
if (liveStream.ConsumerCount <= 0)
{
- _openStreams.Remove(id);
+ _openStreams.TryRemove(id, out _);
_logger.LogInformation("Closing live stream {0}", id);
@@ -842,24 +838,21 @@ namespace Emby.Server.Implementations.Library
}
}
- // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
- private const char LiveStreamIdDelimeter = '_';
-
- private Tuple<IMediaSourceProvider, string> GetProvider(string key)
+ private (IMediaSourceProvider, string) GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
{
- throw new ArgumentException("key");
+ throw new ArgumentException("Key can't be empty.", nameof(key));
}
- var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
+ var keys = key.Split(LiveStreamIdDelimeter, 2);
- var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
+ var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));
- var splitIndex = key.IndexOf(LiveStreamIdDelimeter);
+ var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal);
var keyId = key.Substring(splitIndex + 1);
- return new Tuple<IMediaSourceProvider, string>(provider, keyId);
+ return (provider, keyId);
}
/// <summary>
@@ -868,9 +861,9 @@ namespace Emby.Server.Implementations.Library
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
- private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
@@ -879,15 +872,12 @@ namespace Emby.Server.Implementations.Library
{
if (dispose)
{
- lock (_disposeLock)
+ foreach (var key in _openStreams.Keys.ToList())
{
- foreach (var key in _openStreams.Keys.ToList())
- {
- var task = CloseLiveStream(key);
-
- Task.WaitAll(task);
- }
+ CloseLiveStream(key).GetAwaiter().GetResult();
}
+
+ _liveStreamSemaphore.Dispose();
}
}
}
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index 0a6c8845d..b3837fedb 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -1,7 +1,11 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
-using MediaBrowser.Model.Configuration;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library
@@ -33,14 +37,12 @@ namespace Emby.Server.Implementations.Library
return null;
}
- public static int? GetDefaultSubtitleStreamIndex(List<MediaStream> streams,
+ public static int? GetDefaultSubtitleStreamIndex(
+ IEnumerable<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
{
- streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
- .ToList();
-
MediaStream stream = null;
if (mode == SubtitlePlaybackMode.None)
@@ -48,57 +50,53 @@ namespace Emby.Server.Implementations.Library
return null;
}
+ var sortedStreams = streams
+ .Where(i => i.Type == MediaStreamType.Subtitle)
+ .OrderByDescending(x => x.IsExternal)
+ .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
+ .ThenByDescending(x => x.IsForced)
+ .ThenByDescending(x => x.IsDefault)
+ .ToList();
+
if (mode == SubtitlePlaybackMode.Default)
{
// Prefer embedded metadata over smart logic
-
- stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ??
- streams.FirstOrDefault(s => s.IsForced) ??
- streams.FirstOrDefault(s => s.IsDefault);
+ stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
{
- stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
+ stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Smart)
{
- // Prefer smart logic over embedded metadata
-
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
{
- stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
+ stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
}
}
else if (mode == SubtitlePlaybackMode.Always)
{
// always load the most suitable full subtitles
- stream = streams.FirstOrDefault(s => !s.IsForced);
+ stream = sortedStreams.FirstOrDefault(s => !s.IsForced);
}
else if (mode == SubtitlePlaybackMode.OnlyForced)
{
// always load the most suitable full subtitles
- stream = streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ??
- streams.FirstOrDefault(s => s.IsForced);
+ stream = sortedStreams.FirstOrDefault(x => x.IsForced);
}
// load forced subs if we have found no suitable full subtitles
- stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
-
- if (stream != null)
- {
- return stream.Index;
- }
-
- return null;
+ stream ??= sortedStreams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
+ return stream?.Index;
}
private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, string[] languagePreferences)
{
- // Give some preferance to external text subs for better performance
+ // Give some preference to external text subs for better performance
return streams.Where(i => i.Type == type)
.OrderBy(i =>
{
@@ -113,7 +111,8 @@ namespace Emby.Server.Implementations.Library
.ThenBy(i => i.Index);
}
- public static void SetSubtitleStreamScores(List<MediaStream> streams,
+ public static void SetSubtitleStreamScores(
+ List<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 10602fea7..e2f1fb0ad 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -1,13 +1,19 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
namespace Emby.Server.Implementations.Library
{
@@ -30,9 +36,10 @@ namespace Emby.Server.Implementations.Library
return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
}
- public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
+ /// <inheritdoc />
+ public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions)
{
- return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
+ return GetInstantMixFromGenres(artist.Genres, user, dtoOptions);
}
public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
@@ -45,7 +52,7 @@ namespace Emby.Server.Implementations.Library
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { typeof(Audio).Name },
+ IncludeItemTypes = new[] { nameof(Audio) },
DtoOptions = dtoOptions
})
.Cast<Audio>()
@@ -73,7 +80,6 @@ namespace Emby.Server.Implementations.Library
{
return Guid.Empty;
}
-
}).Where(i => !i.Equals(Guid.Empty)).ToArray();
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
@@ -83,53 +89,46 @@ namespace Emby.Server.Implementations.Library
{
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { typeof(Audio).Name },
+ IncludeItemTypes = new[] { nameof(Audio) },
GenreIds = genreIds.ToArray(),
Limit = 200,
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) },
+ OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
DtoOptions = dtoOptions
-
});
}
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
- var genre = item as MusicGenre;
- if (genre != null)
+ if (item is MusicGenre genre)
{
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
}
- var playlist = item as Playlist;
- if (playlist != null)
+ if (item is Playlist playlist)
{
return GetInstantMixFromPlaylist(playlist, user, dtoOptions);
}
- var album = item as MusicAlbum;
- if (album != null)
+ if (item is MusicAlbum album)
{
return GetInstantMixFromAlbum(album, user, dtoOptions);
}
- var artist = item as MusicArtist;
- if (artist != null)
+ if (item is MusicArtist artist)
{
return GetInstantMixFromArtist(artist, user, dtoOptions);
}
- var song = item as Audio;
- if (song != null)
+ if (item is Audio song)
{
return GetInstantMixFromSong(song, user, dtoOptions);
}
- var folder = item as Folder;
- if (folder != null)
+ if (item is Folder folder)
{
return GetInstantMixFromFolder(folder, user, dtoOptions);
}
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index d3a81f622..d5b855cdf 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -1,45 +1,117 @@
using System;
-using System.Text.RegularExpressions;
+using System.Diagnostics.CodeAnalysis;
+using MediaBrowser.Common.Providers;
namespace Emby.Server.Implementations.Library
{
+ /// <summary>
+ /// Class providing extension methods for working with paths.
+ /// </summary>
public static class PathExtensions
{
/// <summary>
/// Gets the attribute value.
/// </summary>
/// <param name="str">The STR.</param>
- /// <param name="attrib">The attrib.</param>
+ /// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">attrib</exception>
- public static string GetAttributeValue(this string str, string attrib)
+ /// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
+ public static string? GetAttributeValue(this string str, string attribute)
{
- if (string.IsNullOrEmpty(str))
+ if (str.Length == 0)
{
- throw new ArgumentNullException(nameof(str));
+ throw new ArgumentException("String can't be empty.", nameof(str));
}
- if (string.IsNullOrEmpty(attrib))
+ if (attribute.Length == 0)
{
- throw new ArgumentNullException(nameof(attrib));
+ throw new ArgumentException("String can't be empty.", nameof(attribute));
}
- string srch = "[" + attrib + "=";
+ string srch = "[" + attribute + "=";
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
- if (start > -1)
+ if (start != -1)
{
start += srch.Length;
int end = str.IndexOf(']', start);
return str.Substring(start, end - start);
}
+
// for imdbid we also accept pattern matching
- if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
{
- var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
- return m.Success ? m.Value : null;
+ var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
+ return match ? imdbId.ToString() : null;
}
return null;
}
+
+ /// <summary>
+ /// Replaces a sub path with another sub path and normalizes the final path.
+ /// </summary>
+ /// <param name="path">The original path.</param>
+ /// <param name="subPath">The original sub path.</param>
+ /// <param name="newSubPath">The new sub path.</param>
+ /// <param name="newPath">The result of the sub path replacement.</param>
+ /// <returns>The path after replacing the sub path.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
+ public static bool TryReplaceSubPath(
+ [NotNullWhen(true)] this string? path,
+ [NotNullWhen(true)] string? subPath,
+ [NotNullWhen(true)] string? newSubPath,
+ [NotNullWhen(true)] out string? newPath)
+ {
+ newPath = null;
+
+ if (string.IsNullOrEmpty(path)
+ || string.IsNullOrEmpty(subPath)
+ || string.IsNullOrEmpty(newSubPath)
+ || subPath.Length > path.Length)
+ {
+ return false;
+ }
+
+ char oldDirectorySeparatorChar;
+ char newDirectorySeparatorChar;
+ // True normalization is still not possible https://github.com/dotnet/runtime/issues/2162
+ // The reasoning behind this is that a forward slash likely means it's a Linux path and
+ // so the whole path should be normalized to use / and vice versa for Windows (although Windows doesn't care much).
+ if (newSubPath.Contains('/', StringComparison.Ordinal))
+ {
+ oldDirectorySeparatorChar = '\\';
+ newDirectorySeparatorChar = '/';
+ }
+ else
+ {
+ oldDirectorySeparatorChar = '/';
+ newDirectorySeparatorChar = '\\';
+ }
+
+ path = path.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+ subPath = subPath.Replace(oldDirectorySeparatorChar, newDirectorySeparatorChar);
+
+ // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
+ // when the sub path matches a similar but in-complete subpath
+ var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
+ if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (path.Length > subPath.Length
+ && !oldSubPathEndsWithSeparator
+ && path[subPath.Length] != newDirectorySeparatorChar)
+ {
+ return false;
+ }
+
+ var newSubPathTrimmed = newSubPath.AsSpan().TrimEnd(newDirectorySeparatorChar);
+ // Ensure that the path with the old subpath removed starts with a leading dir separator
+ int idx = oldSubPathEndsWithSeparator ? subPath.Length - 1 : subPath.Length;
+ newPath = string.Concat(newSubPathTrimmed, path.AsSpan(idx));
+
+ return true;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs
index 96d1bff92..ac75e5d3a 100644
--- a/Emby.Server.Implementations/Library/ResolverHelper.cs
+++ b/Emby.Server.Implementations/Library/ResolverHelper.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Class ResolverHelper
+ /// Class ResolverHelper.
/// </summary>
public static class ResolverHelper
{
@@ -18,11 +18,10 @@ namespace Emby.Server.Implementations.Library
/// </summary>
/// <param name="item">The item.</param>
/// <param name="parent">The parent.</param>
- /// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="directoryService">The directory service.</param>
- /// <exception cref="ArgumentException">Item must have a path</exception>
- public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService)
+ /// <exception cref="ArgumentException">Item must have a path.</exception>
+ public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
{
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set
if (string.IsNullOrEmpty(item.Path))
@@ -43,9 +42,14 @@ namespace Emby.Server.Implementations.Library
// Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path);
- SetDateCreated(item, fileSystem, fileInfo);
+ if (fileInfo == null)
+ {
+ throw new FileNotFoundException("Can't find item path.", item.Path);
+ }
- EnsureName(item, item.Path, fileInfo);
+ SetDateCreated(item, fileInfo);
+
+ EnsureName(item, fileInfo);
}
/// <summary>
@@ -72,9 +76,9 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// Make sure the item has a name
- EnsureName(item, item.Path, args.FileInfo);
+ EnsureName(item, args.FileInfo);
- item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
+ item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values
@@ -84,49 +88,23 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Ensures the name.
/// </summary>
- private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo)
+ private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
{
// If the subclass didn't supply a name, add it here
- if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath))
+ if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
{
- var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name;
-
- item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
+ item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
}
}
/// <summary>
- /// Gets the display name.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
- /// <returns>System.String.</returns>
- private static string GetDisplayName(string path, bool isDirectory)
- {
- return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
- }
-
- /// <summary>
- /// Ensures DateCreated and DateModified have values
+ /// Ensures DateCreated and DateModified have values.
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{
- if (fileSystem == null)
- {
- throw new ArgumentNullException(nameof(fileSystem));
- }
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
- if (args == null)
- {
- throw new ArgumentNullException(nameof(args));
- }
-
// See if a different path came out of the resolver than what went in
if (!fileSystem.AreEqual(args.Path, item.Path))
{
@@ -134,7 +112,7 @@ namespace Emby.Server.Implementations.Library
if (childData != null)
{
- SetDateCreated(item, fileSystem, childData);
+ SetDateCreated(item, childData);
}
else
{
@@ -142,17 +120,17 @@ namespace Emby.Server.Implementations.Library
if (fileData.Exists)
{
- SetDateCreated(item, fileSystem, fileData);
+ SetDateCreated(item, fileData);
}
}
}
else
{
- SetDateCreated(item, fileSystem, args.FileInfo);
+ SetDateCreated(item, args.FileInfo);
}
}
- private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info)
+ private static void SetDateCreated(BaseItem item, FileSystemMetadata? info)
{
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
@@ -161,7 +139,7 @@ namespace Emby.Server.Implementations.Library
// directoryService.getFile may return null
if (info != null)
{
- var dateCreated = fileSystem.GetCreationTimeUtc(info);
+ var dateCreated = info.CreationTimeUtc;
if (dateCreated.Equals(DateTime.MinValue))
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index e39192d28..fd9747b4b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -1,3 +1,7 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -13,24 +17,25 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
- /// Class AudioResolver
+ /// Class AudioResolver.
/// </summary>
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
{
- private readonly ILibraryManager LibraryManager;
+ private readonly ILibraryManager _libraryManager;
public AudioResolver(ILibraryManager libraryManager)
{
- LibraryManager = libraryManager;
+ _libraryManager = libraryManager;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
- public override ResolverPriority Priority => ResolverPriority.Fourth;
+ public override ResolverPriority Priority => ResolverPriority.Fifth;
- public MultiItemResolverResult ResolveMultiple(Folder parent,
+ public MultiItemResolverResult ResolveMultiple(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -48,7 +53,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return result;
}
- private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
+ private MultiItemResolverResult ResolveMultipleInternal(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -70,7 +76,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
// Return audio if the path is a file and has a matching extension
- var libraryOptions = args.GetLibraryOptions();
var collectionType = args.GetCollectionType();
var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
@@ -83,13 +88,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
- if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
+ if (_libraryManager.IsAudioFile(args.Path))
{
var extension = Path.GetExtension(args.Path);
@@ -102,7 +107,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
- if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions))
+ if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path))
{
return null;
}
@@ -118,7 +123,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
item = new MediaBrowser.Controller.Entities.Audio.Audio();
}
-
else if (isBooksCollectionType)
{
item = new AudioBook();
@@ -178,7 +182,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new AudioBookListResolver(namingOptions);
var resolverResult = resolver.Resolve(files).ToList();
@@ -199,7 +203,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
continue;
}
- var firstMedia = resolvedItem.Files.First();
+ if (resolvedItem.Files.Count == 0)
+ {
+ continue;
+ }
+
+ var firstMedia = resolvedItem.Files[0];
var libraryItem = new T
{
@@ -209,8 +218,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
Name = parseName ?
resolvedItem.Name :
Path.GetFileNameWithoutExtension(firstMedia.Path),
- //AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
- //LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
+ // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
+ // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
};
result.Items.Add(libraryItem);
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 3ce1da81a..60720dd2f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -1,11 +1,15 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Emby.Naming.Audio;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -13,15 +17,21 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
- /// Class MusicAlbumResolver
+ /// Class MusicAlbumResolver.
/// </summary>
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
- private readonly ILogger _logger;
+ private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
- public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -32,7 +42,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
- public override ResolverPriority Priority => ResolverPriority.Second;
+ public override ResolverPriority Priority => ResolverPriority.Third;
/// <summary>
/// Resolves the specified args.
@@ -50,26 +60,38 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null;
}
- if (!args.IsDirectory) return null;
+ if (!args.IsDirectory)
+ {
+ return null;
+ }
// Avoid mis-identifying top folders
- if (args.HasParent<MusicAlbum>()) return null;
- if (args.Parent.IsRoot) return null;
+ if (args.HasParent<MusicAlbum>())
+ {
+ return null;
+ }
+
+ if (args.Parent.IsRoot)
+ {
+ return null;
+ }
return IsMusicAlbum(args) ? new MusicAlbum() : null;
}
-
/// <summary>
- /// Determine if the supplied file data points to a music album
+ /// Determine if the supplied file data points to a music album.
/// </summary>
- public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
+ /// <param name="path">The path to check.</param>
+ /// <param name="directoryService">The directory service.</param>
+ /// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
+ public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
- return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager);
+ return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
}
/// <summary>
- /// Determine if the supplied resolve args should be considered a music album
+ /// Determine if the supplied resolve args should be considered a music album.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
@@ -78,83 +100,74 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
// Args points to an album if parent is an Artist folder or it directly contains music
if (args.IsDirectory)
{
- //if (args.Parent is MusicArtist) return true; //saves us from testing children twice
- if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager)) return true;
+ // if (args.Parent is MusicArtist) return true; // saves us from testing children twice
+ if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
+ {
+ return true;
+ }
}
return false;
}
/// <summary>
- /// Determine if the supplied list contains what we should consider music
+ /// Determine if the supplied list contains what we should consider music.
/// </summary>
- private bool ContainsMusic(IEnumerable<FileSystemMetadata> list,
+ private bool ContainsMusic(
+ IEnumerable<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService,
- ILogger logger,
+ ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
- LibraryOptions libraryOptions,
ILibraryManager libraryManager)
{
+ // check for audio files before digging down into directories
+ var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName));
+ if (foundAudioFile)
+ {
+ // at least one audio file exists
+ return true;
+ }
+
+ if (!allowSubfolders)
+ {
+ // not music since no audio file exists and we're not looking into subfolders
+ return false;
+ }
+
var discSubfolderCount = 0;
- var notMultiDisc = false;
- foreach (var fileSystemInfo in list)
+ var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
+ var parser = new AlbumParser(namingOptions);
+
+ var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
+
+ var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
- if (fileSystemInfo.IsDirectory)
+ var path = fileSystemInfo.FullName;
+ var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
+
+ if (hasMusic)
{
- if (allowSubfolders)
+ if (parser.IsMultiPart(path))
{
- if (notMultiDisc)
- {
- continue;
- }
-
- var path = fileSystemInfo.FullName;
- var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
-
- if (hasMusic)
- {
- if (IsMultiDiscFolder(path, libraryOptions))
- {
- logger.LogDebug("Found multi-disc folder: " + path);
- discSubfolderCount++;
- }
- else
- {
- // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
- notMultiDisc = true;
- }
- }
+ logger.LogDebug("Found multi-disc folder: " + path);
+ Interlocked.Increment(ref discSubfolderCount);
}
- }
- else
- {
- var fullName = fileSystemInfo.FullName;
-
- if (libraryManager.IsAudioFile(fullName, libraryOptions))
+ else
{
- return true;
+ // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
+ state.Stop();
}
}
- }
+ });
- if (notMultiDisc)
+ if (!result.IsCompleted)
{
return false;
}
return discSubfolderCount > 0;
}
-
- private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
- {
- var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
-
- var parser = new AlbumParser(namingOptions);
- var result = parser.ParseMultiPart(path);
-
- return result.IsMultiPart;
- }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 74e9b8304..3d2ae95d2 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -1,5 +1,8 @@
+#nullable disable
+
using System;
using System.Linq;
+using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -11,16 +14,27 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
- /// Class MusicArtistResolver
+ /// Class MusicArtistResolver.
/// </summary>
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
- private readonly ILogger _logger;
+ private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
- public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
+ /// </summary>
+ /// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="config">The configuration manager.</param>
+ public MusicArtistResolver(
+ ILogger<MusicAlbumResolver> logger,
+ IFileSystem fileSystem,
+ ILibraryManager libraryManager,
+ IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -41,7 +55,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <returns>MusicArtist.</returns>
protected override MusicArtist Resolve(ItemResolveArgs args)
{
- if (!args.IsDirectory) return null;
+ if (!args.IsDirectory)
+ {
+ return null;
+ }
// Don't allow nested artists
if (args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
@@ -64,21 +81,29 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return new MusicArtist();
}
- if (_config.Configuration.EnableSimpleArtistDetection)
+ // Avoid mis-identifying top folders
+ if (args.Parent.IsRoot)
{
return null;
}
- // Avoid mis-identifying top folders
- if (args.Parent.IsRoot) return null;
-
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
// If we contain an album assume we are an artist folder
- return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
- }
+ var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
+ var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
+ {
+ if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService))
+ {
+ // stop once we see a music album
+ state.Stop();
+ }
+ });
+
+ return !result.IsCompleted ? new MusicArtist() : null;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 541b13cbe..9ff99fa43 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -1,6 +1,11 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Linq;
+using DiscUtils.Udf;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -10,25 +15,25 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
- /// Resolves a Path into a Video or Video subclass
+ /// Resolves a Path into a Video or Video subclass.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
- protected readonly ILibraryManager LibraryManager;
-
protected BaseVideoResolver(ILibraryManager libraryManager)
{
LibraryManager = libraryManager;
}
+ protected ILibraryManager LibraryManager { get; }
+
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
- protected override T Resolve(ItemResolveArgs args)
+ public override T Resolve(ItemResolveArgs args)
{
return ResolveVideo<T>(args, false);
}
@@ -40,14 +45,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <param name="args">The args.</param>
/// <param name="parseName">if set to <c>true</c> [parse name].</param>
/// <returns>``0.</returns>
- protected TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
+ protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = LibraryManager.GetNamingOptions();
// If the path is a file check for a matching extensions
- var parser = new VideoResolver(namingOptions);
-
if (args.IsDirectory)
{
TVideoType video = null;
@@ -62,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -77,9 +80,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
};
break;
}
- if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
+
+ if (IsBluRayDirectory(filename))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -97,7 +101,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else if (IsDvdFile(filename))
{
- videoInfo = parser.ResolveDirectory(args.Path);
+ videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -127,14 +131,14 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else
{
- var videoInfo = parser.Resolve(args.Path, false, false);
+ var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
if (videoInfo == null)
{
return null;
}
- if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub)
+ if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
{
var path = args.Path;
@@ -162,13 +166,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
{
- var extension = Path.GetExtension(video.Path);
- video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
- VideoType.Iso :
- VideoType.VideoFile;
+ var extension = Path.GetExtension(video.Path.AsSpan());
+ video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
+ || extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
+ ? VideoType.Iso
+ : VideoType.VideoFile;
- video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
+ video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
video.IsPlaceHolder = videoInfo.IsStub;
if (videoInfo.IsStub)
@@ -190,14 +194,30 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (video.VideoType == VideoType.Iso)
{
- if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
+ if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
{
video.IsoType = IsoType.Dvd;
}
- else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1)
+ else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
{
video.IsoType = IsoType.BluRay;
}
+ else
+ {
+ // use disc-utils, both DVDs and BDs use UDF filesystem
+ using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
+ {
+ UdfReader udfReader = new UdfReader(videoFileStream);
+ if (udfReader.DirectoryExists("VIDEO_TS"))
+ {
+ video.IsoType = IsoType.Dvd;
+ }
+ else if (udfReader.DirectoryExists("BDMV"))
+ {
+ video.IsoType = IsoType.BluRay;
+ }
+ }
+ }
}
}
@@ -247,10 +267,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video)
{
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
-
- var resolver = new Format3DParser(namingOptions);
- var result = resolver.Parse(video.Path);
+ var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions());
Set3DFormat(video, result.Is3D, result.Format3D);
}
@@ -258,6 +275,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary>
/// Determines whether [is DVD directory] [the specified directory name].
/// </summary>
+ /// <param name="fullPath">The full path of the directory.</param>
+ /// <param name="directoryName">The name of the directory.</param>
+ /// <param name="directoryService">The directory service.</param>
+ /// <returns><c>true</c> if the provided directory is a DVD directory, <c>false</c> otherwise.</returns>
protected bool IsDvdDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
{
if (!string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase))
@@ -279,25 +300,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
/// <summary>
- /// Determines whether [is blu ray directory] [the specified directory name].
+ /// Determines whether [is bluray directory] [the specified directory name].
/// </summary>
- protected bool IsBluRayDirectory(string fullPath, string directoryName, IDirectoryService directoryService)
+ /// <param name="directoryName">The directory name.</param>
+ /// <returns>Whether the directory is a bluray directory.</returns>
+ protected bool IsBluRayDirectory(string directoryName)
{
- if (!string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
-
- return true;
- //var blurayExtensions = new[]
- //{
- // ".mts",
- // ".m2ts",
- // ".bdmv",
- // ".mpls"
- //};
-
- //return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase));
+ return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index f22554ee5..e685c87f1 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -1,3 +1,7 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Linq;
@@ -7,25 +11,19 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers.Books
{
- /// <summary>
- ///
- /// </summary>
public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver<Book>
{
- private readonly string[] _validExtensions = { ".pdf", ".epub", ".mobi", ".cbr", ".cbz", ".azw3" };
+ private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
- /// <summary>
- ///
- /// </summary>
- /// <param name="args"></param>
- /// <returns></returns>
- protected override Book Resolve(ItemResolveArgs args)
+ public override Book Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
// Only process items that are in a collection folder containing books
if (!string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
+ {
return null;
+ }
if (args.IsDirectory)
{
@@ -47,26 +45,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
return null;
}
- /// <summary>
- ///
- /// </summary>
- /// <param name="args"></param>
- /// <returns></returns>
private Book GetBook(ItemResolveArgs args)
{
var bookFiles = args.FileSystemChildren.Where(f =>
{
- var fileExtension = Path.GetExtension(f.FullName) ??
- string.Empty;
+ var fileExtension = Path.GetExtension(f.FullName)
+ ?? string.Empty;
- return _validExtensions.Contains(fileExtension,
- StringComparer
- .OrdinalIgnoreCase);
+ return _validExtensions.Contains(
+ fileExtension,
+ StringComparer.OrdinalIgnoreCase);
}).ToList();
// Don't return a Book if there is more (or less) than one document in the directory
if (bookFiles.Count != 1)
+ {
return null;
+ }
return new Book
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
index e48b6c967..db7703cd6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -5,9 +7,9 @@ using MediaBrowser.Controller.Resolvers;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
- /// Class FolderResolver
+ /// Class FolderResolver.
/// </summary>
- public class FolderResolver : FolderResolver<Folder>
+ public class FolderResolver : GenericFolderResolver<Folder>
{
/// <summary>
/// Gets the priority.
@@ -30,24 +32,4 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
}
-
- /// <summary>
- /// Class FolderResolver
- /// </summary>
- /// <typeparam name="TItemType">The type of the T item type.</typeparam>
- public abstract class FolderResolver<TItemType> : ItemResolver<TItemType>
- where TItemType : Folder, new()
- {
- /// <summary>
- /// Sets the initial item values.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="args">The args.</param>
- protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args)
- {
- base.SetInitialItemValues(item, args);
-
- item.IsRoot = args.Parent == null;
- }
- }
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs
new file mode 100644
index 000000000..f109a5e9a
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericFolderResolver.cs
@@ -0,0 +1,27 @@
+#nullable disable
+
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Class FolderResolver.
+ /// </summary>
+ /// <typeparam name="TItemType">The type of the T item type.</typeparam>
+ public abstract class GenericFolderResolver<TItemType> : ItemResolver<TItemType>
+ where TItemType : Folder, new()
+ {
+ /// <summary>
+ /// Sets the initial item values.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="args">The args.</param>
+ protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args)
+ {
+ base.SetInitialItemValues(item, args);
+
+ item.IsRoot = args.Parent == null;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index 68d5d8b2d..9599faea4 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -1,3 +1,7 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
index a6db40714..3f29ab191 100644
--- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
@@ -5,13 +7,19 @@ using MediaBrowser.Controller.Resolvers;
namespace Emby.Server.Implementations.Library.Resolvers
{
/// <summary>
- /// Class ItemResolver
+ /// Class ItemResolver.
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="T">The type of BaseItem.</typeparam>
public abstract class ItemResolver<T> : IItemResolver
where T : BaseItem, new()
{
/// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public virtual ResolverPriority Priority => ResolverPriority.First;
+
+ /// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
@@ -22,13 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
/// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- public virtual ResolverPriority Priority => ResolverPriority.First;
-
- /// <summary>
- /// Sets initial values on the newly resolved item
+ /// Sets initial values on the newly resolved item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
index 922bd4bbb..e7abe1e6d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs
@@ -1,17 +1,18 @@
+#nullable disable
+
using System;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
/// <summary>
- /// Class BoxSetResolver
+ /// Class BoxSetResolver.
/// </summary>
- public class BoxSetResolver : FolderResolver<BoxSet>
+ public class BoxSetResolver : GenericFolderResolver<BoxSet>
{
/// <summary>
/// Resolves the specified args.
@@ -63,14 +64,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <param name="item">The item.</param>
private static void SetProviderIdFromPath(BaseItem item)
{
- //we need to only look at the name of this actual item (not parents)
+ // we need to only look at the name of this actual item (not parents)
var justName = Path.GetFileName(item.Path);
var id = justName.GetAttributeValue("tmdbid");
if (!string.IsNullOrEmpty(id))
{
- item.SetProviderId(MetadataProviders.Tmdb, id);
+ item.SetProviderId(MetadataProvider.Tmdb, id);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 848563679..f3b6ef0a2 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -1,8 +1,12 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using Emby.Naming.Video;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -11,23 +15,46 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
/// <summary>
- /// Class MovieResolver
+ /// Class MovieResolver.
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
+ private readonly IImageProcessor _imageProcessor;
+
+ private string[] _validCollectionTypes = new[]
+ {
+ CollectionType.Movies,
+ CollectionType.HomeVideos,
+ CollectionType.MusicVideos,
+ CollectionType.Movies,
+ CollectionType.Photos
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MovieResolver"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="imageProcessor">The image processor.</param>
+ public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
+ : base(libraryManager)
+ {
+ _imageProcessor = imageProcessor;
+ }
+
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
- public override ResolverPriority Priority => ResolverPriority.Third;
+ public override ResolverPriority Priority => ResolverPriority.Fourth;
- public MultiItemResolverResult ResolveMultiple(Folder parent,
+ /// <inheritdoc />
+ public MultiItemResolverResult ResolveMultiple(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -45,7 +72,112 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
- private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
+ /// <summary>
+ /// Resolves the specified args.
+ /// </summary>
+ /// <param name="args">The args.</param>
+ /// <returns>Video.</returns>
+ public override Video Resolve(ItemResolveArgs args)
+ {
+ var collectionType = args.GetCollectionType();
+
+ // Find movies with their own folders
+ if (args.IsDirectory)
+ {
+ if (IsInvalid(args.Parent, collectionType))
+ {
+ return null;
+ }
+
+ var files = args.FileSystemChildren
+ .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .ToList();
+
+ if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ }
+
+ if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ }
+
+ if (string.IsNullOrEmpty(collectionType))
+ {
+ // Owned items will be caught by the plain video resolver
+ if (args.Parent == null)
+ {
+ // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ return null;
+ }
+
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
+ {
+ return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
+ }
+ }
+
+ if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
+ }
+
+ return null;
+ }
+
+ // Handle owned items
+ if (args.Parent == null)
+ {
+ return base.Resolve(args);
+ }
+
+ if (IsInvalid(args.Parent, collectionType))
+ {
+ return null;
+ }
+
+ Video item = null;
+
+ if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<MusicVideo>(args, false);
+ }
+
+ // To find a movie file, the collection type must be movies or boxsets
+ else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<Movie>(args, true);
+ }
+ else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<Video>(args, false);
+ }
+ else if (string.IsNullOrEmpty(collectionType))
+ {
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
+ item = ResolveVideo<Video>(args, false);
+ }
+
+ if (item != null)
+ {
+ item.IsInMixedFolder = true;
+ }
+
+ return item;
+ }
+
+ private MultiItemResolverResult ResolveMultipleInternal(
+ Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
@@ -90,7 +222,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null;
}
- private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
+ private MultiItemResolverResult ResolveVideos<T>(
+ Folder parent,
+ IEnumerable<FileSystemMetadata> fileSystemEntries,
+ IDirectoryService directoryService,
+ bool suppportMultiEditions,
+ string collectionType,
+ bool parseName)
where T : Video, new()
{
var files = new List<FileSystemMetadata>();
@@ -103,8 +241,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
if (string.IsNullOrEmpty(collectionType))
{
- if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
{
return null;
}
@@ -114,20 +252,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
leftOver.Add(child);
}
- else if (IsIgnored(child.Name))
- {
-
- }
- else
+ else if (!IsIgnored(child.Name))
{
files.Add(child);
}
}
- var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+ var namingOptions = LibraryManager.GetNamingOptions();
- var resolver = new VideoListResolver(namingOptions);
- var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
+ var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
var result = new MultiItemResolverResult
{
@@ -139,7 +272,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
foreach (var video in resolverResult)
{
- var firstVideo = video.Files.First();
+ var firstVideo = video.Files[0];
var videoItem = new T
{
@@ -167,17 +300,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static bool IsIgnored(string filename)
{
// Ignore samples
- var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("-", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("_", " ", StringComparison.OrdinalIgnoreCase)
- .Replace("!", " ", StringComparison.OrdinalIgnoreCase);
-
- if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
+ Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
- return false;
+ return m.Success;
}
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
@@ -198,111 +323,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
/// <summary>
- /// Resolves the specified args.
- /// </summary>
- /// <param name="args">The args.</param>
- /// <returns>Video.</returns>
- protected override Video Resolve(ItemResolveArgs args)
- {
- var collectionType = args.GetCollectionType();
-
- // Find movies with their own folders
- if (args.IsDirectory)
- {
- if (IsInvalid(args.Parent, collectionType))
- {
- return null;
- }
-
- var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
- .ToList();
-
- if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
- }
-
- if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
- }
-
- if (string.IsNullOrEmpty(collectionType))
- {
- // Owned items will be caught by the plain video resolver
- if (args.Parent == null)
- {
- //return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
- return null;
- }
-
- if (args.HasParent<Series>())
- {
- return null;
- }
-
- {
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
- }
- }
-
- if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
- }
-
- return null;
- }
-
- // Handle owned items
- if (args.Parent == null)
- {
- return base.Resolve(args);
- }
-
- if (IsInvalid(args.Parent, collectionType))
- {
- return null;
- }
-
- Video item = null;
-
- if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<MusicVideo>(args, false);
- }
-
- // To find a movie file, the collection type must be movies or boxsets
- else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<Movie>(args, true);
- }
-
- else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<Video>(args, false);
- }
- else if (string.IsNullOrEmpty(collectionType))
- {
- if (args.HasParent<Series>())
- {
- return null;
- }
-
- item = ResolveVideo<Video>(args, false);
- }
-
- if (item != null)
- {
- item.IsInMixedFolder = true;
- }
-
- return item;
- }
-
- /// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
@@ -322,7 +342,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
if (item is Movie || item is MusicVideo)
{
- //we need to only look at the name of this actual item (not parents)
+ // We need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
if (!string.IsNullOrEmpty(justName))
@@ -332,7 +352,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrWhiteSpace(tmdbid))
{
- item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
+ item.SetProviderId(MetadataProvider.Tmdb, tmdbid);
}
}
@@ -343,23 +363,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (!string.IsNullOrWhiteSpace(imdbid))
{
- item.SetProviderId(MetadataProviders.Imdb, imdbid);
+ item.SetProviderId(MetadataProvider.Imdb, imdbid);
}
}
}
}
/// <summary>
- /// Finds a movie based on a child file system entries
+ /// Finds a movie based on a child file system entries.
/// </summary>
- /// <typeparam name="T"></typeparam>
/// <returns>Movie.</returns>
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : Video, new()
{
var multiDiscFolders = new List<FileSystemMetadata>();
- var libraryOptions = args.GetLibraryOptions();
+ var libraryOptions = args.LibraryOptions;
var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos;
var photos = new List<FileSystemMetadata>();
@@ -380,7 +399,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Set3DFormat(movie);
return movie;
}
- if (IsBluRayDirectory(child.FullName, filename, directoryService))
+
+ if (IsBluRayDirectory(filename))
{
var movie = new T
{
@@ -410,15 +430,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
// TODO: Allow GetMultiDiscMovie in here
- const bool supportsMultiVersion = true;
+ const bool SupportsMultiVersion = true;
- var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
+ var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
- var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, libraryOptions, videoPath, i.Name));
+ var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
if (!hasPhotos)
{
@@ -428,8 +448,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie;
}
}
-
- if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
+ else if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
}
@@ -440,7 +459,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Gets the multi disc movie.
/// </summary>
- /// <typeparam name="T"></typeparam>
/// <param name="multiDiscFolders">The folders.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns>``0.</returns>
@@ -454,7 +472,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var subFileEntries = directoryService.GetFileSystemEntries(i);
var subfolders = subFileEntries
- .Where(e => e.IsDirectory)
+ .Where(e => e.IsDirectory)
.ToList();
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
@@ -462,7 +480,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
videoTypes.Add(VideoType.Dvd);
return true;
}
- if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
+
+ if (subfolders.Any(s => IsBluRayDirectory(s.Name)))
{
videoTypes.Add(VideoType.BluRay);
return true;
@@ -479,7 +498,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
return false;
-
}).OrderBy(i => i).ToList();
// If different video types were found, don't allow this
@@ -494,24 +512,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
- var resolver = new StackResolver(namingOptions);
- var result = resolver.ResolveDirectories(folderPaths);
+ var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
- if (result.Stacks.Count != 1)
+ if (result.Count != 1)
{
return null;
}
+ int additionalPartsLen = folderPaths.Count - 1;
+ var additionalParts = new string[additionalPartsLen];
+ folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen);
+
var returnVideo = new T
{
Path = folderPaths[0],
-
- AdditionalParts = folderPaths.Skip(1).ToArray(),
-
+ AdditionalParts = additionalParts,
VideoType = videoTypes[0],
-
- Name = result.Stacks[0].Name
+ Name = result[0].Name
};
SetIsoType(returnVideo);
@@ -519,16 +537,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return returnVideo;
}
- private string[] ValidCollectionTypes = new[]
- {
- CollectionType.Movies,
- CollectionType.HomeVideos,
- CollectionType.MusicVideos,
- CollectionType.Movies,
- CollectionType.Photos
- };
-
- private bool IsInvalid(Folder parent, string collectionType)
+ private bool IsInvalid(Folder parent, ReadOnlySpan<char> collectionType)
{
if (parent != null)
{
@@ -538,20 +547,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
- if (string.IsNullOrEmpty(collectionType))
+ if (collectionType.IsEmpty)
{
return false;
}
- return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
- }
-
- private IImageProcessor _imageProcessor;
-
- public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
- : base(libraryManager)
- {
- _imageProcessor = imageProcessor;
+ return !_validCollectionTypes.Contains(collectionType, StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
index 82779f8d3..1c560e8a6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@@ -7,17 +9,28 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
{
- public class PhotoAlbumResolver : FolderResolver<PhotoAlbum>
+ /// <summary>
+ /// Class PhotoAlbumResolver.
+ /// </summary>
+ public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum>
{
private readonly IImageProcessor _imageProcessor;
- private ILibraryManager _libraryManager;
+ private readonly ILibraryManager _libraryManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class.
+ /// </summary>
+ /// <param name="imageProcessor">The image processor.</param>
+ /// <param name="libraryManager">The library manager.</param>
public PhotoAlbumResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
}
+ /// <inheritdoc />
+ public override ResolverPriority Priority => ResolverPriority.Second;
+
/// <summary>
/// Resolves the specified args.
/// </summary>
@@ -31,8 +44,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Must be an image file within a photo collection
var collectionType = args.GetCollectionType();
- if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
- (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{
if (HasPhotos(args))
{
@@ -55,13 +68,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor))
{
- var libraryOptions = args.GetLibraryOptions();
var filename = file.Name;
var ownedByMedia = false;
foreach (var siblingFile in files)
{
- if (PhotoResolver.IsOwnedByMedia(_libraryManager, libraryOptions, siblingFile.FullName, filename))
+ if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
{
ownedByMedia = true;
break;
@@ -74,9 +86,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
}
}
+
return false;
}
-
- public override ResolverPriority Priority => ResolverPriority.Second;
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index db270c398..57bf40e9e 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -1,3 +1,7 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -5,7 +9,6 @@ using System.Linq;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
@@ -14,6 +17,18 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
+ private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "folder",
+ "thumb",
+ "landscape",
+ "fanart",
+ "backdrop",
+ "poster",
+ "cover",
+ "logo",
+ "default"
+ };
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
@@ -31,10 +46,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
if (!args.IsDirectory)
{
// Must be an image file within a photo collection
- var collectionType = args.GetCollectionType();
+ var collectionType = args.CollectionType;
- if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
- (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{
if (IsImageFile(args.Path, _imageProcessor))
{
@@ -42,11 +57,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Make sure the image doesn't belong to a video file
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path));
- var libraryOptions = args.GetLibraryOptions();
foreach (var file in files)
{
- if (IsOwnedByMedia(_libraryManager, libraryOptions, file.FullName, filename))
+ if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
{
return null;
}
@@ -63,54 +77,40 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
- internal static bool IsOwnedByMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
{
- if (libraryManager.IsVideoFile(file, libraryOptions))
+ if (libraryManager.IsVideoFile(file))
{
- return IsOwnedByResolvedMedia(libraryManager, libraryOptions, file, imageFilename);
+ return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
}
return false;
}
- internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
+ => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
+
+ internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
{
- if (imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase))
+ if (path == null)
{
- return true;
+ throw new ArgumentNullException(nameof(path));
}
- return false;
- }
-
- private static readonly HashSet<string> IgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
- {
- "folder",
- "thumb",
- "landscape",
- "fanart",
- "backdrop",
- "poster",
- "cover",
- "logo",
- "default"
- };
-
- internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
- {
- var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
+ var filename = Path.GetFileNameWithoutExtension(path);
- if (IgnoreFiles.Contains(filename))
+ if (_ignoreFiles.Contains(filename))
{
return false;
}
- if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
+ if (_ignoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
{
return false;
}
- return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
+ string extension = Path.GetExtension(path).TrimStart('.');
+ return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index c295894d3..8ce59717d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -1,63 +1,74 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Library.Resolvers
{
- public class PlaylistResolver : FolderResolver<Playlist>
+ /// <summary>
+ /// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
+ /// </summary>
+ public class PlaylistResolver : GenericFolderResolver<Playlist>
{
- private string[] SupportedCollectionTypes = new string[] {
-
+ private string[] _musicPlaylistCollectionTypes =
+ {
string.Empty,
CollectionType.Music
};
- /// <summary>
- /// Resolves the specified args.
- /// </summary>
- /// <param name="args">The args.</param>
- /// <returns>BoxSet.</returns>
+ /// <inheritdoc/>
protected override Playlist Resolve(ItemResolveArgs args)
{
- // It's a boxset if all of the following conditions are met:
- // Is a Directory
- // Contains [playlist] in the path
if (args.IsDirectory)
{
- var filename = Path.GetFileName(args.Path);
-
- if (string.IsNullOrEmpty(filename))
+ // It's a boxset if the path is a directory with [playlist] in it's the name
+ // TODO: Should this use Path.GetDirectoryName() instead?
+ bool isBoxSet = Path.GetFileName(args.Path)
+ ?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
+ ?? false;
+ if (isBoxSet)
{
- return null;
+ return new Playlist
+ {
+ Path = args.Path,
+ Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
+ };
}
- if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
+ // It's a directory-based playlist if the directory contains a playlist file
+ var filePaths = Directory.EnumerateFiles(args.Path, "*", new EnumerationOptions { IgnoreInaccessible = true });
+ if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
{
return new Playlist
{
Path = args.Path,
- Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
+ Name = Path.GetFileName(args.Path)
};
}
}
- else
+
+ // Check if this is a music playlist file
+ // It should have the correct collection type and a supported file extension
+ else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- if (SupportedCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ var extension = Path.GetExtension(args.Path);
+ if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- var extension = Path.GetExtension(args.Path);
- if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ return new Playlist
{
- return new Playlist
- {
- Path = args.Path,
- Name = Path.GetFileNameWithoutExtension(args.Path),
- IsInMixedFolder = true
- };
- }
+ Path = args.Path,
+ Name = Path.GetFileNameWithoutExtension(args.Path),
+ IsInMixedFolder = true,
+ PlaylistMediaType = MediaType.Audio
+ };
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index 7e4b38b4c..6bb999641 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -1,3 +1,7 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Linq;
@@ -9,7 +13,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
- public class SpecialFolderResolver : FolderResolver<Folder>
+ public class SpecialFolderResolver : GenericFolderResolver<Folder>
{
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
@@ -39,10 +43,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
return new AggregateFolder();
}
+
if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase))
{
- return new UserRootFolder(); //if we got here and still a root - must be user root
+ return new UserRootFolder(); // if we got here and still a root - must be user root
}
+
if (args.IsVf)
{
return new CollectionFolder
@@ -61,7 +67,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
return args.FileSystemChildren
.Where(i =>
{
-
try
{
return !i.IsDirectory &&
@@ -71,7 +76,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
return false;
}
-
})
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
.FirstOrDefault();
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index a6d18c9b5..cf2d22f4d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -1,5 +1,8 @@
+#nullable disable
+
using System;
using System.Linq;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -7,16 +10,25 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
- /// Class EpisodeResolver
+ /// Class EpisodeResolver.
/// </summary>
public class EpisodeResolver : BaseVideoResolver<Episode>
{
/// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ public EpisodeResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
+ {
+ }
+
+ /// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Episode.</returns>
- protected override Episode Resolve(ItemResolveArgs args)
+ public override Episode Resolve(ItemResolveArgs args)
{
var parent = args.Parent;
@@ -25,35 +37,30 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- var season = parent as Season;
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
- if (season == null)
- {
- season = parent.GetParents().OfType<Season>().FirstOrDefault();
- }
- // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
+ var season = parent as Season ?? parent.GetParents().OfType<Season>().FirstOrDefault();
+
+ // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
- if (season != null ||
- string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
- args.HasParent<Series>())
+ if ((season != null ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent<Series>())
+ && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
{
var episode = ResolveVideo<Episode>(args, false);
if (episode != null)
{
- var series = parent as Series;
- if (series == null)
- {
- series = parent.GetParents().OfType<Series>().FirstOrDefault();
- }
+ var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
if (series != null)
{
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
}
+
if (season != null)
{
episode.SeasonId = season.Id;
@@ -72,10 +79,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
-
- public EpisodeResolver(ILibraryManager libraryManager)
- : base(libraryManager)
- {
- }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index ce1386e91..063f67543 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,6 +1,7 @@
+#nullable disable
+
using System.Globalization;
using Emby.Naming.TV;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
@@ -9,30 +10,25 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
- /// Class SeasonResolver
+ /// Class SeasonResolver.
/// </summary>
- public class SeasonResolver : FolderResolver<Season>
+ public class SeasonResolver : GenericFolderResolver<Season>
{
- /// <summary>
- /// The _config
- /// </summary>
- private readonly IServerConfigurationManager _config;
-
private readonly ILibraryManager _libraryManager;
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly ILocalizationManager _localization;
- private readonly ILogger _logger;
+ private readonly ILogger<SeasonResolver> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary>
- /// <param name="config">The config.</param>
/// <param name="libraryManager">The library manager.</param>
- /// <param name="localization">The localization</param>
- /// <param name="logger">The logger</param>
- public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger)
+ /// <param name="localization">The localization.</param>
+ /// <param name="logger">The logger.</param>
+ public SeasonResolver(
+ ILibraryManager libraryManager,
+ ILocalizationManager localization,
+ ILogger<SeasonResolver> logger)
{
- _config = config;
_libraryManager = libraryManager;
_localization = localization;
_logger = logger;
@@ -45,14 +41,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns>Season.</returns>
protected override Season Resolve(ItemResolveArgs args)
{
- if (args.Parent is Series && args.IsDirectory)
+ if (args.Parent is Series series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
- var series = ((Series)args.Parent);
var path = args.Path;
- var seasonParserResult = new SeasonPathParser(namingOptions).Parse(path, true, true);
+ var seasonParserResult = SeasonPathParser.Parse(path, true, true);
var season = new Season
{
@@ -74,7 +69,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
{
- _logger.LogDebug("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
+ _logger.LogDebug(
+ "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
path,
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
@@ -90,8 +86,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
season.Name = seasonNumber == 0 ?
args.LibraryOptions.SeasonZeroDisplayName :
- string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage);
-
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("NameSeasonNumber"),
+ seasonNumber,
+ args.LibraryOptions.PreferredMetadataLanguage);
}
return season;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index 5c95534ec..b7fbe01c5 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -1,12 +1,14 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -14,17 +16,20 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
- /// Class SeriesResolver
+ /// Class SeriesResolver.
/// </summary>
- public class SeriesResolver : FolderResolver<Series>
+ public class SeriesResolver : GenericFolderResolver<Series>
{
- private readonly IFileSystem _fileSystem;
- private readonly ILogger _logger;
+ private readonly ILogger<SeriesResolver> _logger;
private readonly ILibraryManager _libraryManager;
- public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesResolver"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
{
- _fileSystem = fileSystem;
_logger = logger;
_libraryManager = libraryManager;
}
@@ -52,15 +57,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- //if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
- //{
- // return new Series
- // {
- // Path = args.Path,
- // Name = Path.GetFileName(args.Path)
- // };
- //}
-
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
@@ -93,7 +89,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
+ if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false))
{
return new Series
{
@@ -110,31 +106,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public static bool IsSeriesFolder(
string path,
IEnumerable<FileSystemMetadata> fileSystemChildren,
- IDirectoryService directoryService,
- IFileSystem fileSystem,
- ILogger logger,
+ ILogger<SeriesResolver> logger,
ILibraryManager libraryManager,
- LibraryOptions libraryOptions,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
{
- //if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- //{
- // //logger.LogDebug("Igoring series file or folder marked hidden: {0}", child.FullName);
- // continue;
- //}
-
- // Can't enforce this because files saved by Bitcasa are always marked System
- //if ((attributes & FileAttributes.System) == FileAttributes.System)
- //{
- // logger.LogDebug("Igoring series subfolder marked system: {0}", child.FullName);
- // continue;
- //}
-
if (child.IsDirectory)
{
- if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
+ if (IsSeasonFolder(child.FullName, isTvContentType))
{
logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
return true;
@@ -143,7 +123,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
else
{
string fullName = child.FullName;
- if (libraryManager.IsVideoFile(fullName, libraryOptions))
+ if (libraryManager.IsVideoFile(fullName))
{
if (isTvContentType)
{
@@ -168,35 +148,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
}
/// <summary>
- /// Determines whether [is place holder] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is place holder] [the specified path]; otherwise, <c>false</c>.</returns>
- /// <exception cref="ArgumentNullException">path</exception>
- private static bool IsVideoPlaceHolder(string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- var extension = Path.GetExtension(path);
-
- return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
/// Determines whether [is season folder] [the specified path].
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isTvContentType">if set to <c>true</c> [is tv content type].</param>
- /// <param name="libraryManager">The library manager.</param>
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
- private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
+ private static bool IsSeasonFolder(string path, bool isTvContentType)
{
- var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
-
- var seasonNumber = new SeasonPathParser(namingOptions).Parse(path, isTvContentType, isTvContentType).SeasonNumber;
+ var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}
@@ -226,7 +185,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
if (!string.IsNullOrEmpty(id))
{
- item.SetProviderId(MetadataProviders.Tvdb, id);
+ item.SetProviderId(MetadataProvider.Tvdb, id);
}
}
}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 9c7f7dfcb..bebc25b77 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -1,42 +1,39 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
+using Diacritics.Extensions;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
-using Microsoft.Extensions.Logging;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
- /// <summary>
- /// </summary>
public class SearchEngine : ISearchEngine
{
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
- private readonly ILogger _logger;
- public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager)
+ public SearchEngine(ILibraryManager libraryManager, IUserManager userManager)
{
_libraryManager = libraryManager;
_userManager = userManager;
-
- _logger = loggerFactory.CreateLogger("SearchEngine");
}
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
{
User user = null;
-
- if (query.UserId.Equals(Guid.Empty))
- {
- }
- else
+ if (query.UserId != Guid.Empty)
{
user = _userManager.GetUserById(query.UserId);
}
@@ -46,19 +43,19 @@ namespace Emby.Server.Implementations.Library
if (query.StartIndex.HasValue)
{
- results = results.Skip(query.StartIndex.Value).ToList();
+ results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value);
}
if (query.Limit.HasValue)
{
- results = results.Take(query.Limit.Value).ToList();
+ results = results.GetRange(0, Math.Min(query.Limit.Value, results.Count));
}
return new QueryResult<SearchHintInfo>
{
TotalRecordCount = totalRecordCount,
- Items = results.ToArray()
+ Items = results
};
}
@@ -76,14 +73,14 @@ namespace Emby.Server.Implementations.Library
/// <param name="query">The query.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{SearchHintResult}.</returns>
- /// <exception cref="ArgumentNullException">searchTerm</exception>
+ /// <exception cref="ArgumentException"><c>query.SearchTerm</c> is <c>null</c> or empty.</exception>
private List<SearchHintInfo> GetSearchHints(SearchQuery query, User user)
{
var searchTerm = query.SearchTerm;
if (string.IsNullOrEmpty(searchTerm))
{
- throw new ArgumentNullException(nameof(searchTerm));
+ throw new ArgumentException("SearchTerm can't be empty.", nameof(query));
}
searchTerm = searchTerm.Trim().RemoveDiacritics();
@@ -91,61 +88,61 @@ namespace Emby.Server.Implementations.Library
var excludeItemTypes = query.ExcludeItemTypes.ToList();
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList();
- excludeItemTypes.Add(typeof(Year).Name);
- excludeItemTypes.Add(typeof(Folder).Name);
+ excludeItemTypes.Add(nameof(Year));
+ excludeItemTypes.Add(nameof(Folder));
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, typeof(Genre).Name);
- AddIfMissing(includeItemTypes, typeof(MusicGenre).Name);
+ AddIfMissing(includeItemTypes, nameof(Genre));
+ AddIfMissing(includeItemTypes, nameof(MusicGenre));
}
}
else
{
- AddIfMissing(excludeItemTypes, typeof(Genre).Name);
- AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
+ AddIfMissing(excludeItemTypes, nameof(Genre));
+ AddIfMissing(excludeItemTypes, nameof(MusicGenre));
}
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, typeof(Person).Name);
+ AddIfMissing(includeItemTypes, nameof(Person));
}
}
else
{
- AddIfMissing(excludeItemTypes, typeof(Person).Name);
+ AddIfMissing(excludeItemTypes, nameof(Person));
}
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, typeof(Studio).Name);
+ AddIfMissing(includeItemTypes, nameof(Studio));
}
}
else
{
- AddIfMissing(excludeItemTypes, typeof(Studio).Name);
+ AddIfMissing(excludeItemTypes, nameof(Studio));
}
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
- AddIfMissing(includeItemTypes, typeof(MusicArtist).Name);
+ AddIfMissing(includeItemTypes, nameof(MusicArtist));
}
}
else
{
- AddIfMissing(excludeItemTypes, typeof(MusicArtist).Name);
+ AddIfMissing(excludeItemTypes, nameof(MusicArtist));
}
- AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name);
- AddIfMissing(excludeItemTypes, typeof(Folder).Name);
+ AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
+ AddIfMissing(excludeItemTypes, nameof(Folder));
var mediaTypes = query.MediaTypes.ToList();
if (includeItemTypes.Count > 0)
@@ -160,9 +157,9 @@ namespace Emby.Server.Implementations.Library
ExcludeItemTypes = excludeItemTypes.ToArray(),
IncludeItemTypes = includeItemTypes.ToArray(),
Limit = query.Limit,
- IncludeItemsByName = string.IsNullOrEmpty(query.ParentId),
- ParentId = string.IsNullOrEmpty(query.ParentId) ? Guid.Empty : new Guid(query.ParentId),
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
+ IncludeItemsByName = !query.ParentId.HasValue,
+ ParentId = query.ParentId ?? Guid.Empty,
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Recursive = true,
IsKids = query.IsKids,
@@ -192,6 +189,7 @@ namespace Emby.Server.Implementations.Library
{
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
}
+
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<string>();
@@ -205,7 +203,6 @@ namespace Emby.Server.Implementations.Library
return mediaItems.Select(i => new SearchHintInfo
{
Item = i
-
}).ToList();
}
}
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index dfa1edaff..bb3034142 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -1,8 +1,13 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
+using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -10,41 +15,38 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using Microsoft.Extensions.Logging;
+using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
+using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Class UserDataManager
+ /// Class UserDataManager.
/// </summary>
public class UserDataManager : IUserDataManager
{
- public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
-
private readonly ConcurrentDictionary<string, UserItemData> _userData =
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
- private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
+ private readonly IUserManager _userManager;
+ private readonly IUserDataRepository _repository;
- private Func<IUserManager> _userManager;
-
- public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
+ public UserDataManager(
+ IServerConfigurationManager config,
+ IUserManager userManager,
+ IUserDataRepository repository)
{
_config = config;
- _logger = loggerFactory.CreateLogger(GetType().Name);
_userManager = userManager;
+ _repository = repository;
}
- /// <summary>
- /// Gets or sets the repository.
- /// </summary>
- /// <value>The repository.</value>
- public IUserDataRepository Repository { get; set; }
+ public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
SaveUserData(user, item, userData, reason, cancellationToken);
}
@@ -55,6 +57,7 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(userData));
}
+
if (item == null)
{
throw new ArgumentNullException(nameof(item));
@@ -68,7 +71,7 @@ namespace Emby.Server.Implementations.Library
foreach (var key in keys)
{
- Repository.SaveUserData(userId, key, userData, cancellationToken);
+ _repository.SaveUserData(userId, key, userData, cancellationToken);
}
var cacheKey = GetCacheKey(userId, item.Id);
@@ -87,32 +90,31 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache.
/// </summary>
- /// <param name="userId"></param>
- /// <param name="userData"></param>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
+ /// <param name="userId">The user id.</param>
+ /// <param name="userData">The user item data.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
- Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
+ _repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
}
/// <summary>
- /// Retrieve all user data for the given user
+ /// Retrieve all user data for the given user.
/// </summary>
- /// <param name="userId"></param>
- /// <returns></returns>
+ /// <param name="userId">The user id.</param>
+ /// <returns>A <see cref="List{UserItemData}"/> containing all of the user's item data.</returns>
public List<UserItemData> GetAllUserData(Guid userId)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
- return Repository.GetAllUserData(user.InternalId);
+ return _repository.GetAllUserData(user.InternalId);
}
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
return GetUserData(user, itemId, keys);
}
@@ -128,7 +130,7 @@ namespace Emby.Server.Implementations.Library
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
{
- var userData = Repository.GetUserData(internalUserId, keys);
+ var userData = _repository.GetUserData(internalUserId, keys);
if (userData != null)
{
@@ -152,7 +154,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>System.String.</returns>
private static string GetCacheKey(long internalUserId, Guid itemId)
{
- return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N");
+ return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture);
}
public UserItemData GetUserData(User user, BaseItem item)
@@ -160,11 +162,6 @@ namespace Emby.Server.Implementations.Library
return GetUserData(user, item.Id, item.GetUserDataKeys());
}
- public UserItemData GetUserData(string userId, BaseItem item)
- {
- return GetUserData(new Guid(userId), item);
- }
-
public UserItemData GetUserData(Guid userId, BaseItem item)
{
return GetUserData(userId, item.Id, item.GetUserDataKeys());
@@ -179,6 +176,7 @@ namespace Emby.Server.Implementations.Library
return dto;
}
+ /// <inheritdoc />
public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options)
{
var userData = GetUserData(user, item);
@@ -189,11 +187,11 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Converts a UserItemData to a DTOUserItemData
+ /// Converts a UserItemData to a DTOUserItemData.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>DtoUserItemData.</returns>
- /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception>
private UserItemDataDto GetUserItemDataDto(UserItemData data)
{
if (data == null)
@@ -214,6 +212,7 @@ namespace Emby.Server.Implementations.Library
};
}
+ /// <inheritdoc />
public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks)
{
var playedToCompletion = false;
@@ -224,28 +223,25 @@ namespace Emby.Server.Implementations.Library
var hasRuntime = runtimeTicks > 0;
// If a position has been reported, and if we know the duration
- if (positionTicks > 0 && hasRuntime)
+ if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book)
{
var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100;
- // Don't track in very beginning
if (pctIn < _config.Configuration.MinResumePct)
{
+ // ignore progress during the beginning
positionTicks = 0;
}
-
- // If we're at the end, assume completed
else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= runtimeTicks)
{
+ // mark as completed close to the end
positionTicks = 0;
data.Played = playedToCompletion = true;
}
-
else
{
// Enforce MinResumeDuration
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
-
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
{
positionTicks = 0;
@@ -253,6 +249,23 @@ namespace Emby.Server.Implementations.Library
}
}
}
+ else if (positionTicks > 0 && hasRuntime && item is AudioBook)
+ {
+ var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes;
+ var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
+
+ if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume)
+ {
+ // ignore progress during the beginning
+ positionTicks = 0;
+ }
+ else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks)
+ {
+ // mark as completed close to the end
+ positionTicks = 0;
+ data.Played = playedToCompletion = true;
+ }
+ }
else if (!hasRuntime)
{
// If we don't know the runtime we'll just have to assume it was fully played
@@ -265,6 +278,7 @@ namespace Emby.Server.Implementations.Library
positionTicks = 0;
data.Played = false;
}
+
if (!item.SupportsPositionTicksResume)
{
positionTicks = 0;
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
deleted file mode 100644
index dfef8e997..000000000
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ /dev/null
@@ -1,1220 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Users;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.Library
-{
- /// <summary>
- /// Class UserManager
- /// </summary>
- public class UserManager : IUserManager
- {
- /// <summary>
- /// Gets the users.
- /// </summary>
- /// <value>The users.</value>
- public IEnumerable<User> Users => _users;
-
- private User[] _users;
-
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
-
- /// <summary>
- /// Gets the active user repository
- /// </summary>
- /// <value>The user repository.</value>
- private IUserRepository UserRepository { get; set; }
- public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
-
- private readonly IXmlSerializer _xmlSerializer;
- private readonly IJsonSerializer _jsonSerializer;
-
- private readonly INetworkManager _networkManager;
-
- private readonly Func<IImageProcessor> _imageProcessorFactory;
- private readonly Func<IDtoService> _dtoServiceFactory;
- private readonly IServerApplicationHost _appHost;
- private readonly IFileSystem _fileSystem;
-
- private IAuthenticationProvider[] _authenticationProviders;
- private DefaultAuthenticationProvider _defaultAuthenticationProvider;
-
- public UserManager(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager configurationManager,
- IUserRepository userRepository,
- IXmlSerializer xmlSerializer,
- INetworkManager networkManager,
- Func<IImageProcessor> imageProcessorFactory,
- Func<IDtoService> dtoServiceFactory,
- IServerApplicationHost appHost,
- IJsonSerializer jsonSerializer,
- IFileSystem fileSystem)
- {
- _logger = loggerFactory.CreateLogger(nameof(UserManager));
- UserRepository = userRepository;
- _xmlSerializer = xmlSerializer;
- _networkManager = networkManager;
- _imageProcessorFactory = imageProcessorFactory;
- _dtoServiceFactory = dtoServiceFactory;
- _appHost = appHost;
- _jsonSerializer = jsonSerializer;
- _fileSystem = fileSystem;
- ConfigurationManager = configurationManager;
- _users = Array.Empty<User>();
-
- DeletePinFile();
- }
-
- public NameIdPair[] GetAuthenticationProviders()
- {
- return _authenticationProviders
- .Where(i => i.IsEnabled)
- .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1)
- .ThenBy(i => i.Name)
- .Select(i => new NameIdPair
- {
- Name = i.Name,
- Id = GetAuthenticationProviderId(i)
- })
- .ToArray();
- }
-
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders)
- {
- _authenticationProviders = authenticationProviders.ToArray();
-
- _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
- }
-
- #region UserUpdated Event
- /// <summary>
- /// Occurs when [user updated].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserUpdated;
- public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
- public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
- public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
- /// <summary>
- /// Called when [user updated].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserUpdated(User user)
- {
- UserUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- #region UserDeleted Event
- /// <summary>
- /// Occurs when [user deleted].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserDeleted;
- /// <summary>
- /// Called when [user deleted].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserDeleted(User user)
- {
- UserDeleted?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- #endregion
-
- /// <summary>
- /// Gets a User by Id
- /// </summary>
- /// <param name="id">The id.</param>
- /// <returns>User.</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public User GetUserById(Guid id)
- {
- if (id == Guid.Empty)
- {
- throw new ArgumentException(nameof(id), "Guid can't be empty");
- }
-
- return Users.FirstOrDefault(u => u.Id == id);
- }
-
- /// <summary>
- /// Gets the user by identifier.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns>User.</returns>
- public User GetUserById(string id)
- {
- return GetUserById(new Guid(id));
- }
-
- public User GetUserByName(string name)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
- }
-
- public void Initialize()
- {
- _users = LoadUsers();
-
- var users = Users.ToList();
-
- // If there are no local users with admin rights, make them all admins
- if (!users.Any(i => i.Policy.IsAdministrator))
- {
- foreach (var user in users)
- {
- user.Policy.IsAdministrator = true;
- UpdateUserPolicy(user, user.Policy, false);
- }
- }
- }
-
- public bool IsValidUsername(string username)
- {
- // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
- foreach (var currentChar in username)
- {
- if (!IsValidUsernameCharacter(currentChar))
- {
- return false;
- }
- }
- return true;
- }
-
- private static bool IsValidUsernameCharacter(char i)
- {
- return !char.Equals(i, '<') && !char.Equals(i, '>');
- }
-
- public string MakeValidUsername(string username)
- {
- if (IsValidUsername(username))
- {
- return username;
- }
-
- // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
- var builder = new StringBuilder();
-
- foreach (var c in username)
- {
- if (IsValidUsernameCharacter(c))
- {
- builder.Append(c);
- }
- }
- return builder.ToString();
- }
-
- public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession)
- {
- if (string.IsNullOrWhiteSpace(username))
- {
- throw new ArgumentNullException(nameof(username));
- }
-
- var user = Users
- .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
-
- var success = false;
- IAuthenticationProvider authenticationProvider = null;
-
- if (user != null)
- {
- var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- success = authResult.Item2;
- }
- else
- {
- // user is null
- var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
- authenticationProvider = authResult.Item1;
- success = authResult.Item2;
-
- if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
- {
- user = await CreateUser(username).ConfigureAwait(false);
-
- var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy;
- if (hasNewUserPolicy != null)
- {
- var policy = hasNewUserPolicy.GetNewUserPolicy();
- UpdateUserPolicy(user, policy, true);
- }
- }
- }
-
- if (success && user != null && authenticationProvider != null)
- {
- var providerId = GetAuthenticationProviderId(authenticationProvider);
-
- if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
- {
- user.Policy.AuthenticationProviderId = providerId;
- UpdateUserPolicy(user, user.Policy, true);
- }
- }
-
- if (user == null)
- {
- throw new SecurityException("Invalid username or password entered.");
- }
-
- if (user.Policy.IsDisabled)
- {
- throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
- }
-
- if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
- {
- throw new SecurityException("Forbidden.");
- }
-
- if (!user.IsParentalScheduleAllowed())
- {
- throw new SecurityException("User is not allowed access at this time.");
- }
-
- // Update LastActivityDate and LastLoginDate, then save
- if (success)
- {
- if (isUserSession)
- {
- user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
- UpdateUser(user);
- }
- UpdateInvalidLoginAttemptCount(user, 0);
- }
- else
- {
- UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1);
- }
-
- _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
-
- return success ? user : null;
- }
-
- private static string GetAuthenticationProviderId(IAuthenticationProvider provider)
- {
- return provider.GetType().FullName;
- }
-
- private IAuthenticationProvider GetAuthenticationProvider(User user)
- {
- return GetAuthenticationProviders(user).First();
- }
-
- private IAuthenticationProvider[] GetAuthenticationProviders(User user)
- {
- var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
-
- var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray();
-
- if (!string.IsNullOrEmpty(authenticationProviderId))
- {
- providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
- }
-
- if (providers.Length == 0)
- {
- providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider };
- }
-
- return providers;
- }
-
- private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
- {
- try
- {
- var requiresResolvedUser = provider as IRequiresResolvedUser;
- if (requiresResolvedUser != null)
- {
- await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false);
- }
- else
- {
- await provider.Authenticate(username, password).ConfigureAwait(false);
- }
-
- return true;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
-
- return false;
- }
- }
-
- private async Task<Tuple<IAuthenticationProvider, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
- {
- bool success = false;
- IAuthenticationProvider authenticationProvider = null;
-
- if (password != null && user != null)
- {
- // Doesn't look like this is even possible to be used, because of password == null checks below
- hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password);
- }
-
- if (password == null)
- {
- // legacy
- success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- foreach (var provider in GetAuthenticationProviders(user))
- {
- success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
-
- if (success)
- {
- authenticationProvider = provider;
- break;
- }
- }
- }
-
- if (user != null)
- {
- if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword)
- {
- if (password == null)
- {
- // legacy
- success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
- }
- }
- }
-
- return new Tuple<IAuthenticationProvider, bool>(authenticationProvider, success);
- }
-
- private void UpdateInvalidLoginAttemptCount(User user, int newValue)
- {
- if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0)
- {
- return;
- }
-
- user.Policy.InvalidLoginAttemptCount = newValue;
-
- var maxCount = user.Policy.IsAdministrator ? 3 : 5;
-
- var fireLockout = false;
-
- if (newValue >= maxCount)
- {
- _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue);
- user.Policy.IsDisabled = true;
-
- fireLockout = true;
- }
-
- UpdateUserPolicy(user, user.Policy, false);
-
- if (fireLockout)
- {
- UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
- }
- }
-
- private string GetLocalPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? _defaultAuthenticationProvider.GetEmptyHashedString(user)
- : user.EasyPassword;
- }
-
- private bool IsPasswordEmpty(User user, string passwordHash)
- {
- return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Loads the users from the repository
- /// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private User[] LoadUsers()
- {
- var users = UserRepository.RetrieveAllUsers();
-
- // There always has to be at least one user.
- if (users.Count == 0)
- {
- var defaultName = Environment.UserName;
- if (string.IsNullOrWhiteSpace(defaultName))
- {
- defaultName = "MyJellyfinUser";
- }
- var name = MakeValidUsername(defaultName);
-
- var user = InstantiateNewUser(name);
-
- user.DateLastSaved = DateTime.UtcNow;
-
- UserRepository.CreateUser(user);
-
- users.Add(user);
-
- user.Policy.IsAdministrator = true;
- user.Policy.EnableContentDeletion = true;
- user.Policy.EnableRemoteControlOfOtherUsers = true;
- UpdateUserPolicy(user, user.Policy, false);
- }
-
- return users.ToArray();
- }
-
- public UserDto GetUserDto(User user, string remoteEndPoint = null)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
- var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user));
-
- var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
- hasConfiguredEasyPassword :
- hasConfiguredPassword;
-
- var dto = new UserDto
- {
- Id = user.Id,
- Name = user.Name,
- HasPassword = hasPassword,
- HasConfiguredPassword = hasConfiguredPassword,
- HasConfiguredEasyPassword = hasConfiguredEasyPassword,
- LastActivityDate = user.LastActivityDate,
- LastLoginDate = user.LastLoginDate,
- Configuration = user.Configuration,
- ServerId = _appHost.SystemId,
- Policy = user.Policy
- };
-
- if (!hasPassword && Users.Count() == 1)
- {
- dto.EnableAutoLogin = true;
- }
-
- var image = user.GetImageInfo(ImageType.Primary, 0);
-
- if (image != null)
- {
- dto.PrimaryImageTag = GetImageCacheTag(user, image);
-
- try
- {
- _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user);
- }
- catch (Exception ex)
- {
- // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
- _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name);
- }
- }
-
- return dto;
- }
-
- public UserDto GetOfflineUserDto(User user)
- {
- var dto = GetUserDto(user);
-
- dto.ServerName = _appHost.FriendlyName;
-
- return dto;
- }
-
- private string GetImageCacheTag(BaseItem item, ItemImageInfo image)
- {
- try
- {
- return _imageProcessorFactory().GetImageCacheTag(item, image);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path);
- return null;
- }
- }
-
- /// <summary>
- /// Refreshes metadata for each user
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public async Task RefreshUsersMetadata(CancellationToken cancellationToken)
- {
- foreach (var user in Users)
- {
- await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false);
- }
- }
-
- /// <summary>
- /// Renames the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="newName">The new name.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">user</exception>
- /// <exception cref="ArgumentException"></exception>
- public async Task RenameUser(User user, string newName)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- if (string.IsNullOrEmpty(newName))
- {
- throw new ArgumentNullException(nameof(newName));
- }
-
- if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)))
- {
- throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName));
- }
-
- if (user.Name.Equals(newName, StringComparison.Ordinal))
- {
- throw new ArgumentException("The new and old names must be different.");
- }
-
- await user.Rename(newName);
-
- OnUserUpdated(user);
- }
-
- /// <summary>
- /// Updates the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <exception cref="ArgumentNullException">user</exception>
- /// <exception cref="ArgumentException"></exception>
- public void UpdateUser(User user)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id)))
- {
- throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id));
- }
-
- user.DateModified = DateTime.UtcNow;
- user.DateLastSaved = DateTime.UtcNow;
-
- UserRepository.UpdateUser(user);
-
- OnUserUpdated(user);
- }
-
- public event EventHandler<GenericEventArgs<User>> UserCreated;
-
- private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1);
-
- /// <summary>
- /// Creates the user.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>User.</returns>
- /// <exception cref="ArgumentNullException">name</exception>
- /// <exception cref="ArgumentException"></exception>
- public async Task<User> CreateUser(string name)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (!IsValidUsername(name))
- {
- throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
- }
-
- if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
- {
- throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name));
- }
-
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- var user = InstantiateNewUser(name);
-
- var list = Users.ToList();
- list.Add(user);
- _users = list.ToArray();
-
- user.DateLastSaved = DateTime.UtcNow;
-
- UserRepository.CreateUser(user);
-
- EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
-
- return user;
- }
- finally
- {
- _userListLock.Release();
- }
- }
-
- /// <summary>
- /// Deletes the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">user</exception>
- /// <exception cref="ArgumentException"></exception>
- public async Task DeleteUser(User user)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- var allUsers = Users.ToList();
-
- if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null)
- {
- throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id));
- }
-
- if (allUsers.Count == 1)
- {
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
- }
-
- if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
- {
- throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
- }
-
- await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- var configPath = GetConfigurationFilePath(user);
-
- UserRepository.DeleteUser(user);
-
- try
- {
- _fileSystem.DeleteFile(configPath);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {path}", configPath);
- }
-
- DeleteUserPolicy(user);
-
- _users = allUsers.Where(i => i.Id != user.Id).ToArray();
-
- OnUserDeleted(user);
- }
- finally
- {
- _userListLock.Release();
- }
- }
-
- /// <summary>
- /// Resets the password by clearing it.
- /// </summary>
- /// <returns>Task.</returns>
- public Task ResetPassword(User user)
- {
- return ChangePassword(user, string.Empty);
- }
-
- public void ResetEasyPassword(User user)
- {
- ChangeEasyPassword(user, string.Empty, null);
- }
-
- public async Task ChangePassword(User user, string newPassword)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
-
- UpdateUser(user);
-
- UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
- }
-
- public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
- {
- if (user == null)
- {
- throw new ArgumentNullException(nameof(user));
- }
-
- if (newPassword != null)
- {
- newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordHash))
- {
- throw new ArgumentNullException(nameof(newPasswordHash));
- }
-
- user.EasyPassword = newPasswordHash;
-
- UpdateUser(user);
-
- UserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
- }
-
- /// <summary>
- /// Instantiates the new user.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>User.</returns>
- private static User InstantiateNewUser(string name)
- {
- return new User
- {
- Name = name,
- Id = Guid.NewGuid(),
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true,
- //Salt = BCrypt.GenerateSalt()
- };
- }
-
- private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt");
-
- private string _lastPin;
- private PasswordPinCreationResult _lastPasswordPinCreationResult;
- private int _pinAttempts;
-
- private async Task<PasswordPinCreationResult> CreatePasswordResetPin()
- {
- var num = new Random().Next(1, 9999);
-
- var path = PasswordResetFile;
-
- var pin = num.ToString("0000", CultureInfo.InvariantCulture);
- _lastPin = pin;
-
- var time = TimeSpan.FromMinutes(5);
- var expiration = DateTime.UtcNow.Add(time);
-
- var text = new StringBuilder();
-
- var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty;
-
- text.AppendLine("Use your web browser to visit:");
- text.AppendLine(string.Empty);
- text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html");
- text.AppendLine(string.Empty);
- text.AppendLine("Enter the following pin code:");
- text.AppendLine(string.Empty);
- text.AppendLine(pin);
- text.AppendLine(string.Empty);
-
- var localExpirationTime = expiration.ToLocalTime();
- // Tuesday, 22 August 2006 06:30 AM
- text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
-
- File.WriteAllText(path, text.ToString(), Encoding.UTF8);
-
- var result = new PasswordPinCreationResult
- {
- PinFile = path,
- ExpirationDate = expiration
- };
-
- _lastPasswordPinCreationResult = result;
- _pinAttempts = 0;
-
- return result;
- }
-
- public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
- {
- DeletePinFile();
-
- var user = string.IsNullOrWhiteSpace(enteredUsername) ?
- null :
- GetUserByName(enteredUsername);
-
- var action = ForgotPasswordAction.InNetworkRequired;
- string pinFile = null;
- DateTime? expirationDate = null;
-
- if (user != null && !user.Policy.IsAdministrator)
- {
- action = ForgotPasswordAction.ContactAdmin;
- }
- else
- {
- if (isInNetwork)
- {
- action = ForgotPasswordAction.PinCode;
- }
-
- var result = await CreatePasswordResetPin().ConfigureAwait(false);
- pinFile = result.PinFile;
- expirationDate = result.ExpirationDate;
- }
-
- return new ForgotPasswordResult
- {
- Action = action,
- PinFile = pinFile,
- PinExpirationDate = expirationDate
- };
- }
-
- public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
- {
- DeletePinFile();
-
- var usersReset = new List<string>();
-
- var valid = !string.IsNullOrWhiteSpace(_lastPin) &&
- string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) &&
- _lastPasswordPinCreationResult != null &&
- _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow;
-
- if (valid)
- {
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
-
- foreach (var user in Users)
- {
- await ResetPassword(user).ConfigureAwait(false);
-
- if (user.Policy.IsDisabled)
- {
- user.Policy.IsDisabled = false;
- UpdateUserPolicy(user, user.Policy, true);
- }
- usersReset.Add(user.Name);
- }
- }
- else
- {
- _pinAttempts++;
- if (_pinAttempts >= 3)
- {
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
- }
- }
-
- return new PinRedeemResult
- {
- Success = valid,
- UsersReset = usersReset.ToArray()
- };
- }
-
- private void DeletePinFile()
- {
- try
- {
- _fileSystem.DeleteFile(PasswordResetFile);
- }
- catch
- {
-
- }
- }
-
- class PasswordPinCreationResult
- {
- public string PinFile { get; set; }
- public DateTime ExpirationDate { get; set; }
- }
-
- public UserPolicy GetUserPolicy(User user)
- {
- var path = GetPolicyFilePath(user);
-
- if (!File.Exists(path))
- {
- return GetDefaultPolicy(user);
- }
-
- try
- {
- lock (_policySyncLock)
- {
- return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path);
- }
- }
- catch (IOException)
- {
- return GetDefaultPolicy(user);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reading policy file: {path}", path);
-
- return GetDefaultPolicy(user);
- }
- }
-
- private static UserPolicy GetDefaultPolicy(User user)
- {
- return new UserPolicy
- {
- EnableContentDownloading = true,
- EnableSyncTranscoding = true
- };
- }
-
- private readonly object _policySyncLock = new object();
- public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy)
- {
- var user = GetUserById(userId);
- UpdateUserPolicy(user, userPolicy, true);
- }
-
- private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
- {
- // The xml serializer will output differently if the type is not exact
- if (userPolicy.GetType() != typeof(UserPolicy))
- {
- var json = _jsonSerializer.SerializeToString(userPolicy);
- userPolicy = _jsonSerializer.DeserializeFromString<UserPolicy>(json);
- }
-
- var path = GetPolicyFilePath(user);
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- lock (_policySyncLock)
- {
- _xmlSerializer.SerializeToFile(userPolicy, path);
- user.Policy = userPolicy;
- }
-
- if (fireEvent)
- {
- UserPolicyUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- }
-
- private void DeleteUserPolicy(User user)
- {
- var path = GetPolicyFilePath(user);
-
- try
- {
- lock (_policySyncLock)
- {
- _fileSystem.DeleteFile(path);
- }
- }
- catch (IOException)
- {
-
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error deleting policy file");
- }
- }
-
- private static string GetPolicyFilePath(User user)
- {
- return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml");
- }
-
- private static string GetConfigurationFilePath(User user)
- {
- return Path.Combine(user.ConfigurationDirectoryPath, "config.xml");
- }
-
- public UserConfiguration GetUserConfiguration(User user)
- {
- var path = GetConfigurationFilePath(user);
-
- if (!File.Exists(path))
- {
- return new UserConfiguration();
- }
-
- try
- {
- lock (_configSyncLock)
- {
- return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
- }
- }
- catch (IOException)
- {
- return new UserConfiguration();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reading policy file: {path}", path);
-
- return new UserConfiguration();
- }
- }
-
- private readonly object _configSyncLock = new object();
- public void UpdateConfiguration(Guid userId, UserConfiguration config)
- {
- var user = GetUserById(userId);
- UpdateConfiguration(user, config);
- }
-
- public void UpdateConfiguration(User user, UserConfiguration config)
- {
- UpdateConfiguration(user, config, true);
- }
-
- private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
- {
- var path = GetConfigurationFilePath(user);
-
- // The xml serializer will output differently if the type is not exact
- if (config.GetType() != typeof(UserConfiguration))
- {
- var json = _jsonSerializer.SerializeToString(config);
- config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
- }
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- lock (_configSyncLock)
- {
- _xmlSerializer.SerializeToFile(config, path);
- user.Configuration = config;
- }
-
- if (fireEvent)
- {
- UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User> { Argument = user });
- }
- }
- }
-
- public class DeviceAccessEntryPoint : IServerEntryPoint
- {
- private IUserManager _userManager;
- private IAuthenticationRepository _authRepo;
- private IDeviceManager _deviceManager;
- private ISessionManager _sessionManager;
-
- public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager)
- {
- _userManager = userManager;
- _authRepo = authRepo;
- _deviceManager = deviceManager;
- _sessionManager = sessionManager;
- }
-
- public Task RunAsync()
- {
- _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated;
-
- return Task.CompletedTask;
- }
-
- private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
- {
- var user = e.Argument;
- if (!user.Policy.EnableAllDevices)
- {
- UpdateDeviceAccess(user);
- }
- }
-
- private void UpdateDeviceAccess(User user)
- {
- var existing = _authRepo.Get(new AuthenticationInfoQuery
- {
- UserId = user.Id
-
- }).Items;
-
- foreach (var authInfo in existing)
- {
- if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId))
- {
- _sessionManager.Logout(authInfo);
- }
- }
- }
-
- public void Dispose()
- {
-
- }
- }
-}
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index e9ce682ee..711647e7a 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -1,7 +1,13 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
@@ -14,6 +20,8 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Person = MediaBrowser.Controller.Entities.Person;
namespace Emby.Server.Implementations.Library
{
@@ -41,6 +49,11 @@ namespace Emby.Server.Implementations.Library
{
var user = _userManager.GetUserById(query.UserId);
+ if (user == null)
+ {
+ throw new ArgumentException("User Id specified in the query does not exist.", nameof(query));
+ }
+
var folders = _libraryManager.GetUserRootFolder()
.GetChildren(user, true)
.OfType<Folder>()
@@ -53,7 +66,7 @@ namespace Emby.Server.Implementations.Library
foreach (var folder in folders)
{
var collectionFolder = folder as ICollectionFolder;
- var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
+ var folderViewType = collectionFolder?.CollectionType;
if (UserView.IsUserSpecific(folder))
{
@@ -117,28 +130,23 @@ namespace Emby.Server.Implementations.Library
if (!query.IncludeHidden)
{
- list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N"))).ToList();
+ list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList();
}
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
- var orders = user.Configuration.OrderedViews.ToList();
+ var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
return list
.OrderBy(i =>
{
- var index = orders.IndexOf(i.Id.ToString("N"));
+ var index = Array.IndexOf(orders, i.Id);
- if (index == -1)
+ if (index == -1
+ && i is UserView view
+ && view.DisplayParentId != Guid.Empty)
{
- var view = i as UserView;
- if (view != null)
- {
- if (!view.DisplayParentId.Equals(Guid.Empty))
- {
- index = orders.IndexOf(view.DisplayParentId.ToString("N"));
- }
- }
+ index = Array.IndexOf(orders, view.DisplayParentId);
}
return index == -1 ? int.MaxValue : index;
@@ -162,7 +170,13 @@ namespace Emby.Server.Implementations.Library
return GetUserSubViewWithName(name, parentId, type, sortName);
}
- private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews)
+ private Folder GetUserView(
+ List<ICollectionFolder> parents,
+ string viewType,
+ string localizationKey,
+ string sortName,
+ Jellyfin.Data.Entities.User user,
+ string[] presetViews)
{
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
{
@@ -223,7 +237,7 @@ namespace Emby.Server.Implementations.Library
return list;
}
- private List<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
+ private IReadOnlyList<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
{
var parentId = request.ParentId;
@@ -235,24 +249,22 @@ namespace Emby.Server.Implementations.Library
if (!parentId.Equals(Guid.Empty))
{
var parentItem = _libraryManager.GetItemById(parentId);
- var parentItemChannel = parentItem as Channel;
- if (parentItemChannel != null)
+ if (parentItem is Channel)
{
- return _channelManager.GetLatestChannelItemsInternal(new InternalItemsQuery(user)
- {
- ChannelIds = new[] { parentId },
- IsPlayed = request.IsPlayed,
- StartIndex = request.StartIndex,
- Limit = request.Limit,
- IncludeItemTypes = request.IncludeItemTypes,
- EnableTotalRecordCount = false
-
-
- }, CancellationToken.None).Result.Items.ToList();
+ return _channelManager.GetLatestChannelItemsInternal(
+ new InternalItemsQuery(user)
+ {
+ ChannelIds = new[] { parentId },
+ IsPlayed = request.IsPlayed,
+ StartIndex = request.StartIndex,
+ Limit = request.Limit,
+ IncludeItemTypes = request.IncludeItemTypes,
+ EnableTotalRecordCount = false
+ },
+ CancellationToken.None).GetAwaiter().GetResult().Items;
}
- var parent = parentItem as Folder;
- if (parent != null)
+ if (parentItem is Folder parent)
{
parents.Add(parent);
}
@@ -269,7 +281,8 @@ namespace Emby.Server.Implementations.Library
{
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.Where(i => i is Folder)
- .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
+ .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes)
+ .Contains(i.Id))
.ToList();
}
@@ -328,20 +341,26 @@ namespace Emby.Server.Implementations.Library
mediaTypes = mediaTypes.Distinct().ToList();
}
- var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[]
- {
- typeof(Person).Name,
- typeof(Studio).Name,
- typeof(Year).Name,
- typeof(MusicGenre).Name,
- typeof(Genre).Name
-
- } : Array.Empty<string>();
+ var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
+ ? new[]
+ {
+ nameof(Person),
+ nameof(Studio),
+ nameof(Year),
+ nameof(MusicGenre),
+ nameof(Genre)
+ }
+ : Array.Empty<string>();
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeItemTypes,
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
+ OrderBy = new[]
+ {
+ (ItemSortBy.DateCreated, SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Descending),
+ (ItemSortBy.ProductionYear, SortOrder.Descending)
+ },
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false,
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
index 27261d449..d51f9aaa7 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
@@ -8,22 +8,27 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
- /// Class ArtistsPostScanTask
+ /// Class ArtistsPostScanTask.
/// </summary>
public class ArtistsPostScanTask : ILibraryPostScanTask
{
/// <summary>
- /// The _library manager
+ /// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
+ private readonly ILogger<ArtistsValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <param name="logger">The logger.</param>
+ /// <param name="itemRepo">The item repository.</param>
+ public ArtistsPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<ArtistsValidator> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 294348660..40436d9c5 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -11,27 +12,28 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
- /// Class ArtistsValidator
+ /// Class ArtistsValidator.
/// </summary>
public class ArtistsValidator
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
- /// The _logger
+ /// The logger.
/// </summary>
- private readonly ILogger _logger;
+ private readonly ILogger<ArtistsValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
- /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+ /// Initializes a new instance of the <see cref="ArtistsValidator" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
- public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <param name="itemRepo">The item repository.</param>
+ public ArtistsValidator(ILibraryManager libraryManager, ILogger<ArtistsValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -79,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(MusicArtist).Name },
+ IncludeItemTypes = new[] { nameof(MusicArtist) },
IsDeadArtist = true,
IsLocked = false
}).Cast<MusicArtist>().ToList();
@@ -91,13 +93,15 @@ namespace Emby.Server.Implementations.Library.Validators
continue;
}
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
+ _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
-
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
index 056807300..d21d2887b 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
@@ -7,21 +7,28 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
+ /// <summary>
+ /// Class GenresPostScanTask.
+ /// </summary>
public class GenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
- /// The _library manager
+ /// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
+ private readonly ILogger<GenresValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
- /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+ /// Initializes a new instance of the <see cref="GenresPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
- public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <param name="itemRepo">The item repository.</param>
+ public GenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<GenresValidator> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
index f8459c61f..e59c62e23 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs
@@ -7,20 +7,29 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
- class GenresValidator
+ /// <summary>
+ /// Class GenresValidator.
+ /// </summary>
+ public class GenresValidator
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
/// <summary>
- /// The _logger
+ /// The logger.
/// </summary>
- private readonly ILogger _logger;
+ private readonly ILogger<GenresValidator> _logger;
- public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GenresValidator"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="itemRepo">The item repository.</param>
+ public GenresValidator(ILibraryManager libraryManager, ILogger<GenresValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
index d7ab92d30..be119866b 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
@@ -8,23 +8,27 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
- /// Class MusicGenresPostScanTask
+ /// Class MusicGenresPostScanTask.
/// </summary>
public class MusicGenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
+ private readonly ILogger<MusicGenresValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
- /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+ /// Initializes a new instance of the <see cref="MusicGenresPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
- public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <param name="itemRepo">The item repository.</param>
+ public MusicGenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<MusicGenresValidator> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
index 710e5d043..1ecf4c87c 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs
@@ -7,20 +7,29 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
- class MusicGenresValidator
+ /// <summary>
+ /// Class MusicGenresValidator.
+ /// </summary>
+ public class MusicGenresValidator
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
- /// The _logger
+ /// The logger.
/// </summary>
- private readonly ILogger _logger;
+ private readonly ILogger<MusicGenresValidator> _logger;
private readonly IItemRepository _itemRepo;
- public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicGenresValidator" /> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="itemRepo">The item repository.</param>
+ public MusicGenresValidator(ILibraryManager libraryManager, ILogger<MusicGenresValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 7899cf01b..8739a9e1b 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -1,7 +1,7 @@
using System;
+using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -11,16 +11,17 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
- /// Class PeopleValidator
+ /// Class PeopleValidator.
/// </summary>
public class PeopleValidator
{
/// <summary>
- /// The _library manager
+ /// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
+
/// <summary>
- /// The _logger
+ /// The _logger.
/// </summary>
private readonly ILogger _logger;
@@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
@@ -62,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators
{
var item = _libraryManager.GetPerson(person);
- var options = new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
+ var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
ImageRefreshMode = MetadataRefreshMode.ValidationOnly,
MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
@@ -89,19 +91,26 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(Person).Name },
+ IncludeItemTypes = new[] { nameof(Person) },
IsDeadPerson = true,
IsLocked = false
});
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
-
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
- }, false);
+ _logger.LogInformation(
+ "Deleting dead {2} {0} {1}.",
+ item.Id.ToString("N", CultureInfo.InvariantCulture),
+ item.Name,
+ item.GetType().Name);
+
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
index 4aa5c7e72..c682b156b 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
@@ -8,23 +8,28 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
- /// Class MusicGenresPostScanTask
+ /// Class MusicGenresPostScanTask.
/// </summary>
public class StudiosPostScanTask : ILibraryPostScanTask
{
/// <summary>
- /// The _library manager
+ /// The _library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
+ private readonly ILogger<StudiosValidator> _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
- /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
+ /// Initializes a new instance of the <see cref="StudiosPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <param name="logger">The logger.</param>
+ /// <param name="itemRepo">The item repository.</param>
+ public StudiosPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<StudiosValidator> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
index da4645a11..8577d722e 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -8,20 +9,30 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Validators
{
- class StudiosValidator
+ /// <summary>
+ /// Class StudiosValidator.
+ /// </summary>
+ public class StudiosValidator
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
+
/// <summary>
- /// The _logger
+ /// The logger.
/// </summary>
- private readonly ILogger _logger;
+ private readonly ILogger<StudiosValidator> _logger;
- public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StudiosValidator" /> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="itemRepo">The item repository.</param>
+ public StudiosValidator(ILibraryManager libraryManager, ILogger<StudiosValidator> logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -69,20 +80,22 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(Studio).Name },
+ IncludeItemTypes = new[] { nameof(Studio) },
IsDeadStudio = true,
IsLocked = false
});
foreach (var item in deadEntities)
{
- _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
-
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
+ _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
progress.Report(100);