mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 20:24:21 -04:00
Merge branch 'master' into client-logger
This commit is contained in:
commit
3398f7f953
@ -148,25 +148,20 @@ namespace Emby.Server.Implementations
|
|||||||
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||||
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
/// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param>
|
||||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
/// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param>
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
|
||||||
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
|
||||||
public ApplicationHost(
|
public ApplicationHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IConfiguration startupConfig,
|
IConfiguration startupConfig)
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection serviceCollection)
|
|
||||||
{
|
{
|
||||||
ApplicationPaths = applicationPaths;
|
ApplicationPaths = applicationPaths;
|
||||||
LoggerFactory = loggerFactory;
|
LoggerFactory = loggerFactory;
|
||||||
_startupOptions = options;
|
_startupOptions = options;
|
||||||
_startupConfig = startupConfig;
|
_startupConfig = startupConfig;
|
||||||
_fileSystemManager = fileSystem;
|
_fileSystemManager = new ManagedFileSystem(LoggerFactory.CreateLogger<ManagedFileSystem>(), applicationPaths);
|
||||||
ServiceCollection = serviceCollection;
|
|
||||||
|
|
||||||
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
Logger = LoggerFactory.CreateLogger<ApplicationHost>();
|
||||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
_fileSystemManager.AddShortcutHandler(new MbLinkShortcutHandler(_fileSystemManager));
|
||||||
|
|
||||||
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||||
ApplicationVersionString = ApplicationVersion.ToString(3);
|
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||||
@ -231,8 +226,6 @@ namespace Emby.Server.Implementations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger<ApplicationHost> Logger { get; }
|
protected ILogger<ApplicationHost> Logger { get; }
|
||||||
|
|
||||||
protected IServiceCollection ServiceCollection { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger factory.
|
/// Gets the logger factory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -522,7 +515,7 @@ namespace Emby.Server.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Init()
|
public void Init(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
DiscoverTypes();
|
DiscoverTypes();
|
||||||
|
|
||||||
@ -552,130 +545,129 @@ namespace Emby.Server.Implementations
|
|||||||
CertificatePath = networkConfiguration.CertificatePath;
|
CertificatePath = networkConfiguration.CertificatePath;
|
||||||
Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
|
Certificate = GetCertificate(CertificatePath, networkConfiguration.CertificatePassword);
|
||||||
|
|
||||||
RegisterServices();
|
RegisterServices(serviceCollection);
|
||||||
|
|
||||||
_pluginManager.RegisterServices(ServiceCollection);
|
_pluginManager.RegisterServices(serviceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers services/resources with the service collection that will be available via DI.
|
/// Registers services/resources with the service collection that will be available via DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void RegisterServices()
|
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
||||||
|
protected virtual void RegisterServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
ServiceCollection.AddSingleton(_startupOptions);
|
serviceCollection.AddSingleton(_startupOptions);
|
||||||
|
|
||||||
ServiceCollection.AddMemoryCache();
|
serviceCollection.AddMemoryCache();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
|
serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
|
||||||
ServiceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
|
serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
|
||||||
ServiceCollection.AddSingleton<IApplicationHost>(this);
|
serviceCollection.AddSingleton<IApplicationHost>(this);
|
||||||
ServiceCollection.AddSingleton<IPluginManager>(_pluginManager);
|
serviceCollection.AddSingleton<IPluginManager>(_pluginManager);
|
||||||
ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
serviceCollection.AddSingleton(_fileSystemManager);
|
||||||
ServiceCollection.AddSingleton<TmdbClientManager>();
|
serviceCollection.AddSingleton<TmdbClientManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(NetManager);
|
serviceCollection.AddSingleton(NetManager);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ITaskManager, TaskManager>();
|
serviceCollection.AddSingleton<ITaskManager, TaskManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_xmlSerializer);
|
serviceCollection.AddSingleton(_xmlSerializer);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IZipClient, ZipClient>();
|
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IServerApplicationHost>(this);
|
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||||
ServiceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||||
ServiceCollection.AddSingleton<EncodingHelper>();
|
serviceCollection.AddSingleton<EncodingHelper>();
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||||
ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMusicManager, MusicManager>();
|
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
|
serviceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||||
ServiceCollection.AddSingleton<IDtoService, DtoService>();
|
serviceCollection.AddSingleton<IDtoService, DtoService>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISessionManager, SessionManager>();
|
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<LiveTvDtoService>();
|
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||||
ServiceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||||
|
|
||||||
ServiceCollection.AddScoped<ISessionContext, SessionContext>();
|
serviceCollection.AddScoped<ISessionContext, SessionContext>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAuthService, AuthService>();
|
serviceCollection.AddSingleton<IAuthService, AuthService>();
|
||||||
ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<TranscodingJobHelper>();
|
serviceCollection.AddSingleton<TranscodingJobHelper>();
|
||||||
ServiceCollection.AddScoped<MediaInfoHelper>();
|
serviceCollection.AddScoped<MediaInfoHelper>();
|
||||||
ServiceCollection.AddScoped<AudioHelper>();
|
serviceCollection.AddScoped<AudioHelper>();
|
||||||
ServiceCollection.AddScoped<DynamicHlsHelper>();
|
serviceCollection.AddScoped<DynamicHlsHelper>();
|
||||||
|
serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
|
||||||
ServiceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
|
serviceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -630,7 +630,7 @@
|
|||||||
"TwoLetterISORegionName": "MD"
|
"TwoLetterISORegionName": "MD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"DisplayName": "Réunion",
|
"DisplayName": "Réunion",
|
||||||
"Name": "RE",
|
"Name": "RE",
|
||||||
"ThreeLetterISORegionName": "REU",
|
"ThreeLetterISORegionName": "REU",
|
||||||
"TwoLetterISORegionName": "RE"
|
"TwoLetterISORegionName": "RE"
|
||||||
|
@ -349,7 +349,8 @@ pli||pi|Pali|pali
|
|||||||
pol||pl|Polish|polonais
|
pol||pl|Polish|polonais
|
||||||
pon|||Pohnpeian|pohnpei
|
pon|||Pohnpeian|pohnpei
|
||||||
por||pt|Portuguese|portugais
|
por||pt|Portuguese|portugais
|
||||||
pob||pt-br|Portuguese (Brazil)|portugais
|
pop||pt-pt|Portuguese (Portugal)|portugais (pt-pt)
|
||||||
|
pob||pt-br|Portuguese (Brazil)|portugais (pt-br)
|
||||||
pra|||Prakrit languages|prâkrit, langues
|
pra|||Prakrit languages|prâkrit, langues
|
||||||
pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500)
|
pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500)
|
||||||
pus||ps|Pushto; Pashto|pachto
|
pus||ps|Pushto; Pashto|pachto
|
||||||
|
@ -22,7 +22,6 @@ using MediaBrowser.Controller.Library;
|
|||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -42,67 +41,61 @@ namespace Jellyfin.Server
|
|||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="startupConfig">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
|
|
||||||
public CoreAppHost(
|
public CoreAppHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IConfiguration startupConfig,
|
IConfiguration startupConfig)
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection collection)
|
|
||||||
: base(
|
: base(
|
||||||
applicationPaths,
|
applicationPaths,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
options,
|
options,
|
||||||
startupConfig,
|
startupConfig)
|
||||||
fileSystem,
|
|
||||||
collection)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void RegisterServices()
|
protected override void RegisterServices(IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
// Register an image encoder
|
// Register an image encoder
|
||||||
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
|
||||||
Type imageEncoderType = useSkiaEncoder
|
Type imageEncoderType = useSkiaEncoder
|
||||||
? typeof(SkiaEncoder)
|
? typeof(SkiaEncoder)
|
||||||
: typeof(NullImageEncoder);
|
: typeof(NullImageEncoder);
|
||||||
ServiceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
|
||||||
|
|
||||||
// Log a warning if the Skia encoder could not be used
|
// Log a warning if the Skia encoder could not be used
|
||||||
if (!useSkiaEncoder)
|
if (!useSkiaEncoder)
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
|
Logger.LogWarning("Skia not available. Will fallback to {ImageEncoder}.", nameof(NullImageEncoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceCollection.AddDbContextPool<JellyfinDb>(
|
serviceCollection.AddDbContextPool<JellyfinDb>(
|
||||||
options => options
|
options => options
|
||||||
.UseLoggerFactory(LoggerFactory)
|
.UseLoggerFactory(LoggerFactory)
|
||||||
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||||
|
|
||||||
ServiceCollection.AddEventServices();
|
serviceCollection.AddEventServices();
|
||||||
ServiceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
serviceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
||||||
ServiceCollection.AddSingleton<IEventManager, EventManager>();
|
serviceCollection.AddSingleton<IEventManager, EventManager>();
|
||||||
ServiceCollection.AddSingleton<JellyfinDbProvider>();
|
serviceCollection.AddSingleton<JellyfinDbProvider>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||||
ServiceCollection.AddSingleton<IUserManager, UserManager>();
|
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||||
ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||||
ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||||
|
|
||||||
// TODO search the assemblies instead of adding them manually?
|
// TODO search the assemblies instead of adding them manually?
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
serviceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||||
|
|
||||||
ServiceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
||||||
|
|
||||||
base.RegisterServices();
|
base.RegisterServices(serviceCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -10,7 +10,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using Emby.Server.Implementations.IO;
|
|
||||||
using Jellyfin.Server.Implementations;
|
using Jellyfin.Server.Implementations;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
@ -159,34 +158,36 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
|
||||||
|
|
||||||
|
// If hosting the web client, validate the client content path
|
||||||
|
if (startupConfig.HostWebClient())
|
||||||
|
{
|
||||||
|
string? webContentPath = appPaths.WebPath;
|
||||||
|
if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"The server is expected to host the web client, but the provided content directory is either " +
|
||||||
|
"invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
|
||||||
|
"server, you may set the '--nowebclient' command line flag, or set" +
|
||||||
|
"'{ConfigKey}=false' in your config settings.",
|
||||||
|
webContentPath,
|
||||||
|
ConfigurationExtensions.HostWebClientKey);
|
||||||
|
Environment.ExitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PerformStaticInitialization();
|
PerformStaticInitialization();
|
||||||
var serviceCollection = new ServiceCollection();
|
|
||||||
|
|
||||||
var appHost = new CoreAppHost(
|
var appHost = new CoreAppHost(
|
||||||
appPaths,
|
appPaths,
|
||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
options,
|
options,
|
||||||
startupConfig,
|
startupConfig);
|
||||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
|
||||||
serviceCollection);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If hosting the web client, validate the client content path
|
var serviceCollection = new ServiceCollection();
|
||||||
if (startupConfig.HostWebClient())
|
appHost.Init(serviceCollection);
|
||||||
{
|
|
||||||
string? webContentPath = appHost.ConfigurationManager.ApplicationPaths.WebPath;
|
|
||||||
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"The server is expected to host the web client, but the provided content directory is either " +
|
|
||||||
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
|
|
||||||
"server, you may set the '--nowebclient' command line flag, or set" +
|
|
||||||
$"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appHost.Init();
|
|
||||||
|
|
||||||
var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
var webHost = new WebHostBuilder().ConfigureWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace MediaBrowser.Common
|
namespace MediaBrowser.Common
|
||||||
{
|
{
|
||||||
@ -137,7 +138,8 @@ namespace MediaBrowser.Common
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes this instance.
|
/// Initializes this instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Init();
|
/// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param>
|
||||||
|
void Init(IServiceCollection serviceCollection);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the instance.
|
/// Creates the instance.
|
||||||
|
@ -2345,7 +2345,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
RemoveImages(new List<ItemImageInfo> { image });
|
RemoveImages(new List<ItemImageInfo> { image });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveImages(List<ItemImageInfo> deletedImages)
|
public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
|
||||||
}
|
}
|
||||||
@ -2495,11 +2495,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the images.
|
/// Adds the images, updating metadata if they already are part of this item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="imageType">Type of the image.</param>
|
/// <param name="imageType">Type of the image.</param>
|
||||||
/// <param name="images">The images.</param>
|
/// <param name="images">The images.</param>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if images were added or updated, <c>false</c> otherwise.</returns>
|
||||||
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
|
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
|
||||||
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
|
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
|
||||||
{
|
{
|
||||||
@ -2512,7 +2512,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var newImageList = new List<FileSystemMetadata>();
|
var newImageList = new List<FileSystemMetadata>();
|
||||||
var imageAdded = false;
|
|
||||||
var imageUpdated = false;
|
var imageUpdated = false;
|
||||||
|
|
||||||
foreach (var newImage in images)
|
foreach (var newImage in images)
|
||||||
@ -2528,7 +2527,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
newImageList.Add(newImage);
|
newImageList.Add(newImage);
|
||||||
imageAdded = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2549,19 +2547,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageAdded || images.Count != existingImages.Count)
|
|
||||||
{
|
|
||||||
var newImagePaths = images.Select(i => i.FullName).ToList();
|
|
||||||
|
|
||||||
var deleted = existingImages
|
|
||||||
.FindAll(i => i.IsLocalFile && !newImagePaths.Contains(i.Path.AsSpan(), StringComparison.OrdinalIgnoreCase) && !File.Exists(i.Path));
|
|
||||||
|
|
||||||
if (deleted.Count > 0)
|
|
||||||
{
|
|
||||||
ImageInfos = ImageInfos.Except(deleted).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newImageList.Count > 0)
|
if (newImageList.Count > 0)
|
||||||
{
|
{
|
||||||
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
|
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
|
||||||
|
@ -455,7 +455,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (directPlayProfile == null)
|
if (directPlayProfile == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogDebug(
|
||||||
"Profile: {0}, No audio direct play profiles found for {1} with codec {2}",
|
"Profile: {0}, No audio direct play profiles found for {1} with codec {2}",
|
||||||
options.Profile.Name ?? "Unknown Profile",
|
options.Profile.Name ?? "Unknown Profile",
|
||||||
item.Path ?? "Unknown path",
|
item.Path ?? "Unknown path",
|
||||||
@ -682,7 +682,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
|
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
|
||||||
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1);
|
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1);
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogDebug(
|
||||||
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
||||||
options.Profile.Name ?? "Unknown Profile",
|
options.Profile.Name ?? "Unknown Profile",
|
||||||
item.Path ?? "Unknown path",
|
item.Path ?? "Unknown path",
|
||||||
@ -1033,7 +1033,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (directPlay == null)
|
if (directPlay == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogDebug(
|
||||||
"Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}",
|
"Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}",
|
||||||
container,
|
container,
|
||||||
videoStream?.Codec ?? "no video",
|
videoStream?.Codec ?? "no video",
|
||||||
@ -1198,7 +1198,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
|
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogDebug(
|
||||||
"Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}",
|
"Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}",
|
||||||
type,
|
type,
|
||||||
profile.Name ?? "Unknown Profile",
|
profile.Name ?? "Unknown Profile",
|
||||||
@ -1222,7 +1222,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Not eligible for {0} due to unsupported subtitles", playMethod);
|
_logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod);
|
||||||
return (false, TranscodeReason.SubtitleCodecNotSupported);
|
return (false, TranscodeReason.SubtitleCodecNotSupported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1404,7 +1404,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (itemBitrate > requestedMaxBitrate)
|
if (itemBitrate > requestedMaxBitrate)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogDebug(
|
||||||
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
|
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
|
||||||
playMethod,
|
playMethod,
|
||||||
itemBitrate,
|
itemBitrate,
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CA1002, CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -25,6 +23,9 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace MediaBrowser.Providers.Manager
|
namespace MediaBrowser.Providers.Manager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utilities for managing images attached to items.
|
||||||
|
/// </summary>
|
||||||
public class ItemImageProvider
|
public class ItemImageProvider
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@ -47,6 +48,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
ImageType.Thumb
|
ImageType.Thumb
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ItemImageProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="providerManager">The provider manager for interacting with provider image references.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
|
public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -54,6 +61,13 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies existing images have valid paths and adds any new local images provided.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <see cref="BaseItem"/> to validate images for.</param>
|
||||||
|
/// <param name="providers">The providers to use, must include <see cref="ILocalImageProvider"/>(s) for local scanning.</param>
|
||||||
|
/// <param name="directoryService">The directory service for <see cref="ILocalImageProvider"/>s to use.</param>
|
||||||
|
/// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
|
||||||
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService)
|
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var hasChanges = false;
|
var hasChanges = false;
|
||||||
@ -73,6 +87,15 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return hasChanges;
|
return hasChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes from the providers according to the given options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <see cref="BaseItem"/> to gather images for.</param>
|
||||||
|
/// <param name="libraryOptions">The library options.</param>
|
||||||
|
/// <param name="providers">The providers to query for images.</param>
|
||||||
|
/// <param name="refreshOptions">The refresh options.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The refresh result.</returns>
|
||||||
public async Task<RefreshResult> RefreshImages(
|
public async Task<RefreshResult> RefreshImages(
|
||||||
BaseItem item,
|
BaseItem item,
|
||||||
LibraryOptions libraryOptions,
|
LibraryOptions libraryOptions,
|
||||||
@ -80,14 +103,16 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
ImageRefreshOptions refreshOptions,
|
ImageRefreshOptions refreshOptions,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var oldBackdropImages = Array.Empty<ItemImageInfo>();
|
||||||
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
|
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
|
||||||
{
|
{
|
||||||
ClearImages(item, ImageType.Backdrop);
|
oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldScreenshotImages = Array.Empty<ItemImageInfo>();
|
||||||
if (refreshOptions.IsReplacingImage(ImageType.Screenshot))
|
if (refreshOptions.IsReplacingImage(ImageType.Screenshot))
|
||||||
{
|
{
|
||||||
ClearImages(item, ImageType.Screenshot);
|
oldScreenshotImages = item.GetImages(ImageType.Screenshot).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new RefreshResult { UpdateType = ItemUpdateType.None };
|
var result = new RefreshResult { UpdateType = ItemUpdateType.None };
|
||||||
@ -95,9 +120,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
var typeName = item.GetType().Name;
|
var typeName = item.GetType().Name;
|
||||||
var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
|
var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
|
||||||
|
|
||||||
// In order to avoid duplicates, only download these if there are none already
|
// track library limits, adding buffer to allow lazy replacing of current images
|
||||||
var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop);
|
var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
|
||||||
var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot);
|
var screenshotLimit = typeOptions.GetLimit(ImageType.Screenshot) + oldScreenshotImages.Length;
|
||||||
var downloadedImages = new List<ImageType>();
|
var downloadedImages = new List<ImageType>();
|
||||||
|
|
||||||
foreach (var provider in providers)
|
foreach (var provider in providers)
|
||||||
@ -114,11 +139,22 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only delete existing multi-images if new ones were added
|
||||||
|
if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
|
||||||
|
{
|
||||||
|
PruneImages(item, oldBackdropImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldScreenshotImages.Length > 0 && oldScreenshotImages.Length < item.GetImages(ImageType.Screenshot).Count())
|
||||||
|
{
|
||||||
|
PruneImages(item, oldScreenshotImages);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes from provider.
|
/// Refreshes from a dynamic provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task RefreshFromProvider(
|
private async Task RefreshFromProvider(
|
||||||
BaseItem item,
|
BaseItem item,
|
||||||
@ -153,13 +189,14 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
if (response.Protocol == MediaProtocol.Http)
|
if (response.Protocol == MediaProtocol.Http)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Setting image url into item {0}", item.Id);
|
_logger.LogDebug("Setting image url into item {0}", item.Id);
|
||||||
|
var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
|
||||||
item.SetImage(
|
item.SetImage(
|
||||||
new ItemImageInfo
|
new ItemImageInfo
|
||||||
{
|
{
|
||||||
Path = response.Path,
|
Path = response.Path,
|
||||||
Type = imageType
|
Type = imageType
|
||||||
},
|
},
|
||||||
0);
|
index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -234,7 +271,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes from provider.
|
/// Refreshes from a remote provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="provider">The provider.</param>
|
/// <param name="provider">The provider.</param>
|
||||||
@ -305,12 +342,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
|
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
|
||||||
await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (item is IHasScreenshots hasScreenshots)
|
if (item is IHasScreenshots)
|
||||||
{
|
{
|
||||||
minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
|
minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
|
||||||
await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
await DownloadMultiImages(item, ImageType.Screenshot, refreshOptions, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@ -329,40 +366,36 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return options.IsEnabled(type);
|
return options.IsEnabled(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearImages(BaseItem item, ImageType type)
|
private void PruneImages(BaseItem item, ItemImageInfo[] images)
|
||||||
{
|
{
|
||||||
var deleted = false;
|
for (var i = 0; i < images.Length; i++)
|
||||||
var deletedImages = new List<ItemImageInfo>();
|
|
||||||
|
|
||||||
foreach (var image in item.GetImages(type))
|
|
||||||
{
|
{
|
||||||
if (!image.IsLocalFile)
|
var image = images[i];
|
||||||
{
|
|
||||||
deletedImages.Add(image);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
if (image.IsLocalFile)
|
||||||
{
|
|
||||||
_fileSystem.DeleteFile(image.Path);
|
|
||||||
deleted = true;
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException)
|
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_fileSystem.DeleteFile(image.Path);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item.RemoveImages(deletedImages);
|
item.RemoveImages(images);
|
||||||
|
|
||||||
if (deleted)
|
|
||||||
{
|
|
||||||
item.ValidateImages(new DirectoryService(_fileSystem));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merges a list of images into the provided item, validating existing images and replacing them or adding new images as necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <see cref="BaseItem"/> to modify.</param>
|
||||||
|
/// <param name="images">The new images to place in <c>item</c>.</param>
|
||||||
|
/// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
|
||||||
public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images)
|
public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images)
|
||||||
{
|
{
|
||||||
var changed = false;
|
var changed = item.ValidateImages(new DirectoryService(_fileSystem));
|
||||||
|
|
||||||
for (var i = 0; i < _singularImages.Length; i++)
|
for (var i = 0; i < _singularImages.Length; i++)
|
||||||
{
|
{
|
||||||
@ -398,18 +431,6 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
currentImage.DateModified = newDateModified;
|
currentImage.DateModified = newDateModified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var existing = item.GetImageInfo(type, 0);
|
|
||||||
if (existing != null)
|
|
||||||
{
|
|
||||||
if (existing.IsLocalFile && !File.Exists(existing.Path))
|
|
||||||
{
|
|
||||||
item.RemoveImage(existing);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UpdateMultiImages(item, images, ImageType.Backdrop))
|
if (UpdateMultiImages(item, images, ImageType.Backdrop))
|
||||||
@ -417,13 +438,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasScreenshots = item as IHasScreenshots;
|
if (item is IHasScreenshots && UpdateMultiImages(item, images, ImageType.Screenshot))
|
||||||
if (hasScreenshots != null)
|
|
||||||
{
|
{
|
||||||
if (UpdateMultiImages(item, images, ImageType.Screenshot))
|
changed = true;
|
||||||
{
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
@ -536,7 +553,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is IItemByName && item is not MusicArtist)
|
if (item is IItemByName and not MusicArtist)
|
||||||
{
|
{
|
||||||
var hasDualAccess = item as IHasDualAccess;
|
var hasDualAccess = item as IHasDualAccess;
|
||||||
if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
|
if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
|
||||||
@ -569,7 +586,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
newIndex);
|
newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadBackdrops(BaseItem item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
|
private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var image in images.Where(i => i.Type == imageType))
|
foreach (var image in images.Where(i => i.Type == imageType))
|
||||||
{
|
{
|
||||||
@ -609,8 +626,8 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's already an image of the same size, skip it
|
// If there's already an image of the same file size, skip it unless doing a full refresh
|
||||||
if (response.Content.Headers.ContentLength.HasValue)
|
if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Test Data\**\*.*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
|
606
tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
Normal file
606
tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Drawing;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Providers;
|
||||||
|
using MediaBrowser.Providers.Manager;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Providers.Tests.Manager
|
||||||
|
{
|
||||||
|
public class ItemImageProviderTests
|
||||||
|
{
|
||||||
|
private static readonly string TestDataImagePath = "Test Data/Images/blank{0}.jpg";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateImages_PhotoEmptyProviders_NoChange()
|
||||||
|
{
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.ValidateImages(new Photo(), new List<ILocalImageProvider>(), null);
|
||||||
|
|
||||||
|
Assert.False(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidateImages_EmptyItemEmptyProviders_NoChange()
|
||||||
|
{
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.ValidateImages(new MovieWithScreenshots(), new List<ILocalImageProvider>(), null);
|
||||||
|
|
||||||
|
Assert.False(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TheoryData<ImageType, int> GetImageTypesWithCount()
|
||||||
|
{
|
||||||
|
var theoryTypes = new TheoryData<ImageType, int>
|
||||||
|
{
|
||||||
|
// minimal test cases that hit different handling
|
||||||
|
{ ImageType.Primary, 1 },
|
||||||
|
{ ImageType.Backdrop, 1 },
|
||||||
|
{ ImageType.Backdrop, 2 },
|
||||||
|
{ ImageType.Screenshot, 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
return theoryTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void ValidateImages_EmptyItemAndPopulatedProviders_AddsImages(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
// Has to exist for querying DateModified time on file, results stored but not checked so not populating
|
||||||
|
BaseItem.FileSystem = Mock.Of<IFileSystem>();
|
||||||
|
|
||||||
|
var item = new MovieWithScreenshots();
|
||||||
|
var imageProvider = GetImageProvider(imageType, imageCount, true);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.ValidateImages(item, new List<ILocalImageProvider> { imageProvider }, null);
|
||||||
|
|
||||||
|
Assert.True(changed);
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void ValidateImages_PopulatedItemWithGoodPathsAndEmptyProviders_NoChange(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, true);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.ValidateImages(item, new List<ILocalImageProvider>(), null);
|
||||||
|
|
||||||
|
Assert.False(changed);
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void ValidateImages_PopulatedItemWithBadPathsAndEmptyProviders_RemovesImage(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, false);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.ValidateImages(item, new List<ILocalImageProvider>(), null);
|
||||||
|
|
||||||
|
Assert.True(changed);
|
||||||
|
Assert.Empty(item.GetImages(imageType));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeImages_EmptyItemNewImagesEmpty_NoChange()
|
||||||
|
{
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.MergeImages(new MovieWithScreenshots(), new List<LocalImageInfo>());
|
||||||
|
|
||||||
|
Assert.False(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void MergeImages_PopulatedItemWithGoodPathsAndPopulatedNewImages_AddsUpdatesImages(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
// valid and not valid paths - should replace the valid paths with the invalid ones
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, true);
|
||||||
|
var images = GetImages(imageType, imageCount, false);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, null);
|
||||||
|
var changed = itemImageProvider.MergeImages(item, images);
|
||||||
|
|
||||||
|
Assert.True(changed);
|
||||||
|
// adds for types that allow multiple, replaces singular type images
|
||||||
|
if (item.AllowsMultipleImages(imageType))
|
||||||
|
{
|
||||||
|
Assert.Equal(imageCount * 2, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Single(item.GetImages(imageType));
|
||||||
|
Assert.Same(images[0].FileInfo.FullName, item.GetImages(imageType).First().Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImages_NoChange(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var oldTime = new DateTime(1970, 1, 1);
|
||||||
|
|
||||||
|
// match update time with time added to item images (unix epoch)
|
||||||
|
var fileSystem = new Mock<IFileSystem>();
|
||||||
|
fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny<FileSystemMetadata>()))
|
||||||
|
.Returns(oldTime);
|
||||||
|
BaseItem.FileSystem = fileSystem.Object;
|
||||||
|
|
||||||
|
// all valid paths - matching for strictly updating
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, true);
|
||||||
|
// set size to non-zero to allow for updates to occur
|
||||||
|
foreach (var image in item.GetImages(imageType))
|
||||||
|
{
|
||||||
|
image.DateModified = oldTime;
|
||||||
|
image.Height = 1;
|
||||||
|
image.Width = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var images = GetImages(imageType, imageCount, true);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, fileSystem);
|
||||||
|
var changed = itemImageProvider.MergeImages(item, images);
|
||||||
|
|
||||||
|
Assert.False(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public void MergeImages_PopulatedItemWithGoodPathsAndSameNewImagesWithNewTimestamps_ResetsImageSizes(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var oldTime = new DateTime(1970, 1, 1);
|
||||||
|
var updatedTime = new DateTime(2021, 1, 1);
|
||||||
|
|
||||||
|
var fileSystem = new Mock<IFileSystem>();
|
||||||
|
fileSystem.Setup(fs => fs.GetLastWriteTimeUtc(It.IsAny<FileSystemMetadata>()))
|
||||||
|
.Returns(updatedTime);
|
||||||
|
BaseItem.FileSystem = fileSystem.Object;
|
||||||
|
|
||||||
|
// all valid paths - matching for strictly updating
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, true);
|
||||||
|
// set size to non-zero to allow for image size reset to occur
|
||||||
|
foreach (var image in item.GetImages(imageType))
|
||||||
|
{
|
||||||
|
image.DateModified = oldTime;
|
||||||
|
image.Height = 1;
|
||||||
|
image.Width = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var images = GetImages(imageType, imageCount, true);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, fileSystem);
|
||||||
|
var changed = itemImageProvider.MergeImages(item, images);
|
||||||
|
|
||||||
|
Assert.True(changed);
|
||||||
|
// before and after paths are the same, verify updated by size reset to 0
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
foreach (var image in item.GetImages(imageType))
|
||||||
|
{
|
||||||
|
Assert.Equal(updatedTime, image.DateModified);
|
||||||
|
Assert.Equal(0, image.Height);
|
||||||
|
Assert.Equal(0, image.Width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ImageType.Primary, 1, false)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, false)]
|
||||||
|
[InlineData(ImageType.Screenshot, 2, false)]
|
||||||
|
[InlineData(ImageType.Primary, 1, true)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, true)]
|
||||||
|
[InlineData(ImageType.Screenshot, 2, true)]
|
||||||
|
public async void RefreshImages_PopulatedItemPopulatedProviderDynamic_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh)
|
||||||
|
{
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, false);
|
||||||
|
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
|
||||||
|
|
||||||
|
var imageResponse = new DynamicImageResponse
|
||||||
|
{
|
||||||
|
HasImage = true,
|
||||||
|
Format = ImageFormat.Jpg,
|
||||||
|
Path = "url path",
|
||||||
|
Protocol = MediaProtocol.Http
|
||||||
|
};
|
||||||
|
|
||||||
|
var dynamicProvider = new Mock<IDynamicImageProvider>(MockBehavior.Strict);
|
||||||
|
dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider");
|
||||||
|
dynamicProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(imageResponse);
|
||||||
|
|
||||||
|
var refreshOptions = forceRefresh
|
||||||
|
? new ImageRefreshOptions(null)
|
||||||
|
{
|
||||||
|
ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true
|
||||||
|
}
|
||||||
|
: new ImageRefreshOptions(null);
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(null, new Mock<IFileSystem>());
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { dynamicProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
if (forceRefresh)
|
||||||
|
{
|
||||||
|
// replaces multi-types
|
||||||
|
Assert.Single(item.GetImages(imageType));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// adds to multi-types if room
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ImageType.Primary, 1, true, MediaProtocol.Http)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, true, MediaProtocol.Http)]
|
||||||
|
[InlineData(ImageType.Primary, 1, true, MediaProtocol.File)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, true, MediaProtocol.File)]
|
||||||
|
[InlineData(ImageType.Primary, 1, false, MediaProtocol.File)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, false, MediaProtocol.File)]
|
||||||
|
public async void RefreshImages_EmptyItemPopulatedProviderDynamic_AddsImages(ImageType imageType, int imageCount, bool responseHasPath, MediaProtocol protocol)
|
||||||
|
{
|
||||||
|
// Has to exist for querying DateModified time on file, results stored but not checked so not populating
|
||||||
|
BaseItem.FileSystem = Mock.Of<IFileSystem>();
|
||||||
|
|
||||||
|
var item = new MovieWithScreenshots();
|
||||||
|
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
|
||||||
|
|
||||||
|
// Path must exist if set: is read in as a stream by AsyncFile.OpenRead
|
||||||
|
var imageResponse = new DynamicImageResponse
|
||||||
|
{
|
||||||
|
HasImage = true,
|
||||||
|
Format = ImageFormat.Jpg,
|
||||||
|
Path = responseHasPath ? string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0) : null,
|
||||||
|
Protocol = protocol
|
||||||
|
};
|
||||||
|
|
||||||
|
var dynamicProvider = new Mock<IDynamicImageProvider>(MockBehavior.Strict);
|
||||||
|
dynamicProvider.Setup(rp => rp.Name).Returns("MockDynamicProvider");
|
||||||
|
dynamicProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
dynamicProvider.Setup(rp => rp.GetImage(item, imageType, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(imageResponse);
|
||||||
|
|
||||||
|
var refreshOptions = new ImageRefreshOptions(null);
|
||||||
|
|
||||||
|
var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
|
||||||
|
providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<Stream>(), It.IsAny<string>(), imageType, null, It.IsAny<CancellationToken>()))
|
||||||
|
.Callback<BaseItem, Stream, string, ImageType, int?, CancellationToken>((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
var itemImageProvider = GetItemImageProvider(providerManager.Object, null);
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { dynamicProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
// dynamic provider unable to return multiple images
|
||||||
|
Assert.Single(item.GetImages(imageType));
|
||||||
|
if (protocol == MediaProtocol.Http)
|
||||||
|
{
|
||||||
|
Assert.Equal(imageResponse.Path, item.GetImagePath(imageType, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ImageType.Primary, 1, false)]
|
||||||
|
[InlineData(ImageType.Backdrop, 1, false)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, false)]
|
||||||
|
[InlineData(ImageType.Screenshot, 2, false)]
|
||||||
|
[InlineData(ImageType.Primary, 1, true)]
|
||||||
|
[InlineData(ImageType.Backdrop, 1, true)]
|
||||||
|
[InlineData(ImageType.Backdrop, 2, true)]
|
||||||
|
[InlineData(ImageType.Screenshot, 2, true)]
|
||||||
|
public async void RefreshImages_PopulatedItemPopulatedProviderRemote_UpdatesImagesIfForced(ImageType imageType, int imageCount, bool forceRefresh)
|
||||||
|
{
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, false);
|
||||||
|
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
|
||||||
|
|
||||||
|
var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
|
||||||
|
remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
|
||||||
|
remoteProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
|
||||||
|
var refreshOptions = forceRefresh
|
||||||
|
? new ImageRefreshOptions(null)
|
||||||
|
{
|
||||||
|
ImageRefreshMode = MetadataRefreshMode.FullRefresh, ReplaceAllImages = true
|
||||||
|
}
|
||||||
|
: new ImageRefreshOptions(null);
|
||||||
|
|
||||||
|
var remoteInfo = new List<RemoteImageInfo>();
|
||||||
|
for (int i = 0; i < imageCount; i++)
|
||||||
|
{
|
||||||
|
remoteInfo.Add(new RemoteImageInfo
|
||||||
|
{
|
||||||
|
Type = imageType,
|
||||||
|
Url = "image url " + i,
|
||||||
|
Width = 1 // min width is set to 0, this will always pass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
|
||||||
|
providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(remoteInfo);
|
||||||
|
var itemImageProvider = GetItemImageProvider(providerManager.Object, new Mock<IFileSystem>());
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(forceRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
foreach (var image in item.GetImages(imageType))
|
||||||
|
{
|
||||||
|
if (forceRefresh)
|
||||||
|
{
|
||||||
|
Assert.Matches(@"image url [0-9]", image.Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.DoesNotMatch(@"image url [0-9]", image.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ImageType.Primary, 0, false)] // singular type only fetches if type is missing from item, no caching
|
||||||
|
[InlineData(ImageType.Backdrop, 0, false)] // empty item, no cache to check
|
||||||
|
[InlineData(ImageType.Backdrop, 1, false)] // populated item, cached so no download
|
||||||
|
[InlineData(ImageType.Backdrop, 1, true)] // populated item, forced to download
|
||||||
|
public async void RefreshImages_NonStubItemPopulatedProviderRemote_DownloadsIfNecessary(ImageType imageType, int initialImageCount, bool fullRefresh)
|
||||||
|
{
|
||||||
|
var targetImageCount = 1;
|
||||||
|
|
||||||
|
// Set path and media source manager so images will be downloaded (EnableImageStub will return false)
|
||||||
|
var item = GetItemWithImages(imageType, initialImageCount, false);
|
||||||
|
item.Path = "non-empty path";
|
||||||
|
BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
|
||||||
|
|
||||||
|
// seek 2 so it won't short-circuit out of downloading when populated
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, 2);
|
||||||
|
|
||||||
|
var content = "Content";
|
||||||
|
var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
|
||||||
|
remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
|
||||||
|
remoteProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
remoteProvider.Setup(rp => rp.GetImageResponse(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((string url, CancellationToken _) => new HttpResponseMessage
|
||||||
|
{
|
||||||
|
ReasonPhrase = url,
|
||||||
|
StatusCode = HttpStatusCode.OK,
|
||||||
|
Content = new StringContent(content, Encoding.UTF8, "image/jpeg")
|
||||||
|
});
|
||||||
|
|
||||||
|
var refreshOptions = fullRefresh
|
||||||
|
? new ImageRefreshOptions(null)
|
||||||
|
{
|
||||||
|
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||||
|
ReplaceAllImages = true
|
||||||
|
}
|
||||||
|
: new ImageRefreshOptions(null);
|
||||||
|
|
||||||
|
var remoteInfo = new List<RemoteImageInfo>();
|
||||||
|
for (int i = 0; i < targetImageCount; i++)
|
||||||
|
{
|
||||||
|
remoteInfo.Add(new RemoteImageInfo
|
||||||
|
{
|
||||||
|
Type = imageType,
|
||||||
|
Url = "image url " + i,
|
||||||
|
Width = 1 // min width is set to 0, this will always pass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
|
||||||
|
providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(remoteInfo);
|
||||||
|
providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<Stream>(), It.IsAny<string>(), imageType, null, It.IsAny<CancellationToken>()))
|
||||||
|
.Callback<BaseItem, Stream, string, ImageType, int?, CancellationToken>((callbackItem, _, _, callbackType, _, _) =>
|
||||||
|
callbackItem.SetImagePath(callbackType, callbackItem.AllowsMultipleImages(callbackType) ? callbackItem.GetImages(callbackType).Count() : 0, new FileSystemMetadata()))
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
var fileSystem = new Mock<IFileSystem>();
|
||||||
|
// match reported file size to image content length - condition for skipping already downloaded multi-images
|
||||||
|
fileSystem.Setup(fs => fs.GetFileInfo(It.IsAny<string>()))
|
||||||
|
.Returns(new FileSystemMetadata { Length = content.Length });
|
||||||
|
var itemImageProvider = GetItemImageProvider(providerManager.Object, fileSystem);
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(initialImageCount == 0 || fullRefresh, result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
Assert.Equal(targetImageCount, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public async void RefreshImages_EmptyItemPopulatedProviderRemoteExtras_LimitsImages(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var item = new MovieWithScreenshots();
|
||||||
|
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
|
||||||
|
|
||||||
|
var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
|
||||||
|
remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
|
||||||
|
remoteProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
|
||||||
|
var refreshOptions = new ImageRefreshOptions(null);
|
||||||
|
|
||||||
|
// populate remote with double the required images to verify count is trimmed to the library option count
|
||||||
|
var remoteInfo = new List<RemoteImageInfo>();
|
||||||
|
for (int i = 0; i < imageCount * 2; i++)
|
||||||
|
{
|
||||||
|
remoteInfo.Add(new RemoteImageInfo
|
||||||
|
{
|
||||||
|
Type = imageType,
|
||||||
|
Url = "image url " + i,
|
||||||
|
Width = 1 // min width is set to 0, this will always pass
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var providerManager = new Mock<IProviderManager>(MockBehavior.Strict);
|
||||||
|
providerManager.Setup(pm => pm.GetAvailableRemoteImages(It.IsAny<BaseItem>(), It.IsAny<RemoteImageQuery>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(remoteInfo);
|
||||||
|
var itemImageProvider = GetItemImageProvider(providerManager.Object, null);
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
var actualImages = item.GetImages(imageType).ToList();
|
||||||
|
Assert.Equal(imageCount, actualImages.Count);
|
||||||
|
// images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen
|
||||||
|
foreach (var image in actualImages)
|
||||||
|
{
|
||||||
|
var index = int.Parse(Regex.Match(image.Path, @"[0-9]+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||||
|
Assert.True(index < imageCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetImageTypesWithCount))]
|
||||||
|
public async void RefreshImages_PopulatedItemEmptyProviderRemoteFullRefresh_DoesntClearImages(ImageType imageType, int imageCount)
|
||||||
|
{
|
||||||
|
var item = GetItemWithImages(imageType, imageCount, false);
|
||||||
|
|
||||||
|
var libraryOptions = GetLibraryOptions(item, imageType, imageCount);
|
||||||
|
|
||||||
|
var remoteProvider = new Mock<IRemoteImageProvider>(MockBehavior.Strict);
|
||||||
|
remoteProvider.Setup(rp => rp.Name).Returns("MockRemoteProvider");
|
||||||
|
remoteProvider.Setup(rp => rp.GetSupportedImages(item))
|
||||||
|
.Returns(new[] { imageType });
|
||||||
|
|
||||||
|
var refreshOptions = new ImageRefreshOptions(null)
|
||||||
|
{
|
||||||
|
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
|
||||||
|
ReplaceAllImages = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var itemImageProvider = GetItemImageProvider(Mock.Of<IProviderManager>(), null);
|
||||||
|
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { remoteProvider.Object }, refreshOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.False(result.UpdateType.HasFlag(ItemUpdateType.ImageUpdate));
|
||||||
|
Assert.Equal(imageCount, item.GetImages(imageType).Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemImageProvider GetItemImageProvider(IProviderManager? providerManager, Mock<IFileSystem>? mockFileSystem)
|
||||||
|
{
|
||||||
|
// strict to ensure this isn't accidentally used where a prepared mock is intended
|
||||||
|
providerManager ??= Mock.Of<IProviderManager>(MockBehavior.Strict);
|
||||||
|
|
||||||
|
// BaseItem.ValidateImages depends on the directory service being able to list directory contents, give it the expected valid file paths
|
||||||
|
mockFileSystem ??= new Mock<IFileSystem>(MockBehavior.Strict);
|
||||||
|
mockFileSystem.Setup(fs => fs.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
|
.Returns(new[]
|
||||||
|
{
|
||||||
|
string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 0),
|
||||||
|
string.Format(CultureInfo.InvariantCulture, TestDataImagePath, 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ItemImageProvider(new NullLogger<ItemImageProvider>(), providerManager, mockFileSystem.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BaseItem GetItemWithImages(ImageType type, int count, bool validPaths)
|
||||||
|
{
|
||||||
|
// Has to exist for querying DateModified time on file, results stored but not checked so not populating
|
||||||
|
BaseItem.FileSystem ??= Mock.Of<IFileSystem>();
|
||||||
|
|
||||||
|
var item = new MovieWithScreenshots();
|
||||||
|
|
||||||
|
var path = validPaths ? TestDataImagePath : "invalid path {0}";
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
item.SetImagePath(type, i, new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = string.Format(CultureInfo.InvariantCulture, path, i),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ILocalImageProvider GetImageProvider(ImageType type, int count, bool validPaths)
|
||||||
|
{
|
||||||
|
var images = GetImages(type, count, validPaths);
|
||||||
|
|
||||||
|
var imageProvider = new Mock<ILocalImageProvider>();
|
||||||
|
imageProvider.Setup(ip => ip.GetImages(It.IsAny<BaseItem>(), It.IsAny<IDirectoryService>()))
|
||||||
|
.Returns(images);
|
||||||
|
return imageProvider.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a list of <see cref="LocalImageInfo"/> references of the specified type and size, optionally pointing to files that exist.
|
||||||
|
/// </summary>
|
||||||
|
private static List<LocalImageInfo> GetImages(ImageType type, int count, bool validPaths)
|
||||||
|
{
|
||||||
|
var path = validPaths ? TestDataImagePath : "invalid path {0}";
|
||||||
|
var images = new List<LocalImageInfo>(count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
images.Add(new LocalImageInfo
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
FileInfo = new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = string.Format(CultureInfo.InvariantCulture, path, i)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a <see cref="LibraryOptions"/> object that will allow for the requested number of images for the target type.
|
||||||
|
/// </summary>
|
||||||
|
private static LibraryOptions GetLibraryOptions(BaseItem item, ImageType type, int count)
|
||||||
|
{
|
||||||
|
return new LibraryOptions
|
||||||
|
{
|
||||||
|
TypeOptions = new[]
|
||||||
|
{
|
||||||
|
new TypeOptions
|
||||||
|
{
|
||||||
|
Type = item.GetType().Name,
|
||||||
|
ImageOptions = new[]
|
||||||
|
{
|
||||||
|
new ImageOption
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
Limit = count,
|
||||||
|
MinWidth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a class that implements IHasScreenshots for testing since no BaseItem class is also IHasScreenshots
|
||||||
|
private class MovieWithScreenshots : Movie, IHasScreenshots
|
||||||
|
{
|
||||||
|
// No contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
|||||||
await localizationManager.LoadAll();
|
await localizationManager.LoadAll();
|
||||||
var cultures = localizationManager.GetCultures().ToList();
|
var cultures = localizationManager.GetCultures().ToList();
|
||||||
|
|
||||||
Assert.Equal(189, cultures.Count);
|
Assert.Equal(190, cultures.Count);
|
||||||
|
|
||||||
var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal));
|
var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal));
|
||||||
Assert.NotNull(germany);
|
Assert.NotNull(germany);
|
||||||
|
@ -3,7 +3,6 @@ using System.Collections.Concurrent;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using Emby.Server.Implementations.IO;
|
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
@ -67,7 +66,7 @@ namespace Jellyfin.Server.Integration.Tests
|
|||||||
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
|
var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
|
||||||
|
|
||||||
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
|
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
|
||||||
var serviceCollection = new ServiceCollection();
|
|
||||||
_disposableComponents.Add(loggerFactory);
|
_disposableComponents.Add(loggerFactory);
|
||||||
|
|
||||||
// Create the app host and initialize it
|
// Create the app host and initialize it
|
||||||
@ -75,11 +74,10 @@ namespace Jellyfin.Server.Integration.Tests
|
|||||||
appPaths,
|
appPaths,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
commandLineOpts,
|
commandLineOpts,
|
||||||
new ConfigurationBuilder().Build(),
|
new ConfigurationBuilder().Build());
|
||||||
new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
|
||||||
serviceCollection);
|
|
||||||
_disposableComponents.Add(appHost);
|
_disposableComponents.Add(appHost);
|
||||||
appHost.Init();
|
var serviceCollection = new ServiceCollection();
|
||||||
|
appHost.Init(serviceCollection);
|
||||||
|
|
||||||
// Configure the web host builder
|
// Configure the web host builder
|
||||||
Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
|
Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
|
||||||
|
@ -2,9 +2,7 @@ using System.Collections.Generic;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Integration.Tests
|
namespace Jellyfin.Server.Integration.Tests
|
||||||
@ -21,22 +19,16 @@ namespace Jellyfin.Server.Integration.Tests
|
|||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
|
|
||||||
public TestAppHost(
|
public TestAppHost(
|
||||||
IServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IConfiguration startup,
|
IConfiguration startup)
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection collection)
|
|
||||||
: base(
|
: base(
|
||||||
applicationPaths,
|
applicationPaths,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
options,
|
options,
|
||||||
startup,
|
startup)
|
||||||
fileSystem,
|
|
||||||
collection)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user