#pragma warning disable CS1591
#pragma warning disable CA5394
using System;
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 BitFaster.Caching.Lru;
using Emby.Naming.Common;
using Emby.Naming.TV;
using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Emby.Server.Implementations.Sorting;
using Jellyfin.Data;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
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.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.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
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
{
///
/// Class LibraryManager.
///
public class LibraryManager : ILibraryManager
{
private const string ShortcutFileExtension = ".mblink";
private readonly ILogger _logger;
private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _configurationManager;
private readonly Lazy _libraryMonitorFactory;
private readonly Lazy _providerManagerFactory;
private readonly Lazy _userViewManagerFactory;
private readonly IServerApplicationHost _appHost;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository;
private readonly IItemPersistenceService _persistenceService;
private readonly INextUpService _nextUpService;
private readonly IItemCountService _countService;
private readonly ILinkedChildrenService _linkedChildrenService;
private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
private readonly IPeopleRepository _peopleRepository;
private readonly ExtraResolver _extraResolver;
private readonly IPathManager _pathManager;
private readonly FastConcurrentLru _cache;
///
/// The _root folder sync lock.
///
private readonly Lock _rootFolderSyncLock = new();
private readonly Lock _userRootFolderSyncLock = new();
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
///
/// The _root folder.
///
private volatile AggregateFolder? _rootFolder;
private volatile UserRootFolder? _userRootFolder;
private bool _wizardCompleted;
///
/// Initializes a new instance of the class.
///
/// The application host.
/// The logger factory.
/// The task manager.
/// The user manager.
/// The configuration manager.
/// The user data manager.
/// The library monitor.
/// The file system.
/// The provider manager.
/// The user view manager.
/// The media encoder.
/// The item repository.
/// The item persistence service.
/// The next up service.
/// The item count service.
/// The linked children service.
/// The image processor.
/// The naming options.
/// The directory service.
/// The people repository.
/// The path manager.
public LibraryManager(
IServerApplicationHost appHost,
ILoggerFactory loggerFactory,
ITaskManager taskManager,
IUserManager userManager,
IServerConfigurationManager configurationManager,
IUserDataManager userDataManager,
Lazy libraryMonitorFactory,
IFileSystem fileSystem,
Lazy providerManagerFactory,
Lazy userViewManagerFactory,
IMediaEncoder mediaEncoder,
IItemRepository itemRepository,
IItemPersistenceService persistenceService,
INextUpService nextUpService,
IItemCountService countService,
ILinkedChildrenService linkedChildrenService,
IImageProcessor imageProcessor,
NamingOptions namingOptions,
IDirectoryService directoryService,
IPeopleRepository peopleRepository,
IPathManager pathManager)
{
_appHost = appHost;
_logger = loggerFactory.CreateLogger();
_taskManager = taskManager;
_userManager = userManager;
_configurationManager = configurationManager;
_userDataManager = userDataManager;
_libraryMonitorFactory = libraryMonitorFactory;
_fileSystem = fileSystem;
_providerManagerFactory = providerManagerFactory;
_userViewManagerFactory = userViewManagerFactory;
_mediaEncoder = mediaEncoder;
_itemRepository = itemRepository;
_persistenceService = persistenceService;
_nextUpService = nextUpService;
_countService = countService;
_linkedChildrenService = linkedChildrenService;
_imageProcessor = imageProcessor;
_cache = new FastConcurrentLru(_configurationManager.Configuration.CacheSize);
_namingOptions = namingOptions;
_peopleRepository = peopleRepository;
_pathManager = pathManager;
_extraResolver = new ExtraResolver(loggerFactory.CreateLogger(), namingOptions, directoryService);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(_configurationManager.Configuration);
}
///
/// Occurs when [item added].
///
public event EventHandler? ItemAdded;
///
/// Occurs when [item updated].
///
public event EventHandler? ItemUpdated;
///
/// Occurs when [item removed].
///
public event EventHandler? ItemRemoved;
///
/// Gets the root folder.
///
/// The root folder.
public AggregateFolder RootFolder
{
get
{
if (_rootFolder is null)
{
lock (_rootFolderSyncLock)
{
_rootFolder ??= CreateRootFolder();
}
}
return _rootFolder;
}
}
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
private IProviderManager ProviderManager => _providerManagerFactory.Value;
private IUserViewManager UserViewManager => _userViewManagerFactory.Value;
///
/// Gets or sets the postscan tasks.
///
/// The postscan tasks.
private ILibraryPostScanTask[] PostScanTasks { get; set; } = [];
///
/// Gets or sets the intro providers.
///
/// The intro providers.
private IIntroProvider[] IntroProviders { get; set; } = [];
///
/// Gets or sets the list of entity resolution ignore rules.
///
/// The entity resolution ignore rules.
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = [];
///
/// Gets or sets the list of currently registered entity resolvers.
///
/// The entity resolvers enumerable.
private IItemResolver[] EntityResolvers { get; set; } = [];
private IMultiItemResolver[] MultiItemResolvers { get; set; } = [];
///
/// Gets or sets the comparers.
///
/// The comparers.
private IBaseItemComparer[] Comparers { get; set; } = [];
public bool IsScanRunning { get; private set; }
///
/// Adds the parts.
///
/// The rules.
/// The resolvers.
/// The intro providers.
/// The item comparers.
/// The post scan tasks.
public void AddParts(
IEnumerable rules,
IEnumerable resolvers,
IEnumerable introProviders,
IEnumerable itemComparers,
IEnumerable postScanTasks)
{
EntityResolutionIgnoreRules = rules.ToArray();
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
MultiItemResolvers = EntityResolvers.OfType().ToArray();
IntroProviders = introProviders.ToArray();
Comparers = itemComparers.ToArray();
PostScanTasks = postScanTasks.ToArray();
}
///
/// Records the configuration values.
///
/// The configuration.
private void RecordConfigurationValues(ServerConfiguration configuration)
{
_wizardCompleted = configuration.IsStartupWizardCompleted;
}
///
/// Configurations the updated.
///
/// The sender.
/// The instance containing the event data.
private void ConfigurationUpdated(object? sender, EventArgs e)
{
var config = _configurationManager.Configuration;
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
RecordConfigurationValues(config);
if (wizardChanged)
{
_taskManager.CancelIfRunningAndQueue();
}
}
public void RegisterItem(BaseItem item)
{
ArgumentNullException.ThrowIfNull(item);
if (item is IItemByName)
{
if (item is not MusicArtist)
{
return;
}
}
else if (!item.IsFolder)
{
if (item is not Video && item is not LiveTvChannel)
{
return;
}
}
_cache.AddOrUpdate(item.Id, item);
}
public void DeleteItem(BaseItem item, DeleteOptions options)
{
DeleteItem(item, options, false);
}
public void DeleteItem(BaseItem item, DeleteOptions options, bool notifyParentItem)
{
ArgumentNullException.ThrowIfNull(item);
var parent = item.GetOwner() ?? item.GetParent();
DeleteItem(item, options, parent, notifyParentItem);
}
public void DeleteItemsUnsafeFast(IReadOnlyCollection items, bool deleteSourceFiles = false)
{
var pathMaps = items.Select(e =>
(Item: e,
InternalPath: GetInternalMetadataPaths(e),
DeletePaths: deleteSourceFiles ? e.GetDeletePaths() : [])).ToArray();
foreach (var (item, internalPaths, pathsToDelete) in pathMaps)
{
foreach (var metadataPath in internalPaths)
{
if (!Directory.Exists(metadataPath))
{
continue;
}
_logger.LogDebug(
"Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name,
item.Name ?? "Unknown name",
metadataPath,
item.Id);
try
{
Directory.Delete(metadataPath, true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath);
}
}
foreach (var fileSystemInfo in pathsToDelete)
{
DeleteItemPath(item, false, fileSystemInfo);
}
}
_persistenceService.DeleteItem([.. pathMaps.Select(f => f.Item.Id)]);
}
public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem)
{
ArgumentNullException.ThrowIfNull(item);
if (item.SourceType == SourceType.Channel)
{
if (options.DeleteFromExternalProvider)
{
try
{
BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult();
}
catch (ArgumentException)
{
// channel no longer installed
}
}
options.DeleteFileLocation = false;
}
if (item is LiveTvProgram)
{
_logger.LogDebug(
"Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
item.Id);
}
else
{
_logger.LogInformation(
"Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
item.Id);
}
// If deleting a primary version video, clear PrimaryVersionId from alternate versions
// OwnerId check: items with OwnerId set are alternate versions or extras, not primaries
if (item is Video video && !video.PrimaryVersionId.HasValue && video.OwnerId.IsEmpty())
{
var localAlternateIds = GetLocalAlternateVersionIds(video).ToHashSet();
var alternateVersions = localAlternateIds
.Concat(GetLinkedAlternateVersions(video).Select(v => v.Id))
.Distinct()
.Select(id => GetItemById(id))
.OfType