diff options
Diffstat (limited to 'Emby.Server.Implementations')
66 files changed, 1623 insertions, 1788 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index d900520b2..0925d72a6 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -27,6 +25,9 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Activity { + /// <summary> + /// Entry point for the activity logger. + /// </summary> public sealed class ActivityLogEntryPoint : IServerEntryPoint { private readonly ILogger _logger; @@ -42,16 +43,15 @@ namespace Emby.Server.Implementations.Activity /// <summary> /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class. /// </summary> - /// <param name="logger"></param> - /// <param name="sessionManager"></param> - /// <param name="deviceManager"></param> - /// <param name="taskManager"></param> - /// <param name="activityManager"></param> - /// <param name="localization"></param> - /// <param name="installationManager"></param> - /// <param name="subManager"></param> - /// <param name="userManager"></param> - /// <param name="appHost"></param> + /// <param name="logger">The logger.</param> + /// <param name="sessionManager">The session manager.</param> + /// <param name="deviceManager">The device manager.</param> + /// <param name="taskManager">The task manager.</param> + /// <param name="activityManager">The activity manager.</param> + /// <param name="localization">The localization manager.</param> + /// <param name="installationManager">The installation manager.</param> + /// <param name="subManager">The subtitle manager.</param> + /// <param name="userManager">The user manager.</param> public ActivityLogEntryPoint( ILogger<ActivityLogEntryPoint> logger, ISessionManager sessionManager, @@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Activity _userManager = userManager; } + /// <inheritdoc /> public Task RunAsync() { _taskManager.TaskCompleted += OnTaskCompleted; @@ -168,7 +169,12 @@ namespace Emby.Server.Implementations.Activity CreateLogEntry(new ActivityLogEntry { - Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), + Name = string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), + user.Name, + GetItemName(item), + e.DeviceName), Type = GetPlaybackStoppedNotificationType(item.MediaType), UserId = user.Id }); @@ -416,7 +422,7 @@ namespace Emby.Server.Implementations.Activity }); } - private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e) + private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) { CreateLogEntry(new ActivityLogEntry { @@ -428,8 +434,8 @@ namespace Emby.Server.Implementations.Activity ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), - e.Argument.Item2.versionStr), - Overview = e.Argument.Item2.description + e.Argument.Item2.version), + Overview = e.Argument.Item2.changelog }); } @@ -445,7 +451,7 @@ namespace Emby.Server.Implementations.Activity }); } - private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e) + private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e) { CreateLogEntry(new ActivityLogEntry { @@ -457,7 +463,7 @@ namespace Emby.Server.Implementations.Activity ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), - e.Argument.versionStr) + e.Argument.version) }); } @@ -485,8 +491,8 @@ namespace Emby.Server.Implementations.Activity var result = e.Result; var task = e.Task; - var activityTask = task.ScheduledTask as IConfigurableScheduledTask; - if (activityTask != null && !activityTask.IsLogged) + if (task.ScheduledTask is IConfigurableScheduledTask activityTask + && !activityTask.IsLogged) { return; } @@ -560,7 +566,7 @@ namespace Emby.Server.Implementations.Activity /// <summary> /// Constructs a user-friendly string for this TimeSpan instance. /// </summary> - public static string ToUserFriendlyString(TimeSpan span) + private static string ToUserFriendlyString(TimeSpan span) { const int DaysInYear = 365; const int DaysInMonth = 30; diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs index ee10845cf..c1d8dd8da 100644 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ b/Emby.Server.Implementations/Activity/ActivityManager.cs @@ -11,22 +11,17 @@ namespace Emby.Server.Implementations.Activity { public class ActivityManager : IActivityManager { - public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; - private readonly IActivityRepository _repo; - private readonly ILogger _logger; private readonly IUserManager _userManager; - public ActivityManager( - ILoggerFactory loggerFactory, - IActivityRepository repo, - IUserManager userManager) + public ActivityManager(IActivityRepository repo, IUserManager userManager) { - _logger = loggerFactory.CreateLogger(nameof(ActivityManager)); _repo = repo; _userManager = userManager; } + public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; + public void Create(ActivityLogEntry entry) { entry.Date = DateTime.UtcNow; diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index 7be72319e..26fc229f7 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -17,11 +17,12 @@ namespace Emby.Server.Implementations.Activity { public class ActivityRepository : BaseSqliteRepository, IActivityRepository { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); + private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog"; + private readonly IFileSystem _fileSystem; - public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem) - : base(loggerFactory.CreateLogger(nameof(ActivityRepository))) + public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) + : base(logger) { DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); _fileSystem = fileSystem; @@ -76,8 +77,6 @@ namespace Emby.Server.Implementations.Activity } } - private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog"; - public void Create(ActivityLogEntry entry) { if (entry == null) @@ -87,32 +86,34 @@ namespace Emby.Server.Implementations.Activity using (var connection = GetConnection()) { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) + connection.RunInTransaction( + db => { - statement.TryBind("@Name", entry.Name); + using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) + { + statement.TryBind("@Name", entry.Name); - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); + statement.TryBind("@Overview", entry.Overview); + statement.TryBind("@ShortOverview", entry.ShortOverview); + statement.TryBind("@Type", entry.Type); + statement.TryBind("@ItemId", entry.ItemId); - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); - } + if (entry.UserId.Equals(Guid.Empty)) + { + statement.TryBindNull("@UserId"); + } + else + { + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); + } - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); + statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); + statement.TryBind("@LogSeverity", entry.Severity.ToString()); - statement.MoveNext(); - } - }, TransactionMode); + statement.MoveNext(); + } + }, + TransactionMode); } } @@ -125,33 +126,35 @@ namespace Emby.Server.Implementations.Activity using (var connection = GetConnection()) { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id")) + connection.RunInTransaction( + db => { - statement.TryBind("@Id", entry.Id); + using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id")) + { + statement.TryBind("@Id", entry.Id); - statement.TryBind("@Name", entry.Name); - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); + statement.TryBind("@Name", entry.Name); + statement.TryBind("@Overview", entry.Overview); + statement.TryBind("@ShortOverview", entry.ShortOverview); + statement.TryBind("@Type", entry.Type); + statement.TryBind("@ItemId", entry.ItemId); - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); - } + if (entry.UserId.Equals(Guid.Empty)) + { + statement.TryBindNull("@UserId"); + } + else + { + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); + } - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); + statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); + statement.TryBind("@LogSeverity", entry.Severity.ToString()); - statement.MoveNext(); - } - }, TransactionMode); + statement.MoveNext(); + } + }, + TransactionMode); } } @@ -164,6 +167,7 @@ namespace Emby.Server.Implementations.Activity { whereClauses.Add("DateCreated>=@DateCreated"); } + if (hasUserId.HasValue) { if (hasUserId.Value) @@ -204,7 +208,7 @@ namespace Emby.Server.Implementations.Activity if (limit.HasValue) { - commandText += " LIMIT " + limit.Value.ToString(_usCulture); + commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture); } var statementTexts = new[] @@ -304,7 +308,7 @@ namespace Emby.Server.Implementations.Activity index++; if (reader[index].SQLiteType != SQLiteType.Null) { - info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true); + info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true); } return info; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 49d3b4d6a..33aec1a06 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Cryptography; using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Devices; -using Emby.Server.Implementations.Diagnostics; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; @@ -86,9 +85,7 @@ using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -106,7 +103,6 @@ using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; @@ -123,14 +119,20 @@ namespace Emby.Server.Implementations /// </summary> private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; - private SqliteUserRepository _userRepository; - private SqliteDisplayPreferencesRepository _displayPreferencesRepository; + private readonly IFileSystem _fileSystemManager; + private readonly INetworkManager _networkManager; + private readonly IXmlSerializer _xmlSerializer; + private readonly IStartupOptions _startupOptions; + + private IMediaEncoder _mediaEncoder; + private ISessionManager _sessionManager; + private IHttpServer _httpServer; + private IHttpClient _httpClient; /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> - /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> - public abstract bool CanSelfRestart { get; } + public bool CanSelfRestart => _startupOptions.RestartPath != null; public virtual bool CanLaunchWebBrowser { @@ -141,7 +143,7 @@ namespace Emby.Server.Implementations return false; } - if (StartupOptions.IsService) + if (_startupOptions.IsService) { return false; } @@ -211,21 +213,6 @@ namespace Emby.Server.Implementations /// <value>The configuration manager.</value> protected IConfigurationManager ConfigurationManager { get; set; } - public IFileSystem FileSystemManager { get; set; } - - /// <inheritdoc /> - public PackageVersionClass SystemUpdateLevel - { - get - { -#if BETA - return PackageVersionClass.Beta; -#else - return PackageVersionClass.Release; -#endif - } - } - /// <summary> /// Gets or sets the service provider. /// </summary> @@ -248,112 +235,6 @@ namespace Emby.Server.Implementations public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the library manager. - /// </summary> - /// <value>The library manager.</value> - internal ILibraryManager LibraryManager { get; set; } - - /// <summary> - /// Gets or sets the directory watchers. - /// </summary> - /// <value>The directory watchers.</value> - private ILibraryMonitor LibraryMonitor { get; set; } - - /// <summary> - /// Gets or sets the provider manager. - /// </summary> - /// <value>The provider manager.</value> - private IProviderManager ProviderManager { get; set; } - - /// <summary> - /// Gets or sets the HTTP server. - /// </summary> - /// <value>The HTTP server.</value> - private IHttpServer HttpServer { get; set; } - - private IDtoService DtoService { get; set; } - - public IImageProcessor ImageProcessor { get; set; } - - /// <summary> - /// Gets or sets the media encoder. - /// </summary> - /// <value>The media encoder.</value> - private IMediaEncoder MediaEncoder { get; set; } - - private ISubtitleEncoder SubtitleEncoder { get; set; } - - private ISessionManager SessionManager { get; set; } - - private ILiveTvManager LiveTvManager { get; set; } - - public LocalizationManager LocalizationManager { get; set; } - - private IEncodingManager EncodingManager { get; set; } - - private IChannelManager ChannelManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - private IUserDataManager UserDataManager { get; set; } - - internal SqliteItemRepository ItemRepository { get; set; } - - private INotificationManager NotificationManager { get; set; } - - private ISubtitleManager SubtitleManager { get; set; } - - private IChapterManager ChapterManager { get; set; } - - private IDeviceManager DeviceManager { get; set; } - - internal IUserViewManager UserViewManager { get; set; } - - private IAuthenticationRepository AuthenticationRepository { get; set; } - - private ITVSeriesManager TVSeriesManager { get; set; } - - private ICollectionManager CollectionManager { get; set; } - - private IMediaSourceManager MediaSourceManager { get; set; } - - /// <summary> - /// Gets the installation manager. - /// </summary> - /// <value>The installation manager.</value> - protected IInstallationManager InstallationManager { get; private set; } - - protected IAuthService AuthService { get; private set; } - - public IStartupOptions StartupOptions { get; } - - internal IImageEncoder ImageEncoder { get; private set; } - - protected IProcessFactory ProcessFactory { get; private set; } - - protected readonly IXmlSerializer XmlSerializer; - - protected ISocketFactory SocketFactory { get; private set; } - - protected ITaskManager TaskManager { get; private set; } - - public IHttpClient HttpClient { get; private set; } - - protected INetworkManager NetworkManager { get; set; } - - public IJsonSerializer JsonSerializer { get; private set; } - - protected IIsoManager IsoManager { get; private set; } - - /// <summary> /// Initializes a new instance of the <see cref="ApplicationHost" /> class. /// </summary> public ApplicationHost( @@ -361,29 +242,33 @@ namespace Emby.Server.Implementations ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, - IImageEncoder imageEncoder, INetworkManager networkManager) { - XmlSerializer = new MyXmlSerializer(); + _xmlSerializer = new MyXmlSerializer(); - NetworkManager = networkManager; + _networkManager = networkManager; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; - FileSystemManager = fileSystem; - - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); + _fileSystemManager = fileSystem; - Logger = LoggerFactory.CreateLogger("App"); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); - StartupOptions = options; + Logger = LoggerFactory.CreateLogger<ApplicationHost>(); - ImageEncoder = imageEncoder; + _startupOptions = options; fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); - NetworkManager.NetworkChanged += OnNetworkChanged; + _networkManager.NetworkChanged += OnNetworkChanged; + + CertificateInfo = new CertificateInfo + { + Path = ServerConfigurationManager.Configuration.CertificatePath, + Password = ServerConfigurationManager.Configuration.CertificatePassword + }; + Certificate = GetCertificate(CertificateInfo); } public string ExpandVirtualPath(string path) @@ -451,10 +336,7 @@ namespace Emby.Server.Implementations } } - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> + /// <inheritdoc/> public string Name => ApplicationProductName; /// <summary> @@ -544,7 +426,7 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; - MediaEncoder.SetFFmpegPath(); + _mediaEncoder.SetFFmpegPath(); Logger.LogInformation("ServerId: {0}", SystemId); @@ -556,7 +438,7 @@ namespace Emby.Server.Implementations Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); - HttpServer.GlobalResponse = null; + _httpServer.GlobalResponse = null; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); @@ -580,7 +462,7 @@ namespace Emby.Server.Implementations } /// <inheritdoc/> - public async Task InitAsync(IServiceCollection serviceCollection, IConfiguration startupConfig) + public void Init(IServiceCollection serviceCollection) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -592,8 +474,6 @@ namespace Emby.Server.Implementations HttpsPort = ServerConfiguration.DefaultHttpsPort; } - JsonSerializer = new JsonSerializer(); - if (Plugins != null) { var pluginBuilder = new StringBuilder(); @@ -613,7 +493,7 @@ namespace Emby.Server.Implementations DiscoverTypes(); - await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false); + RegisterServices(serviceCollection); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) @@ -624,7 +504,7 @@ namespace Emby.Server.Implementations return; } - await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); + await _httpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) @@ -640,14 +520,16 @@ namespace Emby.Server.Implementations var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>()); - await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); + await _httpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); } /// <summary> /// Registers services/resources with the service collection that will be available via DI. /// </summary> - protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig) + protected virtual void RegisterServices(IServiceCollection serviceCollection) { + serviceCollection.AddSingleton(_startupOptions); + serviceCollection.AddMemoryCache(); serviceCollection.AddSingleton(ConfigurationManager); @@ -655,236 +537,169 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); - serviceCollection.AddSingleton(JsonSerializer); + serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>(); - // TODO: Support for injecting ILogger should be deprecated in favour of ILogger<T> and this removed - serviceCollection.AddSingleton<ILogger>(Logger); + // TODO: Remove support for injecting ILogger completely + serviceCollection.AddSingleton((provider) => + { + Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>"); + return Logger; + }); - serviceCollection.AddSingleton(FileSystemManager); + serviceCollection.AddSingleton(_fileSystemManager); serviceCollection.AddSingleton<TvdbClientManager>(); - HttpClient = new HttpClientManager.HttpClientManager( - ApplicationPaths, - LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(), - FileSystemManager, - () => ApplicationUserAgent); - serviceCollection.AddSingleton(HttpClient); + serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>(); - serviceCollection.AddSingleton(NetworkManager); + serviceCollection.AddSingleton(_networkManager); - IsoManager = new IsoManager(); - serviceCollection.AddSingleton(IsoManager); + serviceCollection.AddSingleton<IIsoManager, IsoManager>(); - TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager); - serviceCollection.AddSingleton(TaskManager); + serviceCollection.AddSingleton<ITaskManager, TaskManager>(); - serviceCollection.AddSingleton(XmlSerializer); + serviceCollection.AddSingleton(_xmlSerializer); - ProcessFactory = new ProcessFactory(); - serviceCollection.AddSingleton(ProcessFactory); + serviceCollection.AddSingleton<IStreamHelper, StreamHelper>(); - serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper)); + serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>(); - var cryptoProvider = new CryptographyProvider(); - serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider); + serviceCollection.AddSingleton<ISocketFactory, SocketFactory>(); - SocketFactory = new SocketFactory(); - serviceCollection.AddSingleton(SocketFactory); + serviceCollection.AddSingleton<IInstallationManager, InstallationManager>(); - serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager)); + serviceCollection.AddSingleton<IZipClient, ZipClient>(); - serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient)); - - serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory)); + serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>(); serviceCollection.AddSingleton<IServerApplicationHost>(this); serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>()); - await LocalizationManager.LoadAll().ConfigureAwait(false); - serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager); - - serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager)); + serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); - UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); - serviceCollection.AddSingleton(UserDataManager); + serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); - _displayPreferencesRepository = new SqliteDisplayPreferencesRepository( - LoggerFactory.CreateLogger<SqliteDisplayPreferencesRepository>(), - ApplicationPaths, - FileSystemManager); - serviceCollection.AddSingleton<IDisplayPreferencesRepository>(_displayPreferencesRepository); + serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>(); + serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); - ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger<SqliteItemRepository>(), LocalizationManager); - serviceCollection.AddSingleton<IItemRepository>(ItemRepository); + serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>(); - AuthenticationRepository = GetAuthenticationRepository(); - serviceCollection.AddSingleton(AuthenticationRepository); + serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); - _userRepository = GetUserRepository(); + serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); - UserManager = new UserManager( - LoggerFactory.CreateLogger<UserManager>(), - _userRepository, - XmlSerializer, - NetworkManager, - () => ImageProcessor, - () => DtoService, - this, - JsonSerializer, - FileSystemManager, - cryptoProvider); + serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>(); - serviceCollection.AddSingleton(UserManager); + // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required + serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); + serviceCollection.AddSingleton<IUserManager, UserManager>(); - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(), - ServerConfigurationManager, - FileSystemManager, - ProcessFactory, - LocalizationManager, - () => SubtitleEncoder, - startupConfig, - StartupOptions.FFmpegPath); - serviceCollection.AddSingleton(MediaEncoder); + // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required + // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation + serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>)); + serviceCollection.AddSingleton<IMediaEncoder>(provider => + ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty)); - LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager, MediaEncoder); - serviceCollection.AddSingleton(LibraryManager); + // 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<IProviderManager>(provider.GetRequiredService<IProviderManager>)); + serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); + serviceCollection.AddSingleton<ILibraryManager, LibraryManager>(); - var musicManager = new MusicManager(LibraryManager); - serviceCollection.AddSingleton<IMusicManager>(musicManager); + serviceCollection.AddSingleton<IMusicManager, MusicManager>(); - LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); - serviceCollection.AddSingleton(LibraryMonitor); + serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>(); - serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); - - CertificateInfo = GetCertificateInfo(true); - Certificate = GetCertificate(CertificateInfo); + serviceCollection.AddSingleton<ISearchEngine, SearchEngine>(); serviceCollection.AddSingleton<ServiceController>(); serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>(); serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); - ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); - serviceCollection.AddSingleton(ImageProcessor); + serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); - TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); - serviceCollection.AddSingleton(TVSeriesManager); + serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>(); - DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager); - serviceCollection.AddSingleton(DeviceManager); + serviceCollection.AddSingleton<IDeviceManager, DeviceManager>(); - MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); - serviceCollection.AddSingleton(MediaSourceManager); + serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>(); - SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); - serviceCollection.AddSingleton(SubtitleManager); + serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>(); - ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer); - serviceCollection.AddSingleton(ProviderManager); + serviceCollection.AddSingleton<IProviderManager, ProviderManager>(); - DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); - serviceCollection.AddSingleton(DtoService); + // 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.AddSingleton<IDtoService, DtoService>(); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); - serviceCollection.AddSingleton(ChannelManager); + serviceCollection.AddSingleton<IChannelManager, ChannelManager>(); - SessionManager = new SessionManager( - LoggerFactory.CreateLogger<SessionManager>(), - UserDataManager, - LibraryManager, - UserManager, - musicManager, - DtoService, - ImageProcessor, - this, - AuthenticationRepository, - DeviceManager, - MediaSourceManager); - serviceCollection.AddSingleton(SessionManager); + serviceCollection.AddSingleton<ISessionManager, SessionManager>(); - serviceCollection.AddSingleton<IDlnaManager>( - new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this)); + serviceCollection.AddSingleton<IDlnaManager, DlnaManager>(); - CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager); - serviceCollection.AddSingleton(CollectionManager); + serviceCollection.AddSingleton<ICollectionManager, CollectionManager>(); - serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager)); + serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>(); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); - serviceCollection.AddSingleton(LiveTvManager); + serviceCollection.AddSingleton<LiveTvDtoService>(); + serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>(); - UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); - serviceCollection.AddSingleton(UserViewManager); + serviceCollection.AddSingleton<IUserViewManager, UserViewManager>(); - NotificationManager = new NotificationManager( - LoggerFactory.CreateLogger<NotificationManager>(), - UserManager, - ServerConfigurationManager); - serviceCollection.AddSingleton(NotificationManager); + serviceCollection.AddSingleton<INotificationManager, NotificationManager>(); - serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager)); + serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>(); - ChapterManager = new ChapterManager(ItemRepository); - serviceCollection.AddSingleton(ChapterManager); + serviceCollection.AddSingleton<IChapterManager, ChapterManager>(); - EncodingManager = new MediaEncoder.EncodingManager( - LoggerFactory.CreateLogger<MediaEncoder.EncodingManager>(), - FileSystemManager, - MediaEncoder, - ChapterManager, - LibraryManager); - serviceCollection.AddSingleton(EncodingManager); + serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); - var activityLogRepo = GetActivityLogRepository(); - serviceCollection.AddSingleton(activityLogRepo); - serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager)); + serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>(); + serviceCollection.AddSingleton<IActivityManager, ActivityManager>(); - var authContext = new AuthorizationContext(AuthenticationRepository, UserManager); - serviceCollection.AddSingleton<IAuthorizationContext>(authContext); - serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); + serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); + serviceCollection.AddSingleton<ISessionContext, SessionContext>(); - AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); - serviceCollection.AddSingleton(AuthService); + serviceCollection.AddSingleton<IAuthService, AuthService>(); - SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder( - LibraryManager, - LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(), - ApplicationPaths, - FileSystemManager, - MediaEncoder, - HttpClient, - MediaSourceManager, - ProcessFactory); - serviceCollection.AddSingleton(SubtitleEncoder); + serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(); - serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); + serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>(); serviceCollection.AddSingleton<EncodingHelper>(); - serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor)); + serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); + } + + /// <summary> + /// Create services registered with the service container that need to be initialized at application startup. + /// </summary> + /// <returns>A task representing the service initialization operation.</returns> + public async Task InitializeServices() + { + var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>(); + await localizationManager.LoadAll().ConfigureAwait(false); - _displayPreferencesRepository.Initialize(); + _mediaEncoder = Resolve<IMediaEncoder>(); + _sessionManager = Resolve<ISessionManager>(); + _httpServer = Resolve<IHttpServer>(); + _httpClient = Resolve<IHttpClient>(); - var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths); + ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize(); + ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); + ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize(); + ((ActivityRepository)Resolve<IActivityRepository>()).Initialize(); SetStaticProperties(); - ((UserManager)UserManager).Initialize(); + var userManager = (UserManager)Resolve<IUserManager>(); + userManager.Initialize(); - ((UserDataManager)UserDataManager).Repository = userDataRepo; - ItemRepository.Initialize(userDataRepo, UserManager); - ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; - } + var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>(); + ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager); - /// <summary> - /// Create services registered with the service container that need to be initialized at application startup. - /// </summary> - public void InitializeServices() - { - HttpServer = Resolve<IHttpServer>(); + FindParts(); } public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) @@ -954,110 +769,37 @@ namespace Emby.Server.Implementations } /// <summary> - /// Gets the user repository. - /// </summary> - /// <returns><see cref="Task{SqliteUserRepository}" />.</returns> - private SqliteUserRepository GetUserRepository() - { - var repo = new SqliteUserRepository( - LoggerFactory.CreateLogger<SqliteUserRepository>(), - ApplicationPaths); - - repo.Initialize(); - - return repo; - } - - private IAuthenticationRepository GetAuthenticationRepository() - { - var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager); - - repo.Initialize(); - - return repo; - } - - private IActivityRepository GetActivityLogRepository() - { - var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager); - - repo.Initialize(); - - return repo; - } - - /// <summary> /// Dirty hacks. /// </summary> private void SetStaticProperties() { - ItemRepository.ImageProcessor = ImageProcessor; - // For now there's no real way to inject these properly - BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem"); + BaseItem.Logger = Resolve<ILogger<BaseItem>>(); BaseItem.ConfigurationManager = ServerConfigurationManager; - BaseItem.LibraryManager = LibraryManager; - BaseItem.ProviderManager = ProviderManager; - BaseItem.LocalizationManager = LocalizationManager; - BaseItem.ItemRepository = ItemRepository; - User.UserManager = UserManager; - BaseItem.FileSystem = FileSystemManager; - BaseItem.UserDataManager = UserDataManager; - BaseItem.ChannelManager = ChannelManager; - Video.LiveTvManager = LiveTvManager; - Folder.UserViewManager = UserViewManager; - UserView.TVSeriesManager = TVSeriesManager; - UserView.CollectionManager = CollectionManager; - BaseItem.MediaSourceManager = MediaSourceManager; - CollectionFolder.XmlSerializer = XmlSerializer; - CollectionFolder.JsonSerializer = JsonSerializer; + BaseItem.LibraryManager = Resolve<ILibraryManager>(); + BaseItem.ProviderManager = Resolve<IProviderManager>(); + BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); + BaseItem.ItemRepository = Resolve<IItemRepository>(); + User.UserManager = Resolve<IUserManager>(); + BaseItem.FileSystem = _fileSystemManager; + BaseItem.UserDataManager = Resolve<IUserDataManager>(); + BaseItem.ChannelManager = Resolve<IChannelManager>(); + Video.LiveTvManager = Resolve<ILiveTvManager>(); + Folder.UserViewManager = Resolve<IUserViewManager>(); + UserView.TVSeriesManager = Resolve<ITVSeriesManager>(); + UserView.CollectionManager = Resolve<ICollectionManager>(); + BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>(); + CollectionFolder.XmlSerializer = _xmlSerializer; + CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>(); CollectionFolder.ApplicationHost = this; - AuthenticatedAttribute.AuthService = AuthService; - } - - private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args) - { - string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name); - var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories) - .Select(Assembly.LoadFrom) - .SelectMany(x => x.ExportedTypes) - .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType) - .ToArray(); - - int oldLen = _allConcreteTypes.Length; - Array.Resize(ref _allConcreteTypes, oldLen + types.Length); - types.CopyTo(_allConcreteTypes, oldLen); - - var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin))) - .Select(CreateInstanceSafe) - .Where(x => x != null) - .Cast<IPlugin>() - .Select(LoadPlugin) - .Where(x => x != null) - .ToArray(); - - oldLen = _plugins.Length; - Array.Resize(ref _plugins, oldLen + plugins.Length); - plugins.CopyTo(_plugins, oldLen); - - var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint))) - .Select(CreateInstanceSafe) - .Where(x => x != null) - .Cast<IServerEntryPoint>() - .ToList(); - - await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false); - await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false); + AuthenticatedAttribute.AuthService = Resolve<IAuthService>(); } /// <summary> - /// Finds the parts. + /// Finds plugin components and register them with the appropriate services. /// </summary> - public void FindParts() + private void FindParts() { - InstallationManager = ServiceProvider.GetService<IInstallationManager>(); - InstallationManager.PluginInstalled += PluginInstalled; - if (!ServerConfigurationManager.Configuration.IsPortAuthorized) { ServerConfigurationManager.Configuration.IsPortAuthorized = true; @@ -1070,34 +812,34 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - HttpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes()); + _httpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes()); - LibraryManager.AddParts( + Resolve<ILibraryManager>().AddParts( GetExports<IResolverIgnoreRule>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>(), GetExports<ILibraryPostScanTask>()); - ProviderManager.AddParts( + Resolve<IProviderManager>().AddParts( GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), GetExports<IMetadataSaver>(), GetExports<IExternalId>()); - LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); + Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); - SubtitleManager.AddParts(GetExports<ISubtitleProvider>()); + Resolve<ISubtitleManager>().AddParts(GetExports<ISubtitleProvider>()); - ChannelManager.AddParts(GetExports<IChannel>()); + Resolve<IChannelManager>().AddParts(GetExports<IChannel>()); - MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>()); + Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); - NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>()); - UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>()); + Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>()); + Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>()); - IsoManager.AddParts(GetExports<IIsoMounter>()); + Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>()); } private IPlugin LoadPlugin(IPlugin plugin) @@ -1204,16 +946,6 @@ namespace Emby.Server.Implementations }); } - private CertificateInfo GetCertificateInfo(bool generateCertificate) - { - // Custom cert - return new CertificateInfo - { - Path = ServerConfigurationManager.Configuration.CertificatePath, - Password = ServerConfigurationManager.Configuration.CertificatePassword - }; - } - /// <summary> /// Called when [configuration updated]. /// </summary> @@ -1240,14 +972,13 @@ namespace Emby.Server.Implementations } } - if (!HttpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) + if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) { requiresRestart = true; } var currentCertPath = CertificateInfo?.Path; - var newCertInfo = GetCertificateInfo(false); - var newCertPath = newCertInfo?.Path; + var newCertPath = ServerConfigurationManager.Configuration.CertificatePath; if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase)) { @@ -1300,7 +1031,7 @@ namespace Emby.Server.Implementations { try { - await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -1404,7 +1135,7 @@ namespace Emby.Server.Implementations IsShuttingDown = IsShuttingDown, Version = ApplicationVersionString, WebSocketPortNumber = HttpPort, - CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), + CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(), Id = SystemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, WebPath = ApplicationPaths.WebPath, @@ -1424,15 +1155,14 @@ namespace Emby.Server.Implementations ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, - EncoderLocation = MediaEncoder.EncoderLocation, + EncoderLocation = _mediaEncoder.EncoderLocation, SystemArchitecture = RuntimeInformation.OSArchitecture, - SystemUpdateLevel = SystemUpdateLevel, - PackageName = StartupOptions.PackageName + PackageName = _startupOptions.PackageName }; } public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo() - => NetworkManager.GetMacAddresses() + => _networkManager.GetMacAddresses() .Select(i => new WakeOnLanInfo(i)) .ToList(); @@ -1544,7 +1274,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); + addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); } var resultList = new List<IPAddress>(); @@ -1611,7 +1341,7 @@ namespace Emby.Server.Implementations try { - using (var response = await HttpClient.SendAsync( + using (var response = await _httpClient.SendAsync( new HttpRequestOptions { Url = apiUrl, @@ -1664,7 +1394,7 @@ namespace Emby.Server.Implementations try { - await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -1714,15 +1444,17 @@ namespace Emby.Server.Implementations throw new NotSupportedException(); } - var process = ProcessFactory.Create(new ProcessOptions + var process = new Process { - FileName = url, - EnableRaisingEvents = true, - UseShellExecute = true, - ErrorDialog = false - }); - - process.Exited += ProcessExited; + StartInfo = new ProcessStartInfo + { + FileName = url, + UseShellExecute = true, + ErrorDialog = false + }, + EnableRaisingEvents = true + }; + process.Exited += (sender, args) => ((Process)sender).Dispose(); try { @@ -1735,11 +1467,6 @@ namespace Emby.Server.Implementations } } - private static void ProcessExited(object sender, EventArgs e) - { - ((IProcess)sender).Dispose(); - } - public virtual void EnableLoopback(string appName) { } @@ -1788,14 +1515,8 @@ namespace Emby.Server.Implementations Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name); } } - - _userRepository?.Dispose(); - _displayPreferencesRepository?.Dispose(); } - _userRepository = null; - _displayPreferencesRepository = null; - _disposed = true; } } diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs index 4a6e5cfd7..e01495e19 100644 --- a/Emby.Server.Implementations/Archiving/ZipClient.cs +++ b/Emby.Server.Implementations/Archiving/ZipClient.cs @@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Archiving } } + /// <inheritdoc /> public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles) { using (var reader = ZipReader.Open(source)) @@ -66,6 +67,7 @@ namespace Emby.Server.Implementations.Archiving } } + /// <inheritdoc /> public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles) { using (var reader = GZipReader.Open(source)) @@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Archiving } } + /// <inheritdoc /> public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName) { using (var reader = GZipReader.Open(source)) diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs index 62aeb9bcb..cf56a5fae 100644 --- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs @@ -20,6 +20,8 @@ namespace Emby.Server.Implementations.Channels _channelManager = channelManager; } + public string Name => "Channel Image Provider"; + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { return GetChannel(item).GetSupportedChannelImages(); @@ -32,8 +34,6 @@ namespace Emby.Server.Implementations.Channels return channel.GetChannelImage(type, cancellationToken); } - public string Name => "Channel Image Provider"; - public bool Supports(BaseItem item) { return item is Channel; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 6e1baddfe..8502af97a 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels { public class ChannelManager : IChannelManager { - internal IChannel[] Channels { get; private set; } - private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; @@ -43,11 +41,16 @@ namespace Emby.Server.Implementations.Channels private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; + private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo = + new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>(); + + private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); + public ChannelManager( IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, - ILoggerFactory loggerFactory, + ILogger<ChannelManager> logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, @@ -57,7 +60,7 @@ namespace Emby.Server.Implementations.Channels _userManager = userManager; _dtoService = dtoService; _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(ChannelManager)); + _logger = logger; _config = config; _fileSystem = fileSystem; _userDataManager = userDataManager; @@ -65,6 +68,8 @@ namespace Emby.Server.Implementations.Channels _providerManager = providerManager; } + internal IChannel[] Channels { get; private set; } + private static TimeSpan CacheLength => TimeSpan.FromHours(3); public void AddParts(IEnumerable<IChannel> channels) @@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels var internalChannel = _libraryManager.GetItemById(item.ChannelId); var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id)); - var supportsDelete = channel as ISupportsDelete; - return supportsDelete != null && supportsDelete.CanDelete(item); + return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item); } public bool EnableMediaProbe(BaseItem item) @@ -146,15 +150,13 @@ namespace Emby.Server.Implementations.Channels { try { - var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes; - - return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val; + return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes + && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val; } catch { return false; } - }).ToList(); } @@ -171,7 +173,6 @@ namespace Emby.Server.Implementations.Channels { return false; } - }).ToList(); } @@ -188,9 +189,9 @@ namespace Emby.Server.Implementations.Channels { return false; } - }).ToList(); } + if (query.IsFavorite.HasValue) { var val = query.IsFavorite.Value; @@ -215,7 +216,6 @@ namespace Emby.Server.Implementations.Channels { return false; } - }).ToList(); } @@ -226,6 +226,7 @@ namespace Emby.Server.Implementations.Channels { all = all.Skip(query.StartIndex.Value).ToList(); } + if (query.Limit.HasValue) { all = all.Take(query.Limit.Value).ToList(); @@ -256,11 +257,9 @@ namespace Emby.Server.Implementations.Channels var internalResult = GetChannelsInternal(query); - var dtoOptions = new DtoOptions() - { - }; + var dtoOptions = new DtoOptions(); - //TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues. + // TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues. var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> @@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels } catch { - } + return; } @@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels var channel = GetChannel(item.ChannelId); var channelPlugin = GetChannelProvider(channel); - var requiresCallback = channelPlugin as IRequiresMediaInfoCallback; - IEnumerable<MediaSourceInfo> results; - if (requiresCallback != null) + if (channelPlugin is IRequiresMediaInfoCallback requiresCallback) { results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) .ConfigureAwait(false); @@ -384,9 +381,6 @@ namespace Emby.Server.Implementations.Channels .ToList(); } - private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo = - new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>(); - private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken) { if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo)) @@ -444,18 +438,21 @@ namespace Emby.Server.Implementations.Channels { isNew = true; } + item.Path = path; if (!item.ChannelId.Equals(id)) { forceUpdate = true; } + item.ChannelId = id; if (item.ParentId != parentFolderId) { forceUpdate = true; } + item.ParentId = parentFolderId; item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating); @@ -472,10 +469,12 @@ namespace Emby.Server.Implementations.Channels _libraryManager.CreateItem(item, null); } - await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - ForceSave = !isNew && forceUpdate - }, cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = !isNew && forceUpdate + }, + cancellationToken).ConfigureAwait(false); return item; } @@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels public ChannelFeatures[] GetAllChannelFeatures() { - return _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Channel).Name }, - OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } - - }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); + return _libraryManager.GetItemIds( + new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(Channel).Name }, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } + }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); } public ChannelFeatures GetChannelFeatures(string id) @@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels public bool SupportsExternalTransfer(Guid channelId) { - //var channel = GetChannel(channelId); var channelProvider = GetChannelProvider(channelId); return channelProvider.GetChannelFeatures().SupportsContentDownloading; } - public ChannelFeatures GetChannelFeaturesDto(Channel channel, + public ChannelFeatures GetChannelFeaturesDto( + Channel channel, IChannel provider, InternalChannelFeatures features) { @@ -567,6 +566,7 @@ namespace Emby.Server.Implementations.Channels { throw new ArgumentNullException(nameof(name)); } + return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel)); } @@ -614,7 +614,7 @@ namespace Emby.Server.Implementations.Channels query.IsFolder = false; // hack for trailers, figure out a better way later - var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1; + var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal); if (sortByPremiereDate) { @@ -640,10 +640,12 @@ namespace Emby.Server.Implementations.Channels { var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false); - var query = new InternalItemsQuery(); - query.Parent = internalChannel; - query.EnableTotalRecordCount = false; - query.ChannelIds = new Guid[] { internalChannel.Id }; + var query = new InternalItemsQuery + { + Parent = internalChannel, + EnableTotalRecordCount = false, + ChannelIds = new Guid[] { internalChannel.Id } + }; var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); @@ -651,13 +653,15 @@ namespace Emby.Server.Implementations.Channels { if (item is Folder folder) { - await GetChannelItemsInternal(new InternalItemsQuery - { - Parent = folder, - EnableTotalRecordCount = false, - ChannelIds = new Guid[] { internalChannel.Id } - - }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); + await GetChannelItemsInternal( + new InternalItemsQuery + { + Parent = folder, + EnableTotalRecordCount = false, + ChannelIds = new Guid[] { internalChannel.Id } + }, + new SimpleProgress<double>(), + cancellationToken).ConfigureAwait(false); } } } @@ -672,7 +676,8 @@ namespace Emby.Server.Implementations.Channels var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId); - var itemsResult = await GetChannelItems(channelProvider, + var itemsResult = await GetChannelItems( + channelProvider, query.User, parentItem is Channel ? null : parentItem.ExternalId, null, @@ -684,13 +689,12 @@ namespace Emby.Server.Implementations.Channels { query.Parent = channel; } + query.ChannelIds = Array.Empty<Guid>(); // Not yet sure why this is causing a problem query.GroupByPresentationUniqueKey = false; - //_logger.LogDebug("GetChannelItemsInternal"); - // null if came from cache if (itemsResult != null) { @@ -707,12 +711,15 @@ namespace Emby.Server.Implementations.Channels var deadItem = _libraryManager.GetItemById(deadId); if (deadItem != null) { - _libraryManager.DeleteItem(deadItem, new DeleteOptions - { - DeleteFileLocation = false, - DeleteFromExternalProvider = false - - }, parentItem, false); + _libraryManager.DeleteItem( + deadItem, + new DeleteOptions + { + DeleteFileLocation = false, + DeleteFromExternalProvider = false + }, + parentItem, + false); } } } @@ -735,7 +742,6 @@ namespace Emby.Server.Implementations.Channels return result; } - private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private async Task<ChannelItemResult> GetChannelItems(IChannel channel, User user, string externalFolderId, @@ -743,7 +749,7 @@ namespace Emby.Server.Implementations.Channels bool sortDescending, CancellationToken cancellationToken) { - var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture); + var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture); var cacheLength = CacheLength; var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending); @@ -761,11 +767,9 @@ namespace Emby.Server.Implementations.Channels } catch (FileNotFoundException) { - } catch (IOException) { - } await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -785,11 +789,9 @@ namespace Emby.Server.Implementations.Channels } catch (FileNotFoundException) { - } catch (IOException) { - } var query = new InternalChannelItemQuery @@ -833,7 +835,8 @@ namespace Emby.Server.Implementations.Channels } } - private string GetChannelDataCachePath(IChannel channel, + private string GetChannelDataCachePath( + IChannel channel, string userId, string externalFolderId, ChannelItemSortField? sortField, @@ -843,8 +846,7 @@ namespace Emby.Server.Implementations.Channels var userCacheKey = string.Empty; - var hasCacheKey = channel as IHasCacheKey; - if (hasCacheKey != null) + if (channel is IHasCacheKey hasCacheKey) { userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty; } @@ -858,6 +860,7 @@ namespace Emby.Server.Implementations.Channels { filename += "-sortField-" + sortField.Value; } + if (sortDescending) { filename += "-sortDescending"; @@ -865,7 +868,8 @@ namespace Emby.Server.Implementations.Channels filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture); - return Path.Combine(_config.ApplicationPaths.CachePath, + return Path.Combine( + _config.ApplicationPaths.CachePath, "channels", channelId, version, @@ -981,7 +985,6 @@ namespace Emby.Server.Implementations.Channels { item.RunTimeTicks = null; } - else if (isNew || !enableMediaProbe) { item.RunTimeTicks = info.RunTimeTicks; @@ -1014,26 +1017,24 @@ namespace Emby.Server.Implementations.Channels } } - var hasArtists = item as IHasArtist; - if (hasArtists != null) + if (item is IHasArtist hasArtists) { hasArtists.Artists = info.Artists.ToArray(); } - var hasAlbumArtists = item as IHasAlbumArtist; - if (hasAlbumArtists != null) + if (item is IHasAlbumArtist hasAlbumArtists) { hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray(); } - var trailer = item as Trailer; - if (trailer != null) + if (item is Trailer trailer) { if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes)) { _logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name); forceUpdate = true; } + trailer.TrailerTypes = info.TrailerTypes.ToArray(); } @@ -1057,6 +1058,7 @@ namespace Emby.Server.Implementations.Channels forceUpdate = true; _logger.LogDebug("Forcing update due to ChannelId {0}", item.Name); } + item.ChannelId = internalChannelId; if (!item.ParentId.Equals(parentFolderId)) @@ -1064,16 +1066,17 @@ namespace Emby.Server.Implementations.Channels forceUpdate = true; _logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name); } + item.ParentId = parentFolderId; - var hasSeries = item as IHasSeries; - if (hasSeries != null) + if (item is IHasSeries hasSeries) { if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase)) { forceUpdate = true; _logger.LogDebug("Forcing update due to SeriesName {0}", item.Name); } + hasSeries.SeriesName = info.SeriesName; } @@ -1082,24 +1085,23 @@ namespace Emby.Server.Implementations.Channels forceUpdate = true; _logger.LogDebug("Forcing update due to ExternalId {0}", item.Name); } + item.ExternalId = info.Id; - var channelAudioItem = item as Audio; - if (channelAudioItem != null) + if (item is Audio channelAudioItem) { channelAudioItem.ExtraType = info.ExtraType; var mediaSource = info.MediaSources.FirstOrDefault(); - item.Path = mediaSource == null ? null : mediaSource.Path; + item.Path = mediaSource?.Path; } - var channelVideoItem = item as Video; - if (channelVideoItem != null) + if (item is Video channelVideoItem) { channelVideoItem.ExtraType = info.ExtraType; var mediaSource = info.MediaSources.FirstOrDefault(); - item.Path = mediaSource == null ? null : mediaSource.Path; + item.Path = mediaSource?.Path; } if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary)) @@ -1156,7 +1158,7 @@ namespace Emby.Server.Implementations.Channels } } - if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime)) + if (isNew || forceUpdate || item.DateLastRefreshed == default) { _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); } diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs index 266d539d0..250e9da64 100644 --- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs +++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs @@ -14,14 +14,12 @@ namespace Emby.Server.Implementations.Channels public class ChannelPostScanTask { private readonly IChannelManager _channelManager; - private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager) + public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager) { _channelManager = channelManager; - _userManager = userManager; _logger = logger; _libraryManager = libraryManager; } diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 367efcb13..e1d35f2b0 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -7,29 +7,26 @@ using System.Threading.Tasks; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.Channels { public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly IChannelManager _channelManager; - private readonly IUserManager _userManager; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; public RefreshChannelsScheduledTask( IChannelManager channelManager, - IUserManager userManager, ILogger<RefreshChannelsScheduledTask> logger, ILibraryManager libraryManager, ILocalizationManager localization) { _channelManager = channelManager; - _userManager = userManager; _logger = logger; _libraryManager = libraryManager; _localization = localization; @@ -63,7 +60,7 @@ namespace Emby.Server.Implementations.Channels await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); - await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken) + await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken) .ConfigureAwait(false); } @@ -72,7 +69,6 @@ namespace Emby.Server.Implementations.Channels { return new[] { - // Every so often new TaskTriggerInfo { diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 21ba0288e..320552465 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -46,9 +46,7 @@ namespace Emby.Server.Implementations.Collections { var subItem = i; - var episode = subItem as Episode; - - if (episode != null) + if (subItem is Episode episode) { var series = episode.Series; if (series != null && series.HasImage(ImageType.Primary)) diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 321952874..68bcc5a4f 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.Collections } public event EventHandler<CollectionCreatedEventArgs> CollectionCreated; + public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection; + public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection; private IEnumerable<Folder> FindFolders(string path) @@ -109,9 +111,9 @@ namespace Emby.Server.Implementations.Collections { var folder = GetCollectionsFolder(false).Result; - return folder == null ? - new List<BoxSet>() : - folder.GetChildren(user, true).OfType<BoxSet>(); + return folder == null + ? Enumerable.Empty<BoxSet>() + : folder.GetChildren(user, true).OfType<BoxSet>(); } public BoxSet CreateCollection(CollectionCreationOptions options) @@ -191,7 +193,6 @@ namespace Emby.Server.Implementations.Collections private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions) { var collection = _libraryManager.GetItemById(collectionId) as BoxSet; - if (collection == null) { throw new ArgumentException("No collection exists with the supplied Id"); @@ -289,10 +290,13 @@ namespace Emby.Server.Implementations.Collections } collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - ForceSave = true - }, RefreshPriority.High); + _providerManager.QueueRefresh( + collection.Id, + new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + { + ForceSave = true + }, + RefreshPriority.High); ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs { diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 4574a64fd..db7c35a7c 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.Updates; using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -17,6 +18,7 @@ namespace Emby.Server.Implementations { { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index c87793072..716e5071d 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; -using MediaBrowser.Model.Serialization; using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data @@ -109,25 +107,6 @@ namespace Emby.Server.Implementations.Data return null; } - /// <summary> - /// Serializes to bytes. - /// </summary> - /// <returns>System.Byte[][].</returns> - /// <exception cref="ArgumentNullException">obj</exception> - public static byte[] SerializeToBytes(this IJsonSerializer json, object obj) - { - if (obj == null) - { - throw new ArgumentNullException(nameof(obj)); - } - - using (var stream = new MemoryStream()) - { - json.SerializeToStream(obj, stream); - return stream.ToArray(); - } - } - public static void Attach(SQLiteDatabaseConnection db, string path, string alias) { var commandText = string.Format( @@ -287,7 +266,7 @@ namespace Emby.Server.Implementations.Data } } - public static void TryBind(this IStatement statement, string name, byte[] value) + public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value) { if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam)) { @@ -383,11 +362,11 @@ namespace Emby.Server.Implementations.Data } } - public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This) + public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement) { - while (This.MoveNext()) + while (statement.MoveNext()) { - yield return This.Current; + yield return statement.Current; } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index aac0d8d38..ca5cd6fdd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -39,12 +39,11 @@ namespace Emby.Server.Implementations.Data { private const string ChaptersTableName = "Chapters2"; - /// <summary> - /// The _app paths - /// </summary> private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; + // TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static helper method + private readonly IImageProcessor _imageProcessor; private readonly TypeMapper _typeMapper; private readonly JsonSerializerOptions _jsonOptions; @@ -71,7 +70,8 @@ namespace Emby.Server.Implementations.Data IServerConfigurationManager config, IServerApplicationHost appHost, ILogger<SqliteItemRepository> logger, - ILocalizationManager localization) + ILocalizationManager localization, + IImageProcessor imageProcessor) : base(logger) { if (config == null) @@ -82,6 +82,7 @@ namespace Emby.Server.Implementations.Data _config = config; _appHost = appHost; _localization = localization; + _imageProcessor = imageProcessor; _typeMapper = new TypeMapper(); _jsonOptions = JsonDefaults.GetOptions(); @@ -98,8 +99,6 @@ namespace Emby.Server.Implementations.Data /// <inheritdoc /> protected override TempStoreMode TempStore => TempStoreMode.Memory; - public IImageProcessor ImageProcessor { get; set; } - /// <summary> /// Opens the connection to the database /// </summary> @@ -1993,7 +1992,7 @@ namespace Emby.Server.Implementations.Data { try { - chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter); + chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter); } catch (Exception ex) { @@ -3322,7 +3321,7 @@ namespace Emby.Server.Implementations.Data for (int i = 0; i < str.Length; i++) { - if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i])))) + if (!char.IsLetter(str[i]) && !char.IsNumber(str[i])) { return false; } @@ -3346,7 +3345,7 @@ namespace Emby.Server.Implementations.Data return IsAlphaNumeric(value); } - private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "") + private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement) { if (query.IsResumable ?? false) { @@ -3358,27 +3357,27 @@ namespace Emby.Server.Implementations.Data if (query.IsHD.HasValue) { - var threshold = 1200; + const int Threshold = 1200; if (query.IsHD.Value) { - minWidth = threshold; + minWidth = Threshold; } else { - maxWidth = threshold - 1; + maxWidth = Threshold - 1; } } if (query.Is4K.HasValue) { - var threshold = 3800; + const int Threshold = 3800; if (query.Is4K.Value) { - minWidth = threshold; + minWidth = Threshold; } else { - maxWidth = threshold - 1; + maxWidth = Threshold - 1; } } @@ -3387,93 +3386,61 @@ namespace Emby.Server.Implementations.Data if (minWidth.HasValue) { whereClauses.Add("Width>=@MinWidth"); - if (statement != null) - { - statement.TryBind("@MinWidth", minWidth); - } + statement?.TryBind("@MinWidth", minWidth); } + if (query.MinHeight.HasValue) { whereClauses.Add("Height>=@MinHeight"); - if (statement != null) - { - statement.TryBind("@MinHeight", query.MinHeight); - } + statement?.TryBind("@MinHeight", query.MinHeight); } + if (maxWidth.HasValue) { whereClauses.Add("Width<=@MaxWidth"); - if (statement != null) - { - statement.TryBind("@MaxWidth", maxWidth); - } + statement?.TryBind("@MaxWidth", maxWidth); } + if (query.MaxHeight.HasValue) { whereClauses.Add("Height<=@MaxHeight"); - if (statement != null) - { - statement.TryBind("@MaxHeight", query.MaxHeight); - } + statement?.TryBind("@MaxHeight", query.MaxHeight); } if (query.IsLocked.HasValue) { whereClauses.Add("IsLocked=@IsLocked"); - if (statement != null) - { - statement.TryBind("@IsLocked", query.IsLocked); - } + statement?.TryBind("@IsLocked", query.IsLocked); } var tags = query.Tags.ToList(); var excludeTags = query.ExcludeTags.ToList(); - if (query.IsMovie ?? false) + if (query.IsMovie == true) { - var alternateTypes = new List<string>(); - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) - { - alternateTypes.Add(typeof(Movie).FullName); - } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) - { - alternateTypes.Add(typeof(Trailer).FullName); - } - - var programAttribtues = new List<string>(); - if (alternateTypes.Count == 0) + if (query.IncludeItemTypes.Length == 0 + || query.IncludeItemTypes.Contains(nameof(Movie)) + || query.IncludeItemTypes.Contains(nameof(Trailer))) { - programAttribtues.Add("IsMovie=@IsMovie"); + whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); } else { - programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + whereClauses.Add("IsMovie=@IsMovie"); } - if (statement != null) - { - statement.TryBind("@IsMovie", true); - } - - whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")"); + statement?.TryBind("@IsMovie", true); } else if (query.IsMovie.HasValue) { whereClauses.Add("IsMovie=@IsMovie"); - if (statement != null) - { - statement.TryBind("@IsMovie", query.IsMovie); - } + statement?.TryBind("@IsMovie", query.IsMovie); } if (query.IsSeries.HasValue) { whereClauses.Add("IsSeries=@IsSeries"); - if (statement != null) - { - statement.TryBind("@IsSeries", query.IsSeries); - } + statement?.TryBind("@IsSeries", query.IsSeries); } if (query.IsSports.HasValue) @@ -3525,10 +3492,7 @@ namespace Emby.Server.Implementations.Data if (query.IsFolder.HasValue) { whereClauses.Add("IsFolder=@IsFolder"); - if (statement != null) - { - statement.TryBind("@IsFolder", query.IsFolder); - } + statement?.TryBind("@IsFolder", query.IsFolder); } var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); @@ -3539,10 +3503,7 @@ namespace Emby.Server.Implementations.Data if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); - if (statement != null) - { - statement.TryBind("@type", excludeTypes[0]); - } + statement?.TryBind("@type", excludeTypes[0]); } else if (excludeTypes.Length > 1) { @@ -3553,10 +3514,7 @@ namespace Emby.Server.Implementations.Data else if (includeTypes.Length == 1) { whereClauses.Add("type=@type"); - if (statement != null) - { - statement.TryBind("@type", includeTypes[0]); - } + statement?.TryBind("@type", includeTypes[0]); } else if (includeTypes.Length > 1) { @@ -3567,10 +3525,7 @@ namespace Emby.Server.Implementations.Data if (query.ChannelIds.Length == 1) { whereClauses.Add("ChannelId=@ChannelId"); - if (statement != null) - { - statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); - } + statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); } else if (query.ChannelIds.Length > 1) { @@ -3581,98 +3536,65 @@ namespace Emby.Server.Implementations.Data if (!query.ParentId.Equals(Guid.Empty)) { whereClauses.Add("ParentId=@ParentId"); - if (statement != null) - { - statement.TryBind("@ParentId", query.ParentId); - } + statement?.TryBind("@ParentId", query.ParentId); } if (!string.IsNullOrWhiteSpace(query.Path)) { whereClauses.Add("Path=@Path"); - if (statement != null) - { - statement.TryBind("@Path", GetPathToSave(query.Path)); - } + statement?.TryBind("@Path", GetPathToSave(query.Path)); } if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) { whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey"); - if (statement != null) - { - statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey); - } + statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey); } if (query.MinCommunityRating.HasValue) { whereClauses.Add("CommunityRating>=@MinCommunityRating"); - if (statement != null) - { - statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value); - } + statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value); } if (query.MinIndexNumber.HasValue) { whereClauses.Add("IndexNumber>=@MinIndexNumber"); - if (statement != null) - { - statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); - } + statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); } if (query.MinDateCreated.HasValue) { whereClauses.Add("DateCreated>=@MinDateCreated"); - if (statement != null) - { - statement.TryBind("@MinDateCreated", query.MinDateCreated.Value); - } + statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value); } if (query.MinDateLastSaved.HasValue) { whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); - if (statement != null) - { - statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value); - } + statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value); } if (query.MinDateLastSavedForUser.HasValue) { whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)"); - if (statement != null) - { - statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value); - } + statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value); } if (query.IndexNumber.HasValue) { whereClauses.Add("IndexNumber=@IndexNumber"); - if (statement != null) - { - statement.TryBind("@IndexNumber", query.IndexNumber.Value); - } + statement?.TryBind("@IndexNumber", query.IndexNumber.Value); } if (query.ParentIndexNumber.HasValue) { whereClauses.Add("ParentIndexNumber=@ParentIndexNumber"); - if (statement != null) - { - statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); - } + statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); } if (query.ParentIndexNumberNotEquals.HasValue) { whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)"); - if (statement != null) - { - statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); - } + statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value); } var minEndDate = query.MinEndDate; @@ -3693,73 +3615,59 @@ namespace Emby.Server.Implementations.Data if (minEndDate.HasValue) { whereClauses.Add("EndDate>=@MinEndDate"); - if (statement != null) - { - statement.TryBind("@MinEndDate", minEndDate.Value); - } + statement?.TryBind("@MinEndDate", minEndDate.Value); } if (maxEndDate.HasValue) { whereClauses.Add("EndDate<=@MaxEndDate"); - if (statement != null) - { - statement.TryBind("@MaxEndDate", maxEndDate.Value); - } + statement?.TryBind("@MaxEndDate", maxEndDate.Value); } if (query.MinStartDate.HasValue) { whereClauses.Add("StartDate>=@MinStartDate"); - if (statement != null) - { - statement.TryBind("@MinStartDate", query.MinStartDate.Value); - } + statement?.TryBind("@MinStartDate", query.MinStartDate.Value); } if (query.MaxStartDate.HasValue) { whereClauses.Add("StartDate<=@MaxStartDate"); - if (statement != null) - { - statement.TryBind("@MaxStartDate", query.MaxStartDate.Value); - } + statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value); } if (query.MinPremiereDate.HasValue) { whereClauses.Add("PremiereDate>=@MinPremiereDate"); - if (statement != null) - { - statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value); - } + statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value); } + if (query.MaxPremiereDate.HasValue) { whereClauses.Add("PremiereDate<=@MaxPremiereDate"); - if (statement != null) - { - statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value); - } + statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value); } - if (query.TrailerTypes.Length > 0) + var trailerTypes = query.TrailerTypes; + int trailerTypesLen = trailerTypes.Length; + if (trailerTypesLen > 0) { - var clauses = new List<string>(); - var index = 0; - foreach (var type in query.TrailerTypes) + const string Or = " OR "; + StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32); + for (int i = 0; i < trailerTypesLen; i++) { - var paramName = "@TrailerTypes" + index; - - clauses.Add("TrailerTypes like " + paramName); - if (statement != null) - { - statement.TryBind(paramName, "%" + type + "%"); - } - index++; + var paramName = "@TrailerTypes" + i; + clause.Append("TrailerTypes like ") + .Append(paramName) + .Append(Or); + statement?.TryBind(paramName, "%" + trailerTypes[i] + "%"); } - var clause = "(" + string.Join(" OR ", clauses) + ")"; - whereClauses.Add(clause); + + // Remove last " OR " + clause.Length -= Or.Length; + clause.Append(')'); + + whereClauses.Add(clause.ToString()); } if (query.IsAiring.HasValue) @@ -3767,24 +3675,15 @@ namespace Emby.Server.Implementations.Data if (query.IsAiring.Value) { whereClauses.Add("StartDate<=@MaxStartDate"); - if (statement != null) - { - statement.TryBind("@MaxStartDate", DateTime.UtcNow); - } + statement?.TryBind("@MaxStartDate", DateTime.UtcNow); whereClauses.Add("EndDate>=@MinEndDate"); - if (statement != null) - { - statement.TryBind("@MinEndDate", DateTime.UtcNow); - } + statement?.TryBind("@MinEndDate", DateTime.UtcNow); } else { whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)"); - if (statement != null) - { - statement.TryBind("@IsAiringDate", DateTime.UtcNow); - } + statement?.TryBind("@IsAiringDate", DateTime.UtcNow); } } @@ -3799,13 +3698,10 @@ namespace Emby.Server.Implementations.Data var paramName = "@PersonId" + index; clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))"); - - if (statement != null) - { - statement.TryBind(paramName, personId.ToByteArray()); - } + statement?.TryBind(paramName, personId.ToByteArray()); index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3813,47 +3709,31 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrWhiteSpace(query.Person)) { whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)"); - if (statement != null) - { - statement.TryBind("@PersonName", query.Person); - } + statement?.TryBind("@PersonName", query.Person); } if (!string.IsNullOrWhiteSpace(query.MinSortName)) { whereClauses.Add("SortName>=@MinSortName"); - if (statement != null) - { - statement.TryBind("@MinSortName", query.MinSortName); - } + statement?.TryBind("@MinSortName", query.MinSortName); } if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId)) { whereClauses.Add("ExternalSeriesId=@ExternalSeriesId"); - if (statement != null) - { - statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId); - } + statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId); } if (!string.IsNullOrWhiteSpace(query.ExternalId)) { whereClauses.Add("ExternalId=@ExternalId"); - if (statement != null) - { - statement.TryBind("@ExternalId", query.ExternalId); - } + statement?.TryBind("@ExternalId", query.ExternalId); } if (!string.IsNullOrWhiteSpace(query.Name)) { whereClauses.Add("CleanName=@Name"); - - if (statement != null) - { - statement.TryBind("@Name", GetCleanValue(query.Name)); - } + statement?.TryBind("@Name", GetCleanValue(query.Name)); } // These are the same, for now @@ -3872,28 +3752,21 @@ namespace Emby.Server.Implementations.Data if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) { whereClauses.Add("SortName like @NameStartsWith"); - if (statement != null) - { - statement.TryBind("@NameStartsWith", query.NameStartsWith + "%"); - } + statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%"); } + if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) { whereClauses.Add("SortName >= @NameStartsWithOrGreater"); // lowercase this because SortName is stored as lowercase - if (statement != null) - { - statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); - } + statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); } + if (!string.IsNullOrWhiteSpace(query.NameLessThan)) { whereClauses.Add("SortName < @NameLessThan"); // lowercase this because SortName is stored as lowercase - if (statement != null) - { - statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); - } + statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); } if (query.ImageTypes.Length > 0) @@ -3909,18 +3782,12 @@ namespace Emby.Server.Implementations.Data if (query.IsLiked.Value) { whereClauses.Add("rating>=@UserRating"); - if (statement != null) - { - statement.TryBind("@UserRating", UserItemData.MinLikeValue); - } + statement?.TryBind("@UserRating", UserItemData.MinLikeValue); } else { whereClauses.Add("(rating is null or rating<@UserRating)"); - if (statement != null) - { - statement.TryBind("@UserRating", UserItemData.MinLikeValue); - } + statement?.TryBind("@UserRating", UserItemData.MinLikeValue); } } @@ -3934,10 +3801,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)"); } - if (statement != null) - { - statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value); - } + + statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value); } if (query.IsFavorite.HasValue) @@ -3950,10 +3815,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)"); } - if (statement != null) - { - statement.TryBind("@IsFavorite", query.IsFavorite.Value); - } + + statement?.TryBind("@IsFavorite", query.IsFavorite.Value); } if (EnableJoinUserData(query)) @@ -3982,10 +3845,8 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(played is null or played=@IsPlayed)"); } - if (statement != null) - { - statement.TryBind("@IsPlayed", query.IsPlayed.Value); - } + + statement?.TryBind("@IsPlayed", query.IsPlayed.Value); } } } @@ -4017,6 +3878,7 @@ namespace Emby.Server.Implementations.Data } index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4036,6 +3898,7 @@ namespace Emby.Server.Implementations.Data } index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4769,18 +4632,22 @@ namespace Emby.Server.Implementations.Data { list.Add(typeof(Person).Name); } + if (IsTypeInQuery(typeof(Genre).Name, query)) { list.Add(typeof(Genre).Name); } + if (IsTypeInQuery(typeof(MusicGenre).Name, query)) { list.Add(typeof(MusicGenre).Name); } + if (IsTypeInQuery(typeof(MusicArtist).Name, query)) { list.Add(typeof(MusicArtist).Name); } + if (IsTypeInQuery(typeof(Studio).Name, query)) { list.Add(typeof(Studio).Name); @@ -4854,7 +4721,7 @@ namespace Emby.Server.Implementations.Data return false; } - private static readonly Type[] KnownTypes = + private static readonly Type[] _knownTypes = { typeof(LiveTvProgram), typeof(LiveTvChannel), @@ -4923,7 +4790,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); - foreach (var t in KnownTypes) + foreach (var t in _knownTypes) { dict[t.Name] = new[] { t.FullName }; } @@ -4935,7 +4802,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } // Not crazy about having this all the way down here, but at least it's in one place - readonly Dictionary<string, string[]> _types = GetTypeMapDictionary(); + private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary(); private string[] MapIncludeItemTypes(string value) { @@ -4952,7 +4819,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return Array.Empty<string>(); } - public void DeleteItem(Guid id, CancellationToken cancellationToken) + public void DeleteItem(Guid id) { if (id == Guid.Empty) { @@ -4988,7 +4855,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value) + private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value) { using (var statement = PrepareStatement(db, query)) { @@ -5548,6 +5415,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { GetWhereClauses(typeSubQuery, null); } + BindSimilarParams(query, statement); BindSearchParams(query, statement); GetWhereClauses(innerQuery, statement); @@ -5589,7 +5457,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) - .ToLookup(i => i); + .ToLookup(x => x); foreach (var type in allTypes) { @@ -5680,30 +5548,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db) { + const int Limit = 100; var startIndex = 0; - var limit = 100; while (startIndex < values.Count) { var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values "); - var endIndex = Math.Min(values.Count, startIndex + limit); - var isSubsequentRow = false; + var endIndex = Math.Min(values.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - if (isSubsequentRow) - { - insertText.Append(','); - } - insertText.AppendFormat( CultureInfo.InvariantCulture, - "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})", + "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),", i); - isSubsequentRow = true; } + // Remove last comma + insertText.Length--; + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5731,7 +5595,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } @@ -5766,28 +5630,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db) { + const int Limit = 100; var startIndex = 0; - var limit = 100; var listIndex = 0; while (startIndex < people.Count) { var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values "); - var endIndex = Math.Min(people.Count, startIndex + limit); - var isSubsequentRow = false; - + var endIndex = Math.Min(people.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - if (isSubsequentRow) - { - insertText.Append(','); - } - - insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture)); - isSubsequentRow = true; + insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture)); } + // Remove last comma + insertText.Length--; + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); @@ -5811,16 +5670,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader) { - var item = new PersonInfo(); - - item.ItemId = reader.GetGuid(0); - item.Name = reader.GetString(1); + var item = new PersonInfo + { + ItemId = reader.GetGuid(0), + Name = reader.GetString(1) + }; if (!reader.IsDBNull(2)) { @@ -5927,20 +5787,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db) { + const int Limit = 10; var startIndex = 0; - var limit = 10; while (startIndex < streams.Count) { - var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns))); + var insertText = new StringBuilder("insert into mediastreams ("); + foreach (var column in _mediaStreamSaveColumns) + { + insertText.Append(column).Append(','); + } - var endIndex = Math.Min(streams.Count, startIndex + limit); + // Remove last comma + insertText.Length--; + insertText.Append(") values "); + + var endIndex = Math.Min(streams.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { if (i != startIndex) { - insertText.Append(","); + insertText.Append(','); } var index = i.ToString(CultureInfo.InvariantCulture); @@ -5948,11 +5816,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type foreach (var column in _mediaStreamSaveColumns.Skip(1)) { - insertText.Append("@" + column + index + ","); + insertText.Append('@').Append(column).Append(index).Append(','); } + insertText.Length -= 1; // Remove the last comma - insertText.Append(")"); + insertText.Append(')'); } using (var statement = PrepareStatement(db, insertText.ToString())) @@ -6014,7 +5883,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.MoveNext(); } - startIndex += limit; + startIndex += Limit; } } @@ -6031,7 +5900,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type Index = reader[1].ToInt() }; - item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true); + item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true); if (reader[3].SQLiteType != SQLiteType.Null) { diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index adb8e793d..579cb895e 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -38,10 +38,11 @@ namespace Emby.Server.Implementations.Devices private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; - private readonly IAuthenticationRepository _authRepo; + private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache; public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; + public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded; private readonly object _cameraUploadSyncLock = new object(); @@ -65,10 +66,9 @@ namespace Emby.Server.Implementations.Devices _libraryManager = libraryManager; _localizationManager = localizationManager; _authRepo = authRepo; + _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase); } - - private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase); public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) { var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json"); diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs deleted file mode 100644 index bfa49ac5f..000000000 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ /dev/null @@ -1,152 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; - -namespace Emby.Server.Implementations.Diagnostics -{ - public class CommonProcess : IProcess - { - private readonly Process _process; - - private bool _disposed = false; - private bool _hasExited; - - public CommonProcess(ProcessOptions options) - { - StartInfo = options; - - var startInfo = new ProcessStartInfo - { - Arguments = options.Arguments, - FileName = options.FileName, - WorkingDirectory = options.WorkingDirectory, - UseShellExecute = options.UseShellExecute, - CreateNoWindow = options.CreateNoWindow, - RedirectStandardError = options.RedirectStandardError, - RedirectStandardInput = options.RedirectStandardInput, - RedirectStandardOutput = options.RedirectStandardOutput, - ErrorDialog = options.ErrorDialog - }; - - - if (options.IsHidden) - { - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - } - - _process = new Process - { - StartInfo = startInfo - }; - - if (options.EnableRaisingEvents) - { - _process.EnableRaisingEvents = true; - _process.Exited += OnProcessExited; - } - } - - public event EventHandler Exited; - - public ProcessOptions StartInfo { get; } - - public StreamWriter StandardInput => _process.StandardInput; - - public StreamReader StandardError => _process.StandardError; - - public StreamReader StandardOutput => _process.StandardOutput; - - public int ExitCode => _process.ExitCode; - - private bool HasExited - { - get - { - if (_hasExited) - { - return true; - } - - try - { - _hasExited = _process.HasExited; - } - catch (InvalidOperationException) - { - _hasExited = true; - } - - return _hasExited; - } - } - - public void Start() - { - _process.Start(); - } - - public void Kill() - { - _process.Kill(); - } - - public bool WaitForExit(int timeMs) - { - return _process.WaitForExit(timeMs); - } - - public Task<bool> WaitForExitAsync(int timeMs) - { - // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. - - if (HasExited) - { - return Task.FromResult(true); - } - - timeMs = Math.Max(0, timeMs); - - var tcs = new TaskCompletionSource<bool>(); - - var cancellationToken = new CancellationTokenSource(timeMs).Token; - - _process.Exited += (sender, args) => tcs.TrySetResult(true); - - cancellationToken.Register(() => tcs.TrySetResult(HasExited)); - - return tcs.Task; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _process?.Dispose(); - } - - _disposed = true; - } - - private void OnProcessExited(object sender, EventArgs e) - { - _hasExited = true; - Exited?.Invoke(this, e); - } - } -} diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs deleted file mode 100644 index 02ad3c1a8..000000000 --- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Model.Diagnostics; - -namespace Emby.Server.Implementations.Diagnostics -{ - public class ProcessFactory : IProcessFactory - { - public IProcess Create(ProcessOptions options) - { - return new CommonProcess(options); - } - } -} diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 65711e89d..c4b65d265 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -38,21 +38,23 @@ namespace Emby.Server.Implementations.Dto private readonly IProviderManager _providerManager; private readonly IApplicationHost _appHost; - private readonly Func<IMediaSourceManager> _mediaSourceManager; - private readonly Func<ILiveTvManager> _livetvManager; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly Lazy<ILiveTvManager> _livetvManagerFactory; + + private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; public DtoService( - ILoggerFactory loggerFactory, + ILogger<DtoService> logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IProviderManager providerManager, IApplicationHost appHost, - Func<IMediaSourceManager> mediaSourceManager, - Func<ILiveTvManager> livetvManager) + IMediaSourceManager mediaSourceManager, + Lazy<ILiveTvManager> livetvManagerFactory) { - _logger = loggerFactory.CreateLogger(nameof(DtoService)); + _logger = logger; _libraryManager = libraryManager; _userDataRepository = userDataRepository; _itemRepo = itemRepo; @@ -60,7 +62,7 @@ namespace Emby.Server.Implementations.Dto _providerManager = providerManager; _appHost = appHost; _mediaSourceManager = mediaSourceManager; - _livetvManager = livetvManager; + _livetvManagerFactory = livetvManagerFactory; } /// <summary> @@ -125,12 +127,12 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); + LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); } if (channelTuples.Count > 0) { - _livetvManager().AddChannelInfo(channelTuples, options, user); + LivetvManager.AddChannelInfo(channelTuples, options, user); } return returnItems; @@ -142,12 +144,12 @@ namespace Emby.Server.Implementations.Dto if (item is LiveTvChannel tvChannel) { var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) }; - _livetvManager().AddChannelInfo(list, options, user); + LivetvManager.AddChannelInfo(list, options, user); } else if (item is LiveTvProgram) { var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) }; - var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user); + var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user); Task.WaitAll(task); } @@ -223,7 +225,7 @@ namespace Emby.Server.Implementations.Dto if (item is IHasMediaSources && options.ContainsField(ItemFields.MediaSources)) { - dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray(); + dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray(); NormalizeMediaSourceContainers(dto); } @@ -254,7 +256,7 @@ namespace Emby.Server.Implementations.Dto dto.Etag = item.GetEtag(user); } - var liveTvManager = _livetvManager(); + var liveTvManager = LivetvManager; var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); if (activeRecording != null) { @@ -1045,7 +1047,7 @@ namespace Emby.Server.Implementations.Dto } else { - mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); + mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); } dto.MediaStreams = mediaStreams; @@ -1056,30 +1058,19 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.SpecialFeatureCount)) { - if (allExtras == null) - { - allExtras = item.GetExtras().ToArray(); - } - + allExtras = item.GetExtras().ToArray(); dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value)); } if (options.ContainsField(ItemFields.LocalTrailerCount)) { - int trailerCount = 0; - if (allExtras == null) - { - allExtras = item.GetExtras().ToArray(); - } - - trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer); + allExtras ??= item.GetExtras().ToArray(); + dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer); if (item is IHasTrailers hasTrailers) { - trailerCount += hasTrailers.GetTrailerCount(); + dto.LocalTrailerCount += hasTrailers.GetTrailerCount(); } - - dto.LocalTrailerCount = trailerCount; } // Add EpisodeInfo diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d302d8984..bf4a0d939 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,5 +1,10 @@ <Project Sdk="Microsoft.NET.Sdk"> + <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> + <PropertyGroup> + <ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid> + </PropertyGroup> + <ItemGroup> <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" /> @@ -32,11 +37,11 @@ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" /> - <PackageReference Include="Mono.Nat" Version="2.0.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" /> + <PackageReference Include="Mono.Nat" Version="2.0.1" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" /> - <PackageReference Include="sharpcompress" Version="0.24.0" /> + <PackageReference Include="sharpcompress" Version="0.25.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> - <PackageReference Include="System.Interactive.Async" Version="4.0.0" /> </ItemGroup> <ItemGroup> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index e290c62e1..37d7fd479 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Text; @@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; - private readonly object _createdRulesLock = new object(); - private List<IPEndPoint> _createdRules = new List<IPEndPoint>(); + private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>(); + private Timer _timer; - private string _lastConfigIdentifier; + private string _configIdentifier; private bool _disposed = false; @@ -60,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints return new StringBuilder(32) .Append(config.EnableUPnP).Append(Separator) .Append(config.PublicPort).Append(Separator) + .Append(config.PublicHttpsPort).Append(Separator) .Append(_appHost.HttpPort).Append(Separator) .Append(_appHost.HttpsPort).Append(Separator) .Append(_appHost.EnableHttps).Append(Separator) @@ -69,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints private void OnConfigurationUpdated(object sender, EventArgs e) { - if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) + var oldConfigIdentifier = _configIdentifier; + _configIdentifier = GetConfigIdentifier(); + + if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase)) { Stop(); Start(); @@ -93,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints return; } - _logger.LogDebug("Starting NAT discovery"); + _logger.LogInformation("Starting NAT discovery"); NatUtility.DeviceFound += OnNatUtilityDeviceFound; NatUtility.StartDiscovery(); - _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); + _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered; - - _lastConfigIdentifier = GetConfigIdentifier(); } private void Stop() { - _logger.LogDebug("Stopping NAT discovery"); + _logger.LogInformation("Stopping NAT discovery"); NatUtility.StopDiscovery(); NatUtility.DeviceFound -= OnNatUtilityDeviceFound; @@ -117,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; } - private void ClearCreatedRules(object state) - { - lock (_createdRulesLock) - { - _createdRules.Clear(); - } - } - private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e) { NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp); } - private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e) + private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e) { try { - var device = e.Device; - - CreateRules(device); + await CreateRules(e.Device).ConfigureAwait(false); } catch (Exception ex) { @@ -144,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints } } - private async void CreateRules(INatDevice device) + private Task CreateRules(INatDevice device) { if (_disposed) { @@ -153,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints // On some systems the device discovered event seems to fire repeatedly // This check will help ensure we're not trying to port map the same device over and over - var address = device.DeviceEndpoint; - - lock (_createdRulesLock) + if (!_createdRules.TryAdd(device.DeviceEndpoint, 0)) { - if (!_createdRules.Contains(address)) - { - _createdRules.Add(address); - } - else - { - return; - } + return Task.CompletedTask; } - try - { - await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating http port map"); - return; - } + return Task.WhenAll(CreatePortMaps(device)); + } - try - { - await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false); - } - catch (Exception ex) + private IEnumerable<Task> CreatePortMaps(INatDevice device) + { + yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort); + + if (_appHost.EnableHttps) { - _logger.LogError(ex, "Error creating https port map"); + yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort); } } - private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort) + private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort) { _logger.LogDebug( - "Creating port map on local port {0} to public port {1} with device {2}", + "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}", privatePort, publicPort, device.DeviceEndpoint); - return device.CreatePortMapAsync( - new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name)); + try + { + var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name); + await device.CreatePortMapAsync(mapping).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.", + privatePort, + publicPort, + device.DeviceEndpoint); + } } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 8e9771931..2e738deeb 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -16,46 +16,63 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IServerApplicationHost _appHost; private readonly IConfiguration _appConfig; private readonly IServerConfigurationManager _config; + private readonly IStartupOptions _startupOptions; /// <summary> /// Initializes a new instance of the <see cref="StartupWizard"/> class. /// </summary> /// <param name="appHost">The application host.</param> + /// <param name="appConfig">The application configuration.</param> /// <param name="config">The configuration manager.</param> - public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config) + /// <param name="startupOptions">The application startup options.</param> + public StartupWizard( + IServerApplicationHost appHost, + IConfiguration appConfig, + IServerConfigurationManager config, + IStartupOptions startupOptions) { _appHost = appHost; _appConfig = appConfig; _config = config; + _startupOptions = startupOptions; } /// <inheritdoc /> public Task RunAsync() { + Run(); + return Task.CompletedTask; + } + + private void Run() + { if (!_appHost.CanLaunchWebBrowser) { - return Task.CompletedTask; + return; } - if (!_appConfig.HostWebClient()) + // Always launch the startup wizard if possible when it has not been completed + if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient()) { - BrowserLauncher.OpenSwaggerPage(_appHost); + BrowserLauncher.OpenWebApp(_appHost); + return; + } + + // Do nothing if the web app is configured to not run automatically + if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp) + { + return; } - else if (!_config.Configuration.IsStartupWizardCompleted) + + // Launch the swagger page if the web client is not hosted, otherwise open the web client + if (_appConfig.HostWebClient()) { BrowserLauncher.OpenWebApp(_appHost); } - else if (_config.Configuration.AutoRunWebApp) + else { - var options = ((ApplicationHost)_appHost).StartupOptions; - - if (!options.NoAutoRunWebApp) - { - BrowserLauncher.OpenWebApp(_appHost); - } + BrowserLauncher.OpenSwaggerPage(_appHost); } - - return Task.CompletedTask; } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 882bfe2f6..d66bb7638 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -24,7 +25,7 @@ namespace Emby.Server.Implementations.HttpClientManager private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly Func<string> _defaultUserAgentFn; + private readonly IApplicationHost _appHost; /// <summary> /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. @@ -40,12 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager IApplicationPaths appPaths, ILogger<HttpClientManager> logger, IFileSystem fileSystem, - Func<string> defaultUserAgentFn) + IApplicationHost appHost) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _fileSystem = fileSystem; _appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths)); - _defaultUserAgentFn = defaultUserAgentFn; + _appHost = appHost; } /// <summary> @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.EnableDefaultUserAgent && !request.Headers.TryGetValues(HeaderNames.UserAgent, out _)) { - request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn()); + request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent); } switch (options.DecompressionMethod) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7a812f320..211a0c1d9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; @@ -23,6 +24,7 @@ using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -48,6 +50,8 @@ namespace Emby.Server.Implementations.HttpServer private readonly string _baseUrlPrefix; private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>(); private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>(); + private readonly IHostEnvironment _hostEnvironment; + private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); private bool _disposed = false; @@ -61,7 +65,8 @@ namespace Emby.Server.Implementations.HttpServer IXmlSerializer xmlSerializer, IHttpListener socketListener, ILocalizationManager localizationManager, - ServiceController serviceController) + ServiceController serviceController, + IHostEnvironment hostEnvironment) { _appHost = applicationHost; _logger = logger; @@ -75,6 +80,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceController = serviceController; _socketListener.WebSocketConnected = OnWebSocketConnected; + _hostEnvironment = hostEnvironment; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); @@ -225,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer switch (ex) { case ArgumentException _: return 400; - case SecurityException _: return 401; + case AuthenticationException _: return 401; + case SecurityException _: return 403; case DirectoryNotFoundException _: case FileNotFoundException _: case ResourceNotFoundException _: return 404; @@ -234,55 +241,52 @@ namespace Emby.Server.Implementations.HttpServer } } - private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace) + private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog) { - try - { - ex = GetActualException(ex); + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is AuthenticationException + || ex is FileNotFoundException; - if (logExceptionStackTrace) - { - _logger.LogError(ex, "Error processing request"); - } - else - { - _logger.LogError("Error processing request: {Message}", ex.Message); - } - - var httpRes = httpReq.Response; - - if (httpRes.HasStarted) - { - return; - } + if (ignoreStackTrace) + { + _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog); + } + else + { + _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog); + } - var statusCode = GetStatusCode(ex); - httpRes.StatusCode = statusCode; + var httpRes = httpReq.Response; - var errContent = NormalizeExceptionMessage(ex.Message); - httpRes.ContentType = "text/plain"; - httpRes.ContentLength = errContent.Length; - await httpRes.WriteAsync(errContent).ConfigureAwait(false); - } - catch (Exception errorEx) + if (httpRes.HasStarted) { - _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); + return; } + + httpRes.StatusCode = statusCode; + + var errContent = NormalizeExceptionMessage(ex) ?? string.Empty; + httpRes.ContentType = "text/plain"; + httpRes.ContentLength = errContent.Length; + await httpRes.WriteAsync(errContent).ConfigureAwait(false); } - private string NormalizeExceptionMessage(string msg) + private string NormalizeExceptionMessage(Exception ex) { - if (msg == null) + // Do not expose the exception message for AuthenticationException + if (ex is AuthenticationException) { - return string.Empty; + return null; } // Strip any information we don't want to reveal - - msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase); - msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase); - - return msg; + return ex.Message + ?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase) + .Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -451,7 +455,7 @@ namespace Emby.Server.Implementations.HttpServer var stopWatch = new Stopwatch(); stopWatch.Start(); var httpRes = httpReq.Response; - string urlToLog = null; + string urlToLog = GetUrlToLog(urlString); string remoteIp = httpReq.RemoteIp; try @@ -497,8 +501,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - urlToLog = GetUrlToLog(urlString); - if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase) || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) @@ -530,22 +532,35 @@ namespace Emby.Server.Implementations.HttpServer } else { - await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false); + throw new FileNotFoundException(); } } - catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException) + catch (Exception requestEx) { - await ErrorHandler(ex, httpReq, false).ConfigureAwait(false); - } - catch (SecurityException ex) - { - await ErrorHandler(ex, httpReq, false).ConfigureAwait(false); - } - catch (Exception ex) - { - var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); + try + { + var requestInnerEx = GetActualException(requestEx); + var statusCode = GetStatusCode(requestInnerEx); + + // Do not handle 500 server exceptions manually when in development mode + // The framework-defined development exception page will be returned instead + if (statusCode == 500 && _hostEnvironment.IsDevelopment()) + { + throw; + } + + await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false); + } + catch (Exception handlerException) + { + var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException); + _logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog); - await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false); + if (_hostEnvironment.IsDevelopment()) + { + throw aggregateEx; + } + } } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index b42662420..464ca3a0b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -28,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> public class HttpResultFactory : IHttpResultFactory { + // Last-Modified and If-Modified-Since must follow strict date format, + // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since + private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\""; + // We specifically use en-US culture because both day of week and month names require it + private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false); + /// <summary> /// The logger. /// </summary> @@ -420,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer if (!noCache) { - DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); + if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader)) + { + _logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]); + return null; + } if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified)) { @@ -629,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer if (lastModifiedDate.HasValue) { - responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture); + responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture); } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 58421aaf1..256b24924 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Security.Authentication; using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (user == null && auth.UserId != Guid.Empty) { - throw new SecurityException("User with Id " + auth.UserId + " not found"); + throw new AuthenticationException("User with Id " + auth.UserId + " not found"); } if (user != null) @@ -108,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (user.Policy.IsDisabled) { - throw new SecurityException("User account has been disabled.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; + throw new SecurityException("User account has been disabled."); } if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) { - throw new SecurityException("User account has been disabled.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; + throw new SecurityException("User account has been disabled."); } if (!user.Policy.IsAdministrator @@ -128,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); - throw new SecurityException("This user account is not allowed access at this time.") - { - SecurityExceptionType = SecurityExceptionType.ParentalControl - }; + throw new SecurityException("This user account is not allowed access at this time."); } } @@ -190,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (user == null || !user.Policy.IsAdministrator) { - throw new SecurityException("User does not have admin access.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; + throw new SecurityException("User does not have admin access."); } } @@ -201,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (user == null || !user.Policy.EnableContentDeletion) { - throw new SecurityException("User does not have delete access.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; + throw new SecurityException("User does not have delete access."); } } @@ -212,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (user == null || !user.Policy.EnableContentDownloading) { - throw new SecurityException("User does not have download access.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; + throw new SecurityException("User does not have download access."); } } } @@ -230,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (string.IsNullOrEmpty(token)) { - throw new SecurityException("Access token is required."); + throw new AuthenticationException("Access token is required."); } var info = GetTokenInfo(request); if (info == null) { - throw new SecurityException("Access token is invalid or expired."); + throw new AuthenticationException("Access token is invalid or expired."); } //if (!string.IsNullOrEmpty(info.UserId)) diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index b1fb8cc63..5a1eb43bc 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -17,6 +17,11 @@ namespace Emby.Server.Implementations.IO { public class LibraryMonitor : ILibraryMonitor { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; + /// <summary> /// The file system watchers. /// </summary> @@ -113,34 +118,23 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path); + _logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path); } } } /// <summary> - /// Gets or sets the logger. - /// </summary> - /// <value>The logger.</value> - private ILogger Logger { get; set; } - - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly IFileSystem _fileSystem; - - /// <summary> /// Initializes a new instance of the <see cref="LibraryMonitor" /> class. /// </summary> public LibraryMonitor( - ILoggerFactory loggerFactory, + ILogger<LibraryMonitor> logger, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) { - LibraryManager = libraryManager; - Logger = loggerFactory.CreateLogger(GetType().Name); - ConfigurationManager = configurationManager; + _libraryManager = libraryManager; + _logger = logger; + _configurationManager = configurationManager; _fileSystem = fileSystem; } @@ -151,7 +145,7 @@ namespace Emby.Server.Implementations.IO return false; } - var options = LibraryManager.GetLibraryOptions(item); + var options = _libraryManager.GetLibraryOptions(item); if (options != null) { @@ -163,12 +157,12 @@ namespace Emby.Server.Implementations.IO public void Start() { - LibraryManager.ItemAdded += OnLibraryManagerItemAdded; - LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved; + _libraryManager.ItemAdded += OnLibraryManagerItemAdded; + _libraryManager.ItemRemoved += OnLibraryManagerItemRemoved; var pathsToWatch = new List<string>(); - var paths = LibraryManager + var paths = _libraryManager .RootFolder .Children .Where(IsLibraryMonitorEnabled) @@ -261,7 +255,7 @@ namespace Emby.Server.Implementations.IO if (!Directory.Exists(path)) { // Seeing a crash in the mono runtime due to an exception being thrown on a different thread - Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path); + _logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path); return; } @@ -297,7 +291,7 @@ namespace Emby.Server.Implementations.IO if (_fileSystemWatchers.TryAdd(path, newWatcher)) { newWatcher.EnableRaisingEvents = true; - Logger.LogInformation("Watching directory " + path); + _logger.LogInformation("Watching directory " + path); } else { @@ -307,7 +301,7 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Error watching path: {path}", path); + _logger.LogError(ex, "Error watching path: {path}", path); } }); } @@ -333,7 +327,7 @@ namespace Emby.Server.Implementations.IO { using (watcher) { - Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path); + _logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path); watcher.Created -= OnWatcherChanged; watcher.Deleted -= OnWatcherChanged; @@ -372,7 +366,7 @@ namespace Emby.Server.Implementations.IO var ex = e.GetException(); var dw = (FileSystemWatcher)sender; - Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path); + _logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path); DisposeWatcher(dw, true); } @@ -390,7 +384,7 @@ namespace Emby.Server.Implementations.IO } catch (Exception ex) { - Logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath); + _logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath); } } @@ -416,13 +410,13 @@ namespace Emby.Server.Implementations.IO { if (_fileSystem.AreEqual(i, path)) { - Logger.LogDebug("Ignoring change to {Path}", path); + _logger.LogDebug("Ignoring change to {Path}", path); return true; } if (_fileSystem.ContainsSubPath(i, path)) { - Logger.LogDebug("Ignoring change to {Path}", path); + _logger.LogDebug("Ignoring change to {Path}", path); return true; } @@ -430,7 +424,7 @@ namespace Emby.Server.Implementations.IO var parent = Path.GetDirectoryName(i); if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path)) { - Logger.LogDebug("Ignoring change to {Path}", path); + _logger.LogDebug("Ignoring change to {Path}", path); return true; } @@ -485,7 +479,7 @@ namespace Emby.Server.Implementations.IO } } - var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger); + var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger); newRefresher.Completed += NewRefresher_Completed; _activeRefreshers.Add(newRefresher); } @@ -502,8 +496,8 @@ namespace Emby.Server.Implementations.IO /// </summary> public void Stop() { - LibraryManager.ItemAdded -= OnLibraryManagerItemAdded; - LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; + _libraryManager.ItemAdded -= OnLibraryManagerItemAdded; + _libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved; foreach (var watcher in _fileSystemWatchers.Values.ToList()) { diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 6e915de3d..16b68170b 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -3,33 +3,38 @@ namespace Emby.Server.Implementations public interface IStartupOptions { /// <summary> - /// --ffmpeg + /// Gets the value of the --ffmpeg command line option. /// </summary> string FFmpegPath { get; } /// <summary> - /// --service + /// Gets the value of the --service command line option. /// </summary> bool IsService { get; } /// <summary> - /// --noautorunwebapp + /// Gets the value of the --noautorunwebapp command line option. /// </summary> bool NoAutoRunWebApp { get; } /// <summary> - /// --package-name + /// Gets the value of the --package-name command line option. /// </summary> string PackageName { get; } /// <summary> - /// --restartpath + /// Gets the value of the --restartpath command line option. /// </summary> string RestartPath { get; } /// <summary> - /// --restartargs + /// Gets the value of the --restartargs command line option. /// </summary> string RestartArgs { get; } + + /// <summary> + /// Gets the value of the --plugin-manifest-url command line option. + /// </summary> + string PluginManifestUrl { get; } } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index ab036eca7..52c8facc3 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library { if (resolvedUser == null) { - throw new ArgumentNullException(nameof(resolvedUser)); + throw new AuthenticationException($"Specified user does not exist."); } bool success = false; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8ec4d08be..0b86b2db7 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -54,9 +54,29 @@ namespace Emby.Server.Implementations.Library /// </summary> public class LibraryManager : ILibraryManager { + private readonly ILogger _logger; + private readonly ITaskManager _taskManager; + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataRepository; + private readonly IServerConfigurationManager _configurationManager; + private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory; + private readonly Lazy<IProviderManager> _providerManagerFactory; + private readonly Lazy<IUserViewManager> _userviewManagerFactory; + private readonly IServerApplicationHost _appHost; + private readonly IMediaEncoder _mediaEncoder; + private readonly IFileSystem _fileSystem; + private readonly IItemRepository _itemRepository; + private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache; + private NamingOptions _namingOptions; private string[] _videoFileExtensions; + private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value; + + private IProviderManager ProviderManager => _providerManagerFactory.Value; + + private IUserViewManager UserViewManager => _userviewManagerFactory.Value; + /// <summary> /// Gets or sets the postscan tasks. /// </summary> @@ -90,12 +110,6 @@ namespace Emby.Server.Implementations.Library private IBaseItemComparer[] Comparers { get; set; } /// <summary> - /// Gets or sets the active item repository - /// </summary> - /// <value>The item repository.</value> - public IItemRepository ItemRepository { get; set; } - - /// <summary> /// Occurs when [item added]. /// </summary> public event EventHandler<ItemChangeEventArgs> ItemAdded; @@ -110,90 +124,47 @@ namespace Emby.Server.Implementations.Library /// </summary> public event EventHandler<ItemChangeEventArgs> ItemRemoved; - /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// The _task manager - /// </summary> - private readonly ITaskManager _taskManager; - - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _user data repository - /// </summary> - private readonly IUserDataManager _userDataRepository; - - /// <summary> - /// Gets or sets the configuration manager. - /// </summary> - /// <value>The configuration manager.</value> - private IServerConfigurationManager ConfigurationManager { get; set; } - - private readonly Func<ILibraryMonitor> _libraryMonitorFactory; - private readonly Func<IProviderManager> _providerManagerFactory; - private readonly Func<IUserViewManager> _userviewManager; public bool IsScanRunning { get; private set; } - private IServerApplicationHost _appHost; - private readonly IMediaEncoder _mediaEncoder; - - /// <summary> - /// The _library items cache - /// </summary> - private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache; - - /// <summary> - /// Gets the library items cache. - /// </summary> - /// <value>The library items cache.</value> - private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache; - - private readonly IFileSystem _fileSystem; - /// <summary> /// Initializes a new instance of the <see cref="LibraryManager" /> class. /// </summary> /// <param name="appHost">The application host</param> - /// <param name="loggerFactory">The logger factory.</param> + /// <param name="logger">The logger.</param> /// <param name="taskManager">The task manager.</param> /// <param name="userManager">The user manager.</param> /// <param name="configurationManager">The configuration manager.</param> /// <param name="userDataRepository">The user data repository.</param> public LibraryManager( IServerApplicationHost appHost, - ILoggerFactory loggerFactory, + ILogger<LibraryManager> logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, - Func<ILibraryMonitor> libraryMonitorFactory, + Lazy<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, - Func<IProviderManager> providerManagerFactory, - Func<IUserViewManager> userviewManager, - IMediaEncoder mediaEncoder) + Lazy<IProviderManager> providerManagerFactory, + Lazy<IUserViewManager> userviewManagerFactory, + IMediaEncoder mediaEncoder, + IItemRepository itemRepository) { _appHost = appHost; - _logger = loggerFactory.CreateLogger(nameof(LibraryManager)); + _logger = logger; _taskManager = taskManager; _userManager = userManager; - ConfigurationManager = configurationManager; + _configurationManager = configurationManager; _userDataRepository = userDataRepository; _libraryMonitorFactory = libraryMonitorFactory; _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; - _userviewManager = userviewManager; + _userviewManagerFactory = userviewManagerFactory; _mediaEncoder = mediaEncoder; + _itemRepository = itemRepository; _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); - ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; + _configurationManager.ConfigurationUpdated += ConfigurationUpdated; RecordConfigurationValues(configurationManager.Configuration); } @@ -272,7 +243,7 @@ namespace Emby.Server.Implementations.Library /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> private void ConfigurationUpdated(object sender, EventArgs e) { - var config = ConfigurationManager.Configuration; + var config = _configurationManager.Configuration; var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted; @@ -306,7 +277,7 @@ namespace Emby.Server.Implementations.Library } } - LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); + _libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); } public void DeleteItem(BaseItem item, DeleteOptions options) @@ -437,10 +408,10 @@ namespace Emby.Server.Implementations.Library item.SetParent(null); - ItemRepository.DeleteItem(item.Id, CancellationToken.None); + _itemRepository.DeleteItem(item.Id); foreach (var child in children) { - ItemRepository.DeleteItem(child.Id, CancellationToken.None); + _itemRepository.DeleteItem(child.Id); } _libraryItemsCache.TryRemove(item.Id, out BaseItem removed); @@ -509,15 +480,15 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(type)); } - if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal)) + if (key.StartsWith(_configurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal)) { // Try to normalize paths located underneath program-data in an attempt to make them more portable - key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length) + key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length) .TrimStart(new[] { '/', '\\' }) .Replace("/", "\\"); } - if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) + if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds) { key = key.ToLowerInvariant(); } @@ -550,7 +521,7 @@ namespace Emby.Server.Implementations.Library collectionType = GetContentTypeOverride(fullPath, true); } - var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) + var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) { Parent = parent, Path = fullPath, @@ -720,7 +691,7 @@ namespace Emby.Server.Implementations.Library /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception> public AggregateFolder CreateRootFolder() { - var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath; + var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath; Directory.CreateDirectory(rootFolderPath); @@ -734,7 +705,7 @@ namespace Emby.Server.Implementations.Library } // Add in the plug-in folders - var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists"); + var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists"); Directory.CreateDirectory(path); @@ -786,7 +757,7 @@ namespace Emby.Server.Implementations.Library { if (_userRootFolder == null) { - var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; _logger.LogDebug("Creating userRootPath at {path}", userRootPath); Directory.CreateDirectory(userRootPath); @@ -980,7 +951,7 @@ namespace Emby.Server.Implementations.Library where T : BaseItem, new() { var path = getPathFn(name); - var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds; + var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds; return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); } @@ -994,7 +965,7 @@ namespace Emby.Server.Implementations.Library public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { // Ensure the location is available. - Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); + Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath); return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress); } @@ -1031,7 +1002,7 @@ namespace Emby.Server.Implementations.Library public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) { IsScanRunning = true; - _libraryMonitorFactory().Stop(); + LibraryMonitor.Stop(); try { @@ -1039,7 +1010,7 @@ namespace Emby.Server.Implementations.Library } finally { - _libraryMonitorFactory().Start(); + LibraryMonitor.Start(); IsScanRunning = false; } } @@ -1148,7 +1119,7 @@ namespace Emby.Server.Implementations.Library progress.Report(percent * 100); } - ItemRepository.UpdateInheritedValues(cancellationToken); + _itemRepository.UpdateInheritedValues(cancellationToken); progress.Report(100); } @@ -1168,9 +1139,9 @@ namespace Emby.Server.Implementations.Library var topLibraryFolders = GetUserRootFolder().Children.ToList(); _logger.LogDebug("Getting refreshQueue"); - var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null; + var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null; - return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath) + return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath) .Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue)) .ToList(); } @@ -1245,7 +1216,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Guid can't be empty", nameof(id)); } - if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) + if (_libraryItemsCache.TryGetValue(id, out BaseItem item)) { return item; } @@ -1276,7 +1247,7 @@ namespace Emby.Server.Implementations.Library AddUserToQuery(query, query.User, allowExternalContent); } - return ItemRepository.GetItemList(query); + return _itemRepository.GetItemList(query); } public List<BaseItem> GetItemList(InternalItemsQuery query) @@ -1300,7 +1271,7 @@ namespace Emby.Server.Implementations.Library AddUserToQuery(query, query.User); } - return ItemRepository.GetCount(query); + return _itemRepository.GetCount(query); } public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) @@ -1315,7 +1286,7 @@ namespace Emby.Server.Implementations.Library } } - return ItemRepository.GetItemList(query); + return _itemRepository.GetItemList(query); } public QueryResult<BaseItem> QueryItems(InternalItemsQuery query) @@ -1327,12 +1298,12 @@ namespace Emby.Server.Implementations.Library if (query.EnableTotalRecordCount) { - return ItemRepository.GetItems(query); + return _itemRepository.GetItems(query); } return new QueryResult<BaseItem> { - Items = ItemRepository.GetItemList(query).ToArray() + Items = _itemRepository.GetItemList(query).ToArray() }; } @@ -1343,7 +1314,7 @@ namespace Emby.Server.Implementations.Library AddUserToQuery(query, query.User); } - return ItemRepository.GetItemIdsList(query); + return _itemRepository.GetItemIdsList(query); } public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query) @@ -1354,7 +1325,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetStudios(query); + return _itemRepository.GetStudios(query); } public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query) @@ -1365,7 +1336,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetGenres(query); + return _itemRepository.GetGenres(query); } public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query) @@ -1376,7 +1347,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetMusicGenres(query); + return _itemRepository.GetMusicGenres(query); } public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query) @@ -1387,7 +1358,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetAllArtists(query); + return _itemRepository.GetAllArtists(query); } public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query) @@ -1398,7 +1369,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetArtists(query); + return _itemRepository.GetArtists(query); } private void SetTopParentOrAncestorIds(InternalItemsQuery query) @@ -1439,7 +1410,7 @@ namespace Emby.Server.Implementations.Library } SetTopParentOrAncestorIds(query); - return ItemRepository.GetAlbumArtists(query); + return _itemRepository.GetAlbumArtists(query); } public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query) @@ -1460,10 +1431,10 @@ namespace Emby.Server.Implementations.Library if (query.EnableTotalRecordCount) { - return ItemRepository.GetItems(query); + return _itemRepository.GetItems(query); } - var list = ItemRepository.GetItemList(query); + var list = _itemRepository.GetItemList(query); return new QueryResult<BaseItem> { @@ -1509,7 +1480,7 @@ namespace Emby.Server.Implementations.Library string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) && query.ItemIds.Length == 0) { - var userViews = _userviewManager().GetUserViews(new UserViewQuery + var userViews = UserViewManager.GetUserViews(new UserViewQuery { UserId = user.Id, IncludeHidden = true, @@ -1809,7 +1780,7 @@ namespace Emby.Server.Implementations.Library // Don't iterate multiple times var itemsList = items.ToList(); - ItemRepository.SaveItems(itemsList, cancellationToken); + _itemRepository.SaveItems(itemsList, cancellationToken); foreach (var item in itemsList) { @@ -1846,7 +1817,7 @@ namespace Emby.Server.Implementations.Library public void UpdateImages(BaseItem item) { - ItemRepository.SaveImages(item); + _itemRepository.SaveImages(item); RegisterItem(item); } @@ -1863,7 +1834,7 @@ namespace Emby.Server.Implementations.Library { if (item.IsFileProtocol) { - _providerManagerFactory().SaveMetadata(item, updateReason); + ProviderManager.SaveMetadata(item, updateReason); } item.DateLastSaved = DateTime.UtcNow; @@ -1871,7 +1842,7 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } - ItemRepository.SaveItems(itemsList, cancellationToken); + _itemRepository.SaveItems(itemsList, cancellationToken); if (ItemUpdated != null) { @@ -1947,7 +1918,7 @@ namespace Emby.Server.Implementations.Library /// <returns>BaseItem.</returns> public BaseItem RetrieveItem(Guid id) { - return ItemRepository.RetrieveItem(id); + return _itemRepository.RetrieveItem(id); } public List<Folder> GetCollectionFolders(BaseItem item) @@ -2066,7 +2037,7 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes + var nameValuePair = _configurationManager.Configuration.ContentTypes .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); @@ -2115,7 +2086,7 @@ namespace Emby.Server.Implementations.Library string sortName) { var path = Path.Combine( - ConfigurationManager.ApplicationPaths.InternalMetadataPath, + _configurationManager.ApplicationPaths.InternalMetadataPath, "views", _fileSystem.GetValidFilename(viewType)); @@ -2147,7 +2118,7 @@ namespace Emby.Server.Implementations.Library if (refresh) { item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None); - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); + ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal); } return item; @@ -2165,7 +2136,7 @@ namespace Emby.Server.Implementations.Library var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); + var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; @@ -2202,7 +2173,7 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh( + ProviderManager.QueueRefresh( item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { @@ -2269,7 +2240,7 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh( + ProviderManager.QueueRefresh( item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { @@ -2303,7 +2274,7 @@ namespace Emby.Server.Implementations.Library var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); + var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; @@ -2346,7 +2317,7 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh( + ProviderManager.QueueRefresh( item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { @@ -2364,7 +2335,7 @@ namespace Emby.Server.Implementations.Library string videoPath, string[] files) { - new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); + new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files); } /// <inheritdoc /> @@ -2609,14 +2580,12 @@ namespace Emby.Server.Implementations.Library }).OrderBy(i => i.Path); } - private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" }; - public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var namingOptions = GetNamingOptions(); var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) - .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + .Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); @@ -2677,8 +2646,8 @@ namespace Emby.Server.Implementations.Library } } - var metadataPath = ConfigurationManager.Configuration.MetadataPath; - var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath; + var metadataPath = _configurationManager.Configuration.MetadataPath; + var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath; if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) { @@ -2689,7 +2658,7 @@ namespace Emby.Server.Implementations.Library } } - foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) + foreach (var map in _configurationManager.Configuration.PathSubstitutions) { if (!string.IsNullOrWhiteSpace(map.From)) { @@ -2758,7 +2727,7 @@ namespace Emby.Server.Implementations.Library public List<PersonInfo> GetPeople(InternalPeopleQuery query) { - return ItemRepository.GetPeople(query); + return _itemRepository.GetPeople(query); } public List<PersonInfo> GetPeople(BaseItem item) @@ -2781,7 +2750,7 @@ namespace Emby.Server.Implementations.Library public List<Person> GetPeopleItems(InternalPeopleQuery query) { - return ItemRepository.GetPeopleNames(query).Select(i => + return _itemRepository.GetPeopleNames(query).Select(i => { try { @@ -2798,7 +2767,7 @@ namespace Emby.Server.Implementations.Library public List<string> GetPeopleNames(InternalPeopleQuery query) { - return ItemRepository.GetPeopleNames(query); + return _itemRepository.GetPeopleNames(query); } public void UpdatePeople(BaseItem item, List<PersonInfo> people) @@ -2808,7 +2777,7 @@ namespace Emby.Server.Implementations.Library return; } - ItemRepository.UpdatePeople(item.Id, people); + _itemRepository.UpdatePeople(item.Id, people); } public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) @@ -2819,7 +2788,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url); - await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); + await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); @@ -2852,7 +2821,7 @@ namespace Emby.Server.Implementations.Library name = _fileSystem.GetValidFilename(name); - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, name); while (Directory.Exists(virtualFolderPath)) @@ -2871,7 +2840,7 @@ namespace Emby.Server.Implementations.Library } } - _libraryMonitorFactory().Stop(); + LibraryMonitor.Stop(); try { @@ -2906,7 +2875,7 @@ namespace Emby.Server.Implementations.Library { // Need to add a delay here or directory watchers may still pick up the changes await Task.Delay(1000).ConfigureAwait(false); - _libraryMonitorFactory().Start(); + LibraryMonitor.Start(); } } } @@ -2966,7 +2935,7 @@ namespace Emby.Server.Implementations.Library throw new FileNotFoundException("The network path does not exist."); } - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var shortcutFilename = Path.GetFileNameWithoutExtension(path); @@ -3009,7 +2978,7 @@ namespace Emby.Server.Implementations.Library throw new FileNotFoundException("The network path does not exist."); } - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); @@ -3062,7 +3031,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(name)); } - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var path = Path.Combine(rootFolderPath, name); @@ -3071,7 +3040,7 @@ namespace Emby.Server.Implementations.Library throw new FileNotFoundException("The media folder does not exist"); } - _libraryMonitorFactory().Stop(); + LibraryMonitor.Stop(); try { @@ -3091,7 +3060,7 @@ namespace Emby.Server.Implementations.Library { // Need to add a delay here or directory watchers may still pick up the changes await Task.Delay(1000).ConfigureAwait(false); - _libraryMonitorFactory().Start(); + LibraryMonitor.Start(); } } } @@ -3105,7 +3074,7 @@ namespace Emby.Server.Implementations.Library var removeList = new List<NameValuePair>(); - foreach (var contentType in ConfigurationManager.Configuration.ContentTypes) + foreach (var contentType in _configurationManager.Configuration.ContentTypes) { if (string.IsNullOrWhiteSpace(contentType.Name)) { @@ -3120,11 +3089,11 @@ namespace Emby.Server.Implementations.Library if (removeList.Count > 0) { - ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes + _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes .Except(removeList) - .ToArray(); + .ToArray(); - ConfigurationManager.SaveConfiguration(); + _configurationManager.SaveConfiguration(); } } @@ -3135,7 +3104,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(mediaPath)); } - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); if (!Directory.Exists(virtualFolderPath)) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 70d5bd9f4..01fe98f3a 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -33,13 +33,13 @@ namespace Emby.Server.Implementations.Library private readonly ILibraryManager _libraryManager; private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; - - private IMediaSourceProvider[] _providers; private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; - private readonly Func<IMediaEncoder> _mediaEncoder; - private ILocalizationManager _localizationManager; - private IApplicationPaths _appPaths; + private readonly IMediaEncoder _mediaEncoder; + private readonly ILocalizationManager _localizationManager; + private readonly IApplicationPaths _appPaths; + + private IMediaSourceProvider[] _providers; public MediaSourceManager( IItemRepository itemRepo, @@ -47,16 +47,16 @@ namespace Emby.Server.Implementations.Library ILocalizationManager localizationManager, IUserManager userManager, ILibraryManager libraryManager, - ILoggerFactory loggerFactory, + ILogger<MediaSourceManager> logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, - Func<IMediaEncoder> mediaEncoder) + IMediaEncoder mediaEncoder) { _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(MediaSourceManager)); + _logger = logger; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; _userDataManager = userDataManager; @@ -496,7 +496,7 @@ namespace Emby.Server.Implementations.Library // hack - these two values were taken from LiveTVMediaSourceProvider string cacheKey = request.OpenToken; - await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths) + await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths) .AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken) .ConfigureAwait(false); } @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations.Library if (liveStreamInfo is IDirectStreamProvider) { - var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest { MediaSource = mediaSource, ExtractChapters = false, @@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.Library mediaSource.AnalyzeDurationMs = 3000; } - mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest { MediaSource = mediaSource, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 4fdf73b77..1d61ed57e 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library // for imdbid we also accept pattern matching if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase)) { - var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase); + var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase); return m.Success ? m.Value : null; } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 11d6c737a..59a77607d 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -17,16 +17,15 @@ namespace Emby.Server.Implementations.Library { public class SearchEngine : ISearchEngine { + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; - private readonly ILogger _logger; - public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager) + public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager) { + _logger = logger; _libraryManager = libraryManager; _userManager = userManager; - - _logger = loggerFactory.CreateLogger("SearchEngine"); } public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 071681b08..a9772a078 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -28,25 +28,24 @@ namespace Emby.Server.Implementations.Library private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - - private Func<IUserManager> _userManager; - - public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager) + private readonly IUserManager _userManager; + private readonly IUserDataRepository _repository; + + public UserDataManager( + ILogger<UserDataManager> logger, + IServerConfigurationManager config, + IUserManager userManager, + IUserDataRepository repository) { + _logger = logger; _config = config; - _logger = loggerFactory.CreateLogger(GetType().Name); _userManager = userManager; + _repository = repository; } - /// <summary> - /// Gets or sets the repository. - /// </summary> - /// <value>The repository.</value> - public IUserDataRepository Repository { get; set; } - public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken) { - var user = _userManager().GetUserById(userId); + var user = _userManager.GetUserById(userId); SaveUserData(user, item, userData, reason, cancellationToken); } @@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.Library foreach (var key in keys) { - Repository.SaveUserData(userId, key, userData, cancellationToken); + _repository.SaveUserData(userId, key, userData, cancellationToken); } var cacheKey = GetCacheKey(userId, item.Id); @@ -96,9 +95,9 @@ namespace Emby.Server.Implementations.Library /// <returns></returns> public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken) { - var user = _userManager().GetUserById(userId); + var user = _userManager.GetUserById(userId); - Repository.SaveAllUserData(user.InternalId, userData, cancellationToken); + _repository.SaveAllUserData(user.InternalId, userData, cancellationToken); } /// <summary> @@ -108,14 +107,14 @@ namespace Emby.Server.Implementations.Library /// <returns></returns> public List<UserItemData> GetAllUserData(Guid userId) { - var user = _userManager().GetUserById(userId); + var user = _userManager.GetUserById(userId); - return Repository.GetAllUserData(user.InternalId); + return _repository.GetAllUserData(user.InternalId); } public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys) { - var user = _userManager().GetUserById(userId); + var user = _userManager.GetUserById(userId); return GetUserData(user, itemId, keys); } @@ -131,7 +130,7 @@ namespace Emby.Server.Implementations.Library private UserItemData GetUserDataInternal(long internalUserId, List<string> keys) { - var userData = Repository.GetUserData(internalUserId, keys); + var userData = _repository.GetUserData(internalUserId, keys); if (userData != null) { diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7b17cc913..d63bc6bda 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; @@ -44,22 +45,14 @@ namespace Emby.Server.Implementations.Library { private readonly object _policySyncLock = new object(); private readonly object _configSyncLock = new object(); - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger _logger; - /// <summary> - /// Gets the active user repository. - /// </summary> - /// <value>The user repository.</value> + private readonly ILogger _logger; private readonly IUserRepository _userRepository; private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; private readonly INetworkManager _networkManager; - - private readonly Func<IImageProcessor> _imageProcessorFactory; - private readonly Func<IDtoService> _dtoServiceFactory; + private readonly IImageProcessor _imageProcessor; + private readonly Lazy<IDtoService> _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; private readonly ICryptoProvider _cryptoProvider; @@ -74,13 +67,15 @@ namespace Emby.Server.Implementations.Library private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; + private IDtoService DtoService => _dtoServiceFactory.Value; + public UserManager( ILogger<UserManager> logger, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, - Func<IImageProcessor> imageProcessorFactory, - Func<IDtoService> dtoServiceFactory, + IImageProcessor imageProcessor, + Lazy<IDtoService> dtoServiceFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, IFileSystem fileSystem, @@ -90,7 +85,7 @@ namespace Emby.Server.Implementations.Library _userRepository = userRepository; _xmlSerializer = xmlSerializer; _networkManager = networkManager; - _imageProcessorFactory = imageProcessorFactory; + _imageProcessor = imageProcessor; _dtoServiceFactory = dtoServiceFactory; _appHost = appHost; _jsonSerializer = jsonSerializer; @@ -264,6 +259,7 @@ namespace Emby.Server.Implementations.Library { if (string.IsNullOrWhiteSpace(username)) { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); throw new ArgumentNullException(nameof(username)); } @@ -319,26 +315,26 @@ namespace Emby.Server.Implementations.Library if (user == null) { + _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint); throw new AuthenticationException("Invalid username or password entered."); } if (user.Policy.IsDisabled) { - throw new AuthenticationException( - string.Format( - CultureInfo.InvariantCulture, - "The {0} account is currently disabled. Please consult with your administrator.", - user.Name)); + _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint); + throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator."); } if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) { - throw new AuthenticationException("Forbidden."); + _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint); + throw new SecurityException("Forbidden."); } if (!user.IsParentalScheduleAllowed()) { - throw new AuthenticationException("User is not allowed access at this time."); + _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); } // Update LastActivityDate and LastLoginDate, then save @@ -351,14 +347,14 @@ namespace Emby.Server.Implementations.Library } ResetInvalidLoginAttemptCount(user); + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name); } else { IncrementInvalidLoginAttemptCount(user); + _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint); } - _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); - return success ? user : null; } @@ -600,7 +596,7 @@ namespace Emby.Server.Implementations.Library try { - _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + DtoService.AttachPrimaryImageAspectRatio(dto, user); } catch (Exception ex) { @@ -625,7 +621,7 @@ namespace Emby.Server.Implementations.Library { try { - return _imageProcessorFactory().GetImageCacheTag(item, image); + return _imageProcessor.GetImageCacheTag(item, image); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 139aa19a4..900f12062 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; @@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly ILibraryManager _libraryManager; private readonly IProviderManager _providerManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IProcessFactory _processFactory; private readonly IMediaSourceManager _mediaSourceManager; private readonly IStreamHelper _streamHelper; @@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, - IMediaEncoder mediaEncoder, - IProcessFactory processFactory) + IMediaEncoder mediaEncoder) { Current = this; @@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _libraryMonitor = libraryMonitor; _providerManager = providerManager; _mediaEncoder = mediaEncoder; - _processFactory = processFactory; _liveTvManager = (LiveTvManager)liveTvManager; _jsonSerializer = jsonSerializer; _mediaSourceManager = mediaSourceManager; @@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http)) { - return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config); + return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config); } return new DirectRecorder(_logger, _httpClient, _streamHelper); @@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV try { - var process = _processFactory.Create(new ProcessOptions + var process = new Process { - Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments), - CreateNoWindow = true, - EnableRaisingEvents = true, - ErrorDialog = false, - FileName = options.RecordingPostProcessor, - IsHidden = true, - UseShellExecute = false - }); + StartInfo = new ProcessStartInfo + { + Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments), + CreateNoWindow = true, + ErrorDialog = false, + FileName = options.RecordingPostProcessor, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false + }, + EnableRaisingEvents = true + }; _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); @@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void Process_Exited(object sender, EventArgs e) { - using (var process = (IProcess)sender) + using (var process = (Process)sender) { _logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode); - - process.Dispose(); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index d24fc6792..bc86cc59a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; @@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; @@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private bool _hasExited; private Stream _logFileStream; private string _targetPath; - private IProcess _process; - private readonly IProcessFactory _processFactory; + private Process _process; private readonly IJsonSerializer _json; private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>(); private readonly IServerConfigurationManager _config; @@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, - IProcessFactory processFactory, IServerConfigurationManager config) { _logger = logger; _mediaEncoder = mediaEncoder; _appPaths = appPaths; _json = json; - _processFactory = processFactory; _config = config; } @@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _targetPath = targetFile; Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); - var process = _processFactory.Create(new ProcessOptions + var processStartInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, @@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV FileName = _mediaEncoder.EncoderPath, Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration), - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - _process = process; + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }; - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; + var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments; _logger.LogInformation(commandLineLogMessage); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); @@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); - process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile); + _process = new Process + { + StartInfo = processStartInfo, + EnableRaisingEvents = true + }; + _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile); - process.Start(); + _process.Start(); cancellationToken.Register(Stop); onStarted(); // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - StartStreamingLog(process.StandardError.BaseStream, _logFileStream); + StartStreamingLog(_process.StandardError.BaseStream, _logFileStream); _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath); @@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV /// <summary> /// Processes the exited. /// </summary> - private void OnFfMpegProcessExited(IProcess process, string inputFile) + private void OnFfMpegProcessExited(Process process, string inputFile) { - _hasExited = true; + using (process) + { + _hasExited = true; - _logFileStream?.Dispose(); - _logFileStream = null; + _logFileStream?.Dispose(); + _logFileStream = null; - var exitCode = process.ExitCode; + var exitCode = process.ExitCode; - _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath); + _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath); - if (exitCode == 0) - { - _taskCompletionSource.TrySetResult(true); - } - else - { - _taskCompletionSource.TrySetException( - new Exception( - string.Format( - CultureInfo.InvariantCulture, - "Recording for {0} failed. Exit code {1}", - _targetPath, - exitCode))); + if (exitCode == 0) + { + _taskCompletionSource.TrySetResult(true); + } + else + { + _taskCompletionSource.TrySetException( + new Exception( + string.Format( + CultureInfo.InvariantCulture, + "Recording for {0} failed. Exit code {1}", + _targetPath, + exitCode))); + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6e903a18e..a59c1090e 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -22,9 +22,12 @@ namespace Emby.Server.Implementations.LiveTv { public class LiveTvDtoService { + private const string InternalVersionNumber = "4"; + + private const string ServiceName = "Emby"; + private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; - private readonly IDtoService _dtoService; private readonly IApplicationHost _appHost; private readonly ILibraryManager _libraryManager; @@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv public LiveTvDtoService( IDtoService dtoService, IImageProcessor imageProcessor, - ILoggerFactory loggerFactory, + ILogger<LiveTvDtoService> logger, IApplicationHost appHost, ILibraryManager libraryManager) { _dtoService = dtoService; _imageProcessor = imageProcessor; - _logger = loggerFactory.CreateLogger(nameof(LiveTvDtoService)); + _logger = logger; _appHost = appHost; _libraryManager = libraryManager; } @@ -161,7 +164,6 @@ namespace Emby.Server.Implementations.LiveTv Limit = 1, ImageTypes = new ImageType[] { ImageType.Thumb }, DtoOptions = new DtoOptions(false) - }).FirstOrDefault(); if (librarySeries != null) @@ -179,6 +181,7 @@ namespace Emby.Server.Implementations.LiveTv _logger.LogError(ex, "Error"); } } + image = librarySeries.GetImageInfo(ImageType.Backdrop, 0); if (image != null) { @@ -199,13 +202,12 @@ namespace Emby.Server.Implementations.LiveTv var program = _libraryManager.GetItemList(new InternalItemsQuery { - IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, + IncludeItemTypes = new string[] { nameof(LiveTvProgram) }, ExternalSeriesId = programSeriesId, Limit = 1, ImageTypes = new ImageType[] { ImageType.Primary }, DtoOptions = new DtoOptions(false), Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null - }).FirstOrDefault(); if (program != null) @@ -232,9 +234,10 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentBackdropImageTags = new string[] - { + { _imageProcessor.GetImageCacheTag(program, image) - }; + }; + dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) @@ -255,7 +258,6 @@ namespace Emby.Server.Implementations.LiveTv Limit = 1, ImageTypes = new ImageType[] { ImageType.Thumb }, DtoOptions = new DtoOptions(false) - }).FirstOrDefault(); if (librarySeries != null) @@ -273,6 +275,7 @@ namespace Emby.Server.Implementations.LiveTv _logger.LogError(ex, "Error"); } } + image = librarySeries.GetImageInfo(ImageType.Backdrop, 0); if (image != null) { @@ -298,7 +301,6 @@ namespace Emby.Server.Implementations.LiveTv Limit = 1, ImageTypes = new ImageType[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) - }).FirstOrDefault(); if (program == null) @@ -311,7 +313,6 @@ namespace Emby.Server.Implementations.LiveTv ImageTypes = new ImageType[] { ImageType.Primary }, DtoOptions = new DtoOptions(false), Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null - }).FirstOrDefault(); } @@ -396,8 +397,6 @@ namespace Emby.Server.Implementations.LiveTv return null; } - private const string InternalVersionNumber = "4"; - public Guid GetInternalChannelId(string serviceName, string externalId) { var name = serviceName + externalId + InternalVersionNumber; @@ -405,7 +404,6 @@ namespace Emby.Server.Implementations.LiveTv return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel)); } - private const string ServiceName = "Emby"; public string GetInternalTimerId(string externalId) { var name = ServiceName + externalId + InternalVersionNumber; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index b64fe8634..1b10f2d27 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -41,33 +41,32 @@ namespace Emby.Server.Implementations.LiveTv /// </summary> public class LiveTvManager : ILiveTvManager, IDisposable { + private const string ExternalServiceTag = "ExternalServiceId"; + + private const string EtagKey = "ProgramEtag"; + private readonly IServerConfigurationManager _config; private readonly ILogger _logger; private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - private readonly IJsonSerializer _jsonSerializer; - private readonly Func<IChannelManager> _channelManager; - - private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; - + private readonly IJsonSerializer _jsonSerializer; + private readonly IFileSystem _fileSystem; + private readonly IChannelManager _channelManager; private readonly LiveTvDtoService _tvDtoService; private ILiveTvService[] _services = Array.Empty<ILiveTvService>(); - private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>(); private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>(); - private readonly IFileSystem _fileSystem; public LiveTvManager( - IServerApplicationHost appHost, IServerConfigurationManager config, - ILoggerFactory loggerFactory, + ILogger<LiveTvManager> logger, IItemRepository itemRepo, - IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, @@ -76,10 +75,11 @@ namespace Emby.Server.Implementations.LiveTv ILocalizationManager localization, IJsonSerializer jsonSerializer, IFileSystem fileSystem, - Func<IChannelManager> channelManager) + IChannelManager channelManager, + LiveTvDtoService liveTvDtoService) { _config = config; - _logger = loggerFactory.CreateLogger(nameof(LiveTvManager)); + _logger = logger; _itemRepo = itemRepo; _userManager = userManager; _libraryManager = libraryManager; @@ -90,8 +90,7 @@ namespace Emby.Server.Implementations.LiveTv _dtoService = dtoService; _userDataManager = userDataManager; _channelManager = channelManager; - - _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory, appHost, _libraryManager); + _tvDtoService = liveTvDtoService; } public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled; @@ -178,7 +177,6 @@ namespace Emby.Server.Implementations.LiveTv { Name = i.Name, Id = i.Type - }).ToList(); } @@ -261,6 +259,7 @@ namespace Emby.Server.Implementations.LiveTv var endTime = DateTime.UtcNow; _logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds); } + info.RequiresClosing = true; var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_"; @@ -362,30 +361,37 @@ namespace Emby.Server.Implementations.LiveTv { stream.BitRate = null; } + if (stream.Channels.HasValue && stream.Channels <= 0) { stream.Channels = null; } + if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0) { stream.AverageFrameRate = null; } + if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0) { stream.RealFrameRate = null; } + if (stream.Width.HasValue && stream.Width <= 0) { stream.Width = null; } + if (stream.Height.HasValue && stream.Height <= 0) { stream.Height = null; } + if (stream.SampleRate.HasValue && stream.SampleRate <= 0) { stream.SampleRate = null; } + if (stream.Level.HasValue && stream.Level <= 0) { stream.Level = null; @@ -427,7 +433,6 @@ namespace Emby.Server.Implementations.LiveTv } } - private const string ExternalServiceTag = "ExternalServiceId"; private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken) { var parentFolderId = parentFolder.Id; @@ -456,6 +461,7 @@ namespace Emby.Server.Implementations.LiveTv { isNew = true; } + item.Tags = channelInfo.Tags; } @@ -463,6 +469,7 @@ namespace Emby.Server.Implementations.LiveTv { isNew = true; } + item.ParentId = parentFolderId; item.ChannelType = channelInfo.ChannelType; @@ -472,24 +479,28 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.SetProviderId(ExternalServiceTag, serviceName); if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal)) { forceUpdate = true; } + item.ExternalId = channelInfo.Id; if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal)) { forceUpdate = true; } + item.Number = channelInfo.Number; if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal)) { forceUpdate = true; } + item.Name = channelInfo.Name; if (!item.HasImage(ImageType.Primary)) @@ -518,8 +529,6 @@ namespace Emby.Server.Implementations.LiveTv return item; } - private const string EtagKey = "ProgramEtag"; - private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken) { var id = _tvDtoService.GetInternalProgramId(info.Id); @@ -2482,7 +2491,7 @@ namespace Emby.Server.Implementations.LiveTv .OrderBy(i => i.SortName) .ToList(); - folders.AddRange(_channelManager().GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery + folders.AddRange(_channelManager.GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery { UserId = user.Id, IsRecordingsFolder = true, diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 2fe232e79..f313039a6 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -102,5 +102,17 @@ "TaskRefreshLibrary": "افحص مكتبة الوسائط", "TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.", "TaskRefreshChapterImages": "استخراج صور الفصل", - "TasksApplicationCategory": "تطبيق" + "TasksApplicationCategory": "تطبيق", + "TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.", + "TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة", + "TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.", + "TaskRefreshChannels": "إعادة تحديث القنوات", + "TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.", + "TaskCleanTranscode": "حذف سجلات الترميز", + "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.", + "TaskUpdatePlugins": "تحديث الإضافات", + "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.", + "TaskRefreshPeople": "إعادة تحميل الأشخاص", + "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.", + "TaskCleanLogs": "حذف دليل السجل" } diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 2d8299367..7464ac1c0 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -3,19 +3,19 @@ "AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}", "Application": "Aplicació", "Artists": "Artistes", - "AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament", + "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "Books": "Llibres", - "CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}", + "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}", "Channels": "Canals", - "ChapterNameValue": "Episodi {0}", + "ChapterNameValue": "Capítol {0}", "Collections": "Col·leccions", "DeviceOfflineWithName": "{0} s'ha desconnectat", "DeviceOnlineWithName": "{0} està connectat", "FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}", "Favorites": "Preferits", - "Folders": "Directoris", + "Folders": "Carpetes", "Genres": "Gèneres", - "HeaderAlbumArtists": "Artistes dels Àlbums", + "HeaderAlbumArtists": "Artistes del Àlbum", "HeaderCameraUploads": "Pujades de Càmera", "HeaderContinueWatching": "Continua Veient", "HeaderFavoriteAlbums": "Àlbums Preferits", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 94437d237..f5397b62c 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -1,5 +1,5 @@ { - "Albums": "Album", + "Albums": "Albums", "AppDeviceValues": "App: {0}, Enhed: {1}", "Application": "Applikation", "Artists": "Kunstnere", @@ -35,8 +35,8 @@ "Latest": "Seneste", "MessageApplicationUpdated": "Jellyfin Server er blevet opdateret", "MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret", - "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret", + "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret", + "MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret", "MixedContent": "Blandet indhold", "Movies": "Film", "Music": "Musik", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}", "ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek", "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.", + "TaskDownloadMissingSubtitles": "Download manglende undertekster", + "TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.", + "TaskUpdatePlugins": "Opdater Plugins", + "TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.", + "TaskCleanLogs": "Ryd Log Mappe", + "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.", + "TaskRefreshLibrary": "Scan Medie Bibliotek", + "TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.", + "TaskCleanCache": "Ryd Cache Mappe", + "TasksChannelsCategory": "Internet Kanaler", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Vedligeholdelse", + "TaskRefreshChapterImages": "Udtræk Kapitel billeder", + "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.", + "TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.", + "TaskRefreshChannels": "Genopfrisk Kanaler", + "TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.", + "TaskCleanTranscode": "Rengør Transcode Mappen", + "TaskRefreshPeople": "Genopfrisk Personer", + "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek." } diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 414430ff7..82df43be1 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -3,7 +3,7 @@ "AppDeviceValues": "App: {0}, Gerät: {1}", "Application": "Anwendung", "Artists": "Interpreten", - "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifziert", + "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert", "Books": "Bücher", "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen", "Channels": "Kanäle", @@ -99,11 +99,11 @@ "TaskRefreshChannels": "Erneuere Kanäle", "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.", "TaskCleanTranscode": "Lösche Transkodier Pfad", - "TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", + "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", "TaskUpdatePlugins": "Update Plugins", "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.", "TaskRefreshPeople": "Erneuere Schausteller", - "TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.", + "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.", "TaskCleanLogs": "Lösche Log Pfad", "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", "TaskRefreshLibrary": "Scanne alle Bibliotheken", diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index dc4f0b212..544c38cfa 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", + "TaskDownloadMissingSubtitles": "Download missing subtitles", + "TaskRefreshChannelsDescription": "Refreshes internet channel information.", + "TaskRefreshChannels": "Refresh Channels", + "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.", + "TaskCleanTranscode": "Clean Transcode Directory", + "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.", + "TaskUpdatePlugins": "Update Plugins", + "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.", + "TaskRefreshPeople": "Refresh People", + "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.", + "TaskCleanLogs": "Clean Log Directory", + "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", + "TaskRefreshLibrary": "Scan Media Library", + "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", + "TaskRefreshChapterImages": "Extract Chapter Images", + "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", + "TaskCleanCache": "Clean Cache Directory", + "TasksChannelsCategory": "Internet Channels", + "TasksApplicationCategory": "Application", + "TasksLibraryCategory": "Library", + "TasksMaintenanceCategory": "Maintenance" } diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index 1211eef54..1b6c6b5ae 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -17,7 +17,7 @@ "Genres": "Géneros", "HeaderAlbumArtists": "Artistas de álbum", "HeaderCameraUploads": "Subidas de cámara", - "HeaderContinueWatching": "Continuar viendo", + "HeaderContinueWatching": "Seguir viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json index 1a7b57c53..0ef16542f 100644 --- a/Emby.Server.Implementations/Localization/Core/es_DO.json +++ b/Emby.Server.Implementations/Localization/Core/es_DO.json @@ -5,7 +5,7 @@ "Collections": "Colecciones", "Artists": "Artistas", "DeviceOnlineWithName": "{0} está conectado", - "DeviceOfflineWithName": "{0} ha desconectado", + "DeviceOfflineWithName": "{0} se ha desconectado", "ChapterNameValue": "Capítulo {0}", "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 45e74b8eb..500c29217 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "قسمتهای مورد علاقه", "HeaderFavoriteShows": "سریالهای مورد علاقه", "HeaderFavoriteSongs": "آهنگهای مورد علاقه", - "HeaderLiveTV": "پخش زنده تلویزیون", + "HeaderLiveTV": "پخش زنده", "HeaderNextUp": "قسمت بعدی", "HeaderRecordingGroups": "گروههای ضبط", "HomeVideos": "ویدیوهای خانگی", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند", "ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد", "ValueSpecialEpisodeName": "ویژه - {0}", - "VersionNumber": "نسخه {0}" + "VersionNumber": "نسخه {0}", + "TaskCleanTranscodeDescription": "فایلهای کدگذاری که قدیمیتر از یک روز هستند را حذف میکند.", + "TaskCleanTranscode": "پاکسازی مسیر کد گذاری", + "TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونههایی که برای به روز رسانی خودکار پیکربندی شدهاند.", + "TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویسهای ناموجود در اینترنت بر اساس پیکربندی ابردادهها.", + "TaskDownloadMissingSubtitles": "دانلود زیرنویسهای ناموجود", + "TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی میکند.", + "TaskRefreshChannels": "تازه سازی کانالها", + "TaskUpdatePlugins": "به روز رسانی افزونهها", + "TaskRefreshPeopleDescription": "ابردادهها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.", + "TaskRefreshPeople": "تازه سازی افراد", + "TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.", + "TaskCleanLogs": "پاکسازی مسیر واقعه نگار", + "TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن میکند و ابردادهها را تازه سازی میکند.", + "TaskRefreshLibrary": "اسکن کتابخانه رسانه", + "TaskRefreshChapterImagesDescription": "عکسهای کوچک برای ویدیوهایی که سکانس دارند ایجاد میکند.", + "TaskRefreshChapterImages": "استخراج عکسهای سکانس", + "TaskCleanCacheDescription": "فایلهای حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف میشوند.", + "TaskCleanCache": "پاکسازی مسیر حافظه موقت", + "TasksChannelsCategory": "کانالهای داخلی", + "TasksApplicationCategory": "برنامه", + "TasksLibraryCategory": "کتابخانه", + "TasksMaintenanceCategory": "تعمیر" } diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index bf5fc05c4..b39adefe7 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "TV-lähetykset", + "HeaderLiveTV": "Suorat lähetykset", "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon Kausi", "NameSeasonNumber": "Kausi {0}", @@ -19,12 +19,12 @@ "ItemAddedWithName": "{0} lisättiin kirjastoon", "Inherit": "Periytyä", "HomeVideos": "Kotivideot", - "HeaderRecordingGroups": "Nauhoitusryhmät", + "HeaderRecordingGroups": "Nauhoiteryhmät", "HeaderNextUp": "Seuraavaksi", "HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteEpisodes": "Lempijaksot", - "HeaderCameraUploads": "Kameralataukset", + "HeaderCameraUploads": "Kamerasta Lähetetyt", "HeaderFavoriteArtists": "Lempiartistit", "HeaderFavoriteAlbums": "Lempialbumit", "HeaderContinueWatching": "Jatka katsomista", @@ -63,10 +63,10 @@ "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}", "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}", "UserOfflineFromDevice": "{0} yhteys katkaistu {1}", - "UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos", - "UserDownloadingItemWithValues": "{0} latautumassa {1}", - "UserDeletedWithName": "Poistettiin käyttäjä {0}", - "UserCreatedWithName": "Luotiin käyttäjä {0}", + "UserLockedOutWithName": "Käyttäjä {0} lukittu", + "UserDownloadingItemWithValues": "{0} lataa {1}", + "UserDeletedWithName": "Käyttäjä {0} poistettu", + "UserCreatedWithName": "Käyttäjä {0} luotu", "TvShows": "TV-Ohjelmat", "Sync": "Synkronoi", "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}", @@ -74,22 +74,44 @@ "Songs": "Kappaleet", "Shows": "Ohjelmat", "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen", - "ProviderValue": "Palveluntarjoaja: {0}", + "ProviderValue": "Tarjoaja: {0}", "Plugin": "Liitännäinen", - "NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty", - "NotificationOptionVideoPlayback": "Videon toistaminen aloitettu", - "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos", - "NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma", + "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty", + "NotificationOptionVideoPlayback": "Videon toisto aloitettu", + "NotificationOptionUserLockedOut": "Käyttäjä lukittu", + "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui", "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan", - "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty", + "NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu", "NotificationOptionPluginUninstalled": "Liitännäinen poistettu", "NotificationOptionPluginInstalled": "Liitännäinen asennettu", "NotificationOptionPluginError": "Ongelma liitännäisessä", "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty", "NotificationOptionInstallationFailed": "Asennus epäonnistui", - "NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta", - "NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty", - "NotificationOptionAudioPlayback": "Audion toisto aloitettu", - "NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu", - "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla" + "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu", + "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu", + "NotificationOptionAudioPlayback": "Toistetaan ääntä", + "NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu", + "NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla", + "TasksMaintenanceCategory": "Ylläpito", + "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.", + "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset", + "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.", + "TaskRefreshChannels": "Päivitä kanavat", + "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.", + "TaskCleanTranscode": "Puhdista transkoodaushakemisto", + "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.", + "TaskUpdatePlugins": "Päivitä liitännäiset", + "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.", + "TaskRefreshPeople": "Päivitä henkilöt", + "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.", + "TaskCleanLogs": "Puhdista lokihakemisto", + "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.", + "TaskRefreshLibrary": "Skannaa mediakirjasto", + "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.", + "TaskRefreshChapterImages": "Eristä lukujen kuvat", + "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.", + "TaskCleanCache": "Tyhjennä välimuisti-hakemisto", + "TasksChannelsCategory": "Internet kanavat", + "TasksApplicationCategory": "Sovellus", + "TasksLibraryCategory": "Kirjasto" } diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 86a6d1836..47daf2044 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -90,5 +90,13 @@ "Artists": "Artista", "Application": "Aplikasyon", "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}", - "Albums": "Albums" + "Albums": "Albums", + "TaskRefreshLibrary": "Suriin ang nasa librerya", + "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata", + "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata", + "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.", + "TasksChannelsCategory": "Palabas sa internet", + "TasksLibraryCategory": "Librerya", + "TasksMaintenanceCategory": "Pagpapanatili", + "HomeVideos": "Sariling pelikula" } diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 88a7ac190..150952d8b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,17 +5,17 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", "DeviceOfflineWithName": "{0} s'est déconnecté", "DeviceOnlineWithName": "{0} est connecté", - "FailedLoginAttemptWithUserName": "Échec de connexion de {0}", + "FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}", "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes d'album", + "HeaderAlbumArtists": "Artistes de l'album", "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", @@ -69,7 +69,7 @@ "PluginUpdatedWithName": "{0} a été mis à jour", "ProviderValue": "Fournisseur : {0}", "ScheduledTaskFailedWithName": "{0} a échoué", - "ScheduledTaskStartedWithName": "{0} a commencé", + "ScheduledTaskStartedWithName": "{0} a démarré", "ServerNameNeedsToBeRestarted": "{0} doit être redémarré", "Shows": "Émissions", "Songs": "Chansons", @@ -95,21 +95,21 @@ "VersionNumber": "Version {0}", "TasksChannelsCategory": "Chaines en ligne", "TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.", - "TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant", + "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant", "TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.", - "TaskRefreshChannels": "Rafraîchit les chaines", + "TaskRefreshChannels": "Rafraîchir les chaines", "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", - "TaskCleanTranscode": "Nettoie les dossier des transcodages", - "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.", - "TaskUpdatePlugins": "Mettre à jour les plugins", - "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.", - "TaskRefreshPeople": "Rafraîchit les acteurs", + "TaskCleanTranscode": "Nettoyer les dossier des transcodages", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.", + "TaskUpdatePlugins": "Mettre à jour les extensions", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.", + "TaskRefreshPeople": "Rafraîchir les acteurs", "TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.", - "TaskCleanLogs": "Nettoie le répertoire des journaux", + "TaskCleanLogs": "Nettoyer le répertoire des journaux", "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.", - "TaskRefreshLibrary": "Scanne toute les Bibliothèques", + "TaskRefreshLibrary": "Scanner toute les Bibliothèques", "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.", - "TaskRefreshChapterImages": "Extrait les images de chapitre", + "TaskRefreshChapterImages": "Extraire les images de chapitre", "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.", "TaskCleanCache": "Vider le répertoire cache", "TasksApplicationCategory": "Application", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 6f226fe99..c5c3844e3 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -71,7 +71,7 @@ "ScheduledTaskFailedWithName": "{0} sikertelen", "ScheduledTaskStartedWithName": "{0} elkezdve", "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani", - "Shows": "Műsorok", + "Shows": "Sorozatok", "Songs": "Dalok", "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index d0daed7a3..a4d9f9ef6 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -104,10 +104,14 @@ "TasksMaintenanceCategory": "メンテナンス", "TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。", "TaskRefreshChannels": "チャンネルのリフレッシュ", - "TaskCleanTranscodeDescription": "一日以上前のトランスコードを消去します。", - "TaskCleanTranscode": "トランスコード用のディレクトリの掃除", + "TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。", + "TaskCleanTranscode": "トランスコードディレクトリの削除", "TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。", "TaskUpdatePlugins": "プラグインの更新", - "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。", - "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ" + "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。", + "TaskRefreshPeople": "俳優や監督のデータの更新", + "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。", + "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。", + "TaskRefreshChapterImages": "チャプター画像を抽出する", + "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする" } diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json new file mode 100644 index 000000000..50b6360d8 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/mr.json @@ -0,0 +1,61 @@ +{ + "Books": "पुस्तकं", + "Artists": "संगीतकार", + "Albums": "अल्बम", + "Playlists": "प्लेलिस्ट", + "HeaderAlbumArtists": "अल्बम संगीतकार", + "Folders": "फोल्डर", + "HeaderFavoriteEpisodes": "आवडते भाग", + "HeaderFavoriteSongs": "आवडती गाणी", + "Movies": "चित्रपट", + "HeaderFavoriteArtists": "आवडते संगीतकार", + "Shows": "कार्यक्रम", + "HeaderFavoriteAlbums": "आवडते अल्बम", + "Channels": "वाहिन्या", + "ValueSpecialEpisodeName": "विशेष - {0}", + "HeaderFavoriteShows": "आवडते कार्यक्रम", + "Favorites": "आवडीचे", + "HeaderNextUp": "यानंतर", + "Songs": "गाणी", + "HeaderLiveTV": "लाइव्ह टीव्ही", + "Genres": "जाँनरे", + "Photos": "चित्र", + "TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा", + "TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.", + "TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका", + "TaskUpdatePlugins": "प्लगइन अपडेट करा", + "TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका", + "TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका", + "TasksChannelsCategory": "इंटरनेट वाहिन्या", + "TasksApplicationCategory": "अॅप्लिकेशन", + "TasksLibraryCategory": "संग्रहालय", + "VersionNumber": "आवृत्ती {0}", + "UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे", + "UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत", + "UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे", + "UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे", + "User": "प्रयोक्ता", + "TvShows": "टीव्ही कार्यक्रम", + "StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.", + "Plugin": "प्लगइन", + "NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे", + "NotificationOptionApplicationUpdateInstalled": "अॅप्लिकेशन अपडेट इन्स्टॉल केले आहे", + "NotificationOptionApplicationUpdateAvailable": "अॅप्लिकेशन अपडेट उपलब्ध आहे", + "NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.", + "NameSeasonUnknown": "अज्ञात सीझन", + "NameSeasonNumber": "सीझन {0}", + "MusicVideos": "संगीत व्हिडीयो", + "Music": "संगीत", + "MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे", + "MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे", + "Latest": "नवीनतम", + "LabelIpAddressValue": "आयपी पत्ता: {0}", + "ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले", + "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले", + "HomeVideos": "घरचे व्हिडीयो", + "HeaderRecordingGroups": "रेकॉर्डिंग गट", + "HeaderCameraUploads": "कॅमेरा अपलोड", + "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे", + "Application": "अॅप्लिकेशन", + "AppDeviceValues": "अॅप: {0}, यंत्र: {1}" +} diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index bc36cbdd3..baa12e98e 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -1,11 +1,11 @@ { "Albums": "Albums", "AppDeviceValues": "App: {0}, Apparaat: {1}", - "Application": "Applicatie", + "Application": "Programma", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} succesvol geauthenticeerd", + "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}", + "CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}", "ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek", "ValueSpecialEpisodeName": "Speciaal - {0}", - "VersionNumber": "Versie {0}" + "VersionNumber": "Versie {0}", + "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.", + "TaskDownloadMissingSubtitles": "Download missende ondertitels", + "TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.", + "TaskRefreshChannels": "Vernieuw Kanalen", + "TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.", + "TaskCleanLogs": "Log Folder Opschonen", + "TaskCleanTranscode": "Transcode Folder Opschonen", + "TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.", + "TaskUpdatePlugins": "Update Plugins", + "TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.", + "TaskRefreshPeople": "Vernieuw Personen", + "TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.", + "TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.", + "TaskRefreshLibrary": "Scan Media Bibliotheek", + "TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.", + "TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken", + "TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.", + "TaskCleanCache": "Cache Folder Opschonen", + "TasksChannelsCategory": "Internet Kanalen", + "TasksApplicationCategory": "Applicatie", + "TasksLibraryCategory": "Bibliotheek", + "TasksMaintenanceCategory": "Onderhoud" } diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index ebf35c492..c1fb65743 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -26,7 +26,7 @@ "HeaderLiveTV": "TV em Direto", "HeaderNextUp": "A Seguir", "HeaderRecordingGroups": "Grupos de Gravação", - "HomeVideos": "Home videos", + "HomeVideos": "Videos caseiros", "Inherit": "Herdar", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia", "ValueSpecialEpisodeName": "Especial - {0}", - "VersionNumber": "Versão {0}" + "VersionNumber": "Versão {0}", + "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.", + "TaskDownloadMissingSubtitles": "Fazer download de legendas em falta", + "TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.", + "TaskRefreshChannels": "Atualizar Canais", + "TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.", + "TaskCleanTranscode": "Limpar a Diretoria de Transcode", + "TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskRefreshPeopleDescription": "Atualizar metadados para atores e diretores na biblioteca.", + "TaskRefreshPeople": "Atualizar Pessoas", + "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.", + "TaskCleanLogs": "Limpar a Diretoria de Logs", + "TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.", + "TaskRefreshLibrary": "Scannear Biblioteca de Música", + "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.", + "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos", + "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.", + "TaskCleanCache": "Limpar Cache", + "TasksChannelsCategory": "Canais da Internet", + "TasksApplicationCategory": "Aplicação", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Manutenção" } diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 3d5f7cab2..25c5b9053 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -91,5 +91,17 @@ "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Application": "Aplicação", - "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}" + "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}", + "TaskCleanCache": "Limpar Diretório de Cache", + "TasksApplicationCategory": "Aplicação", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Manutenção", + "TaskRefreshChannels": "Atualizar Canais", + "TaskUpdatePlugins": "Atualizar Plugins", + "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.", + "TaskCleanLogs": "Limpar diretório de log", + "TaskRefreshLibrary": "Escanear biblioteca de mídias", + "TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.", + "TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.", + "TasksChannelsCategory": "Canais de Internet" } diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index c46aa5c30..71ee6446c 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -9,8 +9,8 @@ "Channels": "Каналы", "ChapterNameValue": "Сцена {0}", "Collections": "Коллекции", - "DeviceOfflineWithName": "{0} - подкл. разъ-но", - "DeviceOnlineWithName": "{0} - подкл. уст-но", + "DeviceOfflineWithName": "{0} - отключено", + "DeviceOnlineWithName": "{0} - подключено", "FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна", "Favorites": "Избранное", "Folders": "Папки", @@ -26,30 +26,30 @@ "HeaderLiveTV": "Эфир", "HeaderNextUp": "Очередное", "HeaderRecordingGroups": "Группы записей", - "HomeVideos": "Дом. видео", + "HomeVideos": "Домашнее видео", "Inherit": "Наследуемое", "ItemAddedWithName": "{0} - добавлено в медиатеку", "ItemRemovedWithName": "{0} - изъято из медиатеки", "LabelIpAddressValue": "IP-адрес: {0}", "LabelRunningTimeValue": "Длительность: {0}", - "Latest": "Новейшее", + "Latest": "Последнее", "MessageApplicationUpdated": "Jellyfin Server был обновлён", "MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена", - "MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена", + "MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена", + "MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена", "MixedContent": "Смешанное содержимое", "Movies": "Кино", "Music": "Музыка", - "MusicVideos": "Муз. видео", + "MusicVideos": "Музыкальные клипы", "NameInstallFailed": "Установка {0} неудачна", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Сезон неопознан", "NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.", "NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения", "NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено", - "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но", - "NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но", - "NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры", + "NotificationOptionAudioPlayback": "Воспроизведение аудио запущено", + "NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено", + "NotificationOptionCameraImageUploaded": "Изображения с камеры загружены", "NotificationOptionInstallationFailed": "Сбой установки", "NotificationOptionNewLibraryContent": "Новое содержание добавлено", "NotificationOptionPluginError": "Сбой плагина", @@ -59,8 +59,8 @@ "NotificationOptionServerRestartRequired": "Требуется перезапуск сервера", "NotificationOptionTaskFailed": "Сбой назначенной задачи", "NotificationOptionUserLockedOut": "Пользователь заблокирован", - "NotificationOptionVideoPlayback": "Воспр-ие видео зап-но", - "NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но", + "NotificationOptionVideoPlayback": "Воспроизведение видео запущено", + "NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено", "Photos": "Фото", "Playlists": "Плей-листы", "Plugin": "Плагин", @@ -76,21 +76,43 @@ "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", - "Sync": "Синхро", + "Sync": "Синхронизация", "System": "Система", "TvShows": "ТВ", - "User": "Польз-ль", + "User": "Пользователь", "UserCreatedWithName": "Пользователь {0} был создан", "UserDeletedWithName": "Пользователь {0} был удалён", "UserDownloadingItemWithValues": "{0} загружает {1}", "UserLockedOutWithName": "Пользователь {0} был заблокирован", - "UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но", - "UserOnlineFromDevice": "{0} - подкл. с {1} уст-но", - "UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён", - "UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены", - "UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}", - "UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}", + "UserOfflineFromDevice": "{0} отключился с {1}", + "UserOnlineFromDevice": "{0} подключился с {1}", + "UserPasswordChangedWithName": "Пароль пользователя {0} был изменён", + "UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены", + "UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}", + "UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}", "ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)", - "ValueSpecialEpisodeName": "Спецэпизод - {0}", - "VersionNumber": "Версия {0}" + "ValueSpecialEpisodeName": "Специальный эпизод - {0}", + "VersionNumber": "Версия {0}", + "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров", + "TaskRefreshChannels": "Обновление каналов", + "TaskCleanTranscode": "Очистка каталога перекодировки", + "TaskUpdatePlugins": "Обновление плагинов", + "TaskRefreshPeople": "Обновление метаданных людей", + "TaskCleanLogs": "Очистка каталога журналов", + "TaskRefreshLibrary": "Сканирование медиатеки", + "TaskRefreshChapterImages": "Извлечение изображений сцен", + "TaskCleanCache": "Очистка каталога кеша", + "TasksChannelsCategory": "Интернет-каналы", + "TasksApplicationCategory": "Приложение", + "TasksLibraryCategory": "Медиатека", + "TasksMaintenanceCategory": "Обслуживание", + "TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.", + "TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.", + "TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.", + "TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.", + "TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.", + "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).", + "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.", + "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.", + "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе." } diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 96891f994..b7c50394a 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -92,5 +92,26 @@ "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}", "ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek", "ValueSpecialEpisodeName": "Specialavsnitt - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.", + "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter", + "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.", + "TaskRefreshChannels": "Uppdatera kanaler", + "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.", + "TaskCleanTranscode": "Töm transkodningskatalog", + "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.", + "TaskUpdatePlugins": "Uppdatera insticksprogram", + "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.", + "TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.", + "TaskCleanLogs": "Töm loggkatalog", + "TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.", + "TaskRefreshLibrary": "Genomsök mediabibliotek", + "TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.", + "TaskRefreshChapterImages": "Extrahera kapitelbilder", + "TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.", + "TaskCleanCache": "Rensa cachekatalog", + "TasksChannelsCategory": "Internetkanaler", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliotek", + "TasksMaintenanceCategory": "Underhåll" } diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 1d13b0354..3cf3482eb 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Ses çalma başladı", "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu", "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi", - "NotificationOptionInstallationFailed": "Yükleme başarısız oldu", + "NotificationOptionInstallationFailed": "Kurulum hatası", "NotificationOptionNewLibraryContent": "Yeni içerik eklendi", "NotificationOptionPluginError": "Eklenti hatası", "NotificationOptionPluginInstalled": "Eklenti yüklendi", @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", "ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi", "ValueSpecialEpisodeName": "Özel - {0}", - "VersionNumber": "Versiyon {0}" + "VersionNumber": "Versiyon {0}", + "TaskCleanCache": "Geçici dosya klasörünü temizle", + "TasksChannelsCategory": "İnternet kanalları", + "TasksApplicationCategory": "Uygulama", + "TasksLibraryCategory": "Kütüphane", + "TasksMaintenanceCategory": "Onarım", + "TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.", + "TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.", + "TaskDownloadMissingSubtitles": "Eksik altyazıları indir", + "TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.", + "TaskRefreshChannels": "Kanalları Yenile", + "TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.", + "TaskCleanTranscode": "Dönüşüm Dizinini Temizle", + "TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.", + "TaskUpdatePlugins": "Eklentileri Güncelle", + "TaskRefreshPeople": "Kullanıcıları Yenile", + "TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.", + "TaskCleanLogs": "Log Dizinini Temizle", + "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.", + "TaskRefreshLibrary": "Medya Kütüphanesini Tara", + "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.", + "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", + "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler." } diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json new file mode 100644 index 000000000..9a5874e29 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -0,0 +1,117 @@ +{ + "HeaderFavoriteAlbums": "پسندیدہ البمز", + "HeaderNextUp": "اگلا", + "HeaderFavoriteArtists": "پسندیدہ فنکار", + "HeaderAlbumArtists": "البم کے فنکار", + "Movies": "فلمیں", + "HeaderFavoriteEpisodes": "پسندیدہ اقساط", + "Collections": "مجموعہ", + "Folders": "فولڈرز", + "HeaderLiveTV": "براہ راست ٹی وی", + "Channels": "چینل", + "HeaderContinueWatching": "دیکھنا جاری رکھیں", + "Playlists": "پلے لسٹس", + "ValueSpecialEpisodeName": "خاص - {0}", + "Shows": "شوز", + "Genres": "انواع", + "Artists": "فنکار", + "Sync": "مطابقت", + "Photos": "تصوریں", + "Albums": "البم", + "Favorites": "پسندیدہ", + "Songs": "گانے", + "Books": "کتابیں", + "HeaderFavoriteSongs": "پسندیدہ گانے", + "HeaderFavoriteShows": "پسندیدہ شوز", + "TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔", + "TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں", + "TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔", + "TaskRefreshChannels": "چینلز ریفریش کریں", + "TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔", + "TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں", + "TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔", + "TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں", + "TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔", + "TaskRefreshPeople": "لوگوں کو تروتازہ کریں", + "TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔", + "TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں", + "TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.", + "TaskRefreshLibrary": "اسکین میڈیا لائبریری", + "TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔", + "TaskRefreshChapterImages": "باب کی تصاویر نکالیں", + "TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔", + "TaskCleanCache": "کیش ڈائرکٹری کلیر کریں", + "TasksChannelsCategory": "انٹرنیٹ چینلز", + "TasksApplicationCategory": "پروگرام", + "TasksLibraryCategory": "لآیبریری", + "TasksMaintenanceCategory": "مرمت", + "VersionNumber": "ورژن {0}", + "ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے", + "UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے", + "UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے", + "UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے", + "UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے", + "UserOnlineFromDevice": "{0} آن لائن ہے {1} سے", + "UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}", + "UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے", + "UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}", + "UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے", + "UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے", + "User": "صارف", + "TvShows": "ٹی وی کے پروگرام", + "System": "نظام", + "SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے", + "StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔", + "ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے", + "ScheduledTaskStartedWithName": "{0} شروع", + "ScheduledTaskFailedWithName": "{0} ناکام", + "ProviderValue": "فراہم کرنے والا: {0}", + "PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی", + "PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا", + "PluginInstalledWithName": "{0} انسٹال کیا گیا تھا", + "Plugin": "پلگن", + "NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا", + "NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا", + "NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا", + "NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی", + "NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے", + "NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال", + "NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا", + "NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا", + "NotificationOptionPluginError": "پلگ ان کی ناکامی", + "NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا", + "NotificationOptionInstallationFailed": "تنصیب کی ناکامی", + "NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی", + "NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا", + "NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا", + "NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے", + "NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے", + "NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔", + "NameSeasonUnknown": "نامعلوم باب", + "NameSeasonNumber": "باب {0}", + "NameInstallFailed": "{0} تنصیب ناکام ہوگئی", + "MusicVideos": "موسیقی ویڈیو", + "Music": "موسیقی", + "MixedContent": "مخلوط مواد", + "MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے", + "MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے", + "MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}", + "MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے", + "Latest": "تازہ ترین", + "LabelRunningTimeValue": "چلانے کی مدت", + "LabelIpAddressValue": "ای پی پتے {0}", + "ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے", + "ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے", + "Inherit": "وراثت میں", + "HomeVideos": "ہوم ویڈیو", + "HeaderRecordingGroups": "ریکارڈنگ گروپس", + "HeaderCameraUploads": "کیمرہ اپلوڈز", + "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}", + "DeviceOnlineWithName": "{0} متصل ھو چکا ھے", + "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے", + "ChapterNameValue": "باب", + "AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے", + "CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}", + "Application": "پروگرام", + "AppDeviceValues": "پروگرام:{0}, آلہ:{1}" +} diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 21034b76f..a22f66df9 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -20,7 +20,7 @@ "HeaderContinueWatching": "繼續觀賞", "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛演出者", - "HeaderFavoriteEpisodes": "最愛級數", + "HeaderFavoriteEpisodes": "最愛影集", "HeaderFavoriteShows": "最愛節目", "HeaderFavoriteSongs": "最愛歌曲", "HeaderLiveTV": "電視直播", @@ -50,10 +50,10 @@ "NotificationOptionCameraImageUploaded": "相機相片已上傳", "NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionNewLibraryContent": "已新增新內容", - "NotificationOptionPluginError": "擴充元件錯誤", - "NotificationOptionPluginInstalled": "擴充元件已安裝", - "NotificationOptionPluginUninstalled": "擴充元件已移除", - "NotificationOptionPluginUpdateInstalled": "已更新擴充元件", + "NotificationOptionPluginError": "插件安裝錯誤", + "NotificationOptionPluginInstalled": "插件已安裝", + "NotificationOptionPluginUninstalled": "插件已移除", + "NotificationOptionPluginUpdateInstalled": "插件已更新", "NotificationOptionServerRestartRequired": "伺服器需要重新啟動", "NotificationOptionTaskFailed": "排程任務失敗", "NotificationOptionUserLockedOut": "使用者已鎖定", @@ -61,7 +61,7 @@ "NotificationOptionVideoPlaybackStopped": "影片停止播放", "Photos": "相片", "Playlists": "播放清單", - "Plugin": "外掛", + "Plugin": "插件", "PluginInstalledWithName": "{0} 已安裝", "PluginUninstalledWithName": "{0} 已移除", "PluginUpdatedWithName": "{0} 已更新", @@ -91,5 +91,27 @@ "VersionNumber": "版本 {0}", "HeaderRecordingGroups": "錄製組", "Inherit": "繼承", - "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕" + "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕", + "TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。", + "TaskDownloadMissingSubtitles": "下載遺失的字幕", + "TaskRefreshChannels": "重新整理頻道", + "TaskUpdatePlugins": "更新插件", + "TaskRefreshPeople": "重新整理人員", + "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。", + "TaskCleanLogs": "清空紀錄資料夾", + "TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。", + "TaskRefreshLibrary": "掃描媒體庫", + "TaskRefreshChapterImages": "擷取章節圖片", + "TaskCleanCacheDescription": "刪除系統長時間不需要的快取。", + "TaskCleanCache": "清除快取資料夾", + "TasksLibraryCategory": "媒體庫", + "TaskRefreshChannelsDescription": "重新整理網絡頻道資料。", + "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", + "TaskCleanTranscode": "清除轉碼資料夾", + "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", + "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。", + "TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。", + "TasksChannelsCategory": "網絡頻道", + "TasksApplicationCategory": "應用程式", + "TasksMaintenanceCategory": "維修" } diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index bda43e832..e2a634e1a 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -23,9 +23,6 @@ namespace Emby.Server.Implementations.Localization private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; - /// <summary> - /// The _configuration manager. - /// </summary> private readonly IServerConfigurationManager _configurationManager; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index ecf58dbc0..6ffa581a9 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -32,22 +32,8 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue = new ConcurrentQueue<Tuple<Type, TaskOptions>>(); - /// <summary> - /// Gets or sets the json serializer. - /// </summary> - /// <value>The json serializer.</value> private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// Gets or sets the application paths. - /// </summary> - /// <value>The application paths.</value> private readonly IApplicationPaths _applicationPaths; - - /// <summary> - /// Gets the logger. - /// </summary> - /// <value>The logger.</value> private readonly ILogger _logger; private readonly IFileSystem _fileSystem; @@ -56,17 +42,17 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> /// <param name="applicationPaths">The application paths.</param> /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="loggerFactory">The logger factory.</param> + /// <param name="logger">The logger.</param> /// <param name="fileSystem">The filesystem manager.</param> public TaskManager( IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, - ILoggerFactory loggerFactory, + ILogger<TaskManager> logger, IFileSystem fileSystem) { _applicationPaths = applicationPaths; _jsonSerializer = jsonSerializer; - _logger = loggerFactory.CreateLogger(nameof(TaskManager)); + _logger = logger; _fileSystem = fileSystem; ScheduledTasks = Array.Empty<IScheduledTaskWorker>(); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 588944d0e..6a1afced7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks { progress.Report(0); - var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken) - .ToListAsync(cancellationToken) - .ConfigureAwait(false); + var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken); + var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList(); progress.Report(10); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 1ef5c4b99..4e4029f06 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -15,8 +15,8 @@ namespace Emby.Server.Implementations.Security { public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository { - public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config) - : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository))) + public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config) + : base(logger) { DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index de768333d..c93c02c48 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request)); - throw new SecurityException("Invalid username or password entered."); + throw new AuthenticationException("Invalid username or password entered."); } if (!string.IsNullOrEmpty(request.DeviceId) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c897036eb..0b2309889 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -3,8 +3,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -18,17 +20,23 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Updates { /// <summary> - /// Manages all install, uninstall and update operations (both plugins and system). + /// Manages all install, uninstall, and update operations for the system and individual plugins. /// </summary> public class InstallationManager : IInstallationManager { /// <summary> - /// The _logger. + /// The key for a setting that specifies a URL for the plugin repository JSON manifest. + /// </summary> + public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl"; + + /// <summary> + /// The logger. /// </summary> private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; @@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates private readonly IApplicationHost _applicationHost; private readonly IZipClient _zipClient; + private readonly IConfiguration _appConfig; private readonly object _currentInstallationsLock = new object(); @@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates IJsonSerializer jsonSerializer, IServerConfigurationManager config, IFileSystem fileSystem, - IZipClient zipClient) + IZipClient zipClient, + IConfiguration appConfig) { if (logger == null) { @@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates _config = config; _fileSystem = fileSystem; _zipClient = zipClient; + _appConfig = appConfig; } /// <inheritdoc /> @@ -101,10 +112,10 @@ namespace Emby.Server.Implementations.Updates public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled; /// <inheritdoc /> - public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated; + public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated; /// <inheritdoc /> - public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled; + public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled; /// <inheritdoc /> public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal; @@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates /// <inheritdoc /> public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default) { - using (var response = await _httpClient.SendAsync( - new HttpRequestOptions + var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey); + + try + { + using (var response = await _httpClient.SendAsync( + new HttpRequestOptions + { + Url = manifestUrl, + CancellationToken = cancellationToken, + CacheMode = CacheMode.Unconditional, + CacheLength = TimeSpan.FromMinutes(3) + }, + HttpMethod.Get).ConfigureAwait(false)) + using (Stream stream = response.Content) { - Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", - CancellationToken = cancellationToken, - CacheMode = CacheMode.Unconditional, - CacheLength = TimeSpan.FromMinutes(3) - }, - HttpMethod.Get).ConfigureAwait(false)) - using (Stream stream = response.Content) + try + { + return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false); + } + catch (SerializationException ex) + { + const string LogTemplate = + "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " + + "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " + + PluginManifestUrlKey + ", please ensure that it is correct."; + _logger.LogError(ex, LogTemplate, manifestUrl); + throw; + } + } + } + catch (UriFormatException ex) { - return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>( - stream).ConfigureAwait(false); + const string LogTemplate = + "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " + + "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey; + _logger.LogError(ex, LogTemplate, manifestUrl); + throw; } } @@ -148,60 +183,56 @@ namespace Emby.Server.Implementations.Updates } /// <inheritdoc /> - public IEnumerable<PackageVersionInfo> GetCompatibleVersions( - IEnumerable<PackageVersionInfo> availableVersions, - Version minVersion = null, - PackageVersionClass classification = PackageVersionClass.Release) + public IEnumerable<VersionInfo> GetCompatibleVersions( + IEnumerable<VersionInfo> availableVersions, + Version minVersion = null) { var appVer = _applicationHost.ApplicationVersion; availableVersions = availableVersions - .Where(x => x.classification == classification - && Version.Parse(x.requiredVersionStr) <= appVer); + .Where(x => Version.Parse(x.targetAbi) <= appVer); if (minVersion != null) { - availableVersions = availableVersions.Where(x => x.Version >= minVersion); + availableVersions = availableVersions.Where(x => x.version >= minVersion); } - return availableVersions.OrderByDescending(x => x.Version); + return availableVersions.OrderByDescending(x => x.version); } /// <inheritdoc /> - public IEnumerable<PackageVersionInfo> GetCompatibleVersions( + public IEnumerable<VersionInfo> GetCompatibleVersions( IEnumerable<PackageInfo> availablePackages, string name = null, Guid guid = default, - Version minVersion = null, - PackageVersionClass classification = PackageVersionClass.Release) + Version minVersion = null) { var package = FilterPackages(availablePackages, name, guid).FirstOrDefault(); - // Package not found. + // Package not found in repository if (package == null) { - return Enumerable.Empty<PackageVersionInfo>(); + return Enumerable.Empty<VersionInfo>(); } return GetCompatibleVersions( package.versions, - minVersion, - classification); + minVersion); } /// <inheritdoc /> - public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default) + public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default) { var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); + return GetAvailablePluginUpdates(catalog); + } - var systemUpdateLevel = _applicationHost.SystemUpdateLevel; - - // Figure out what needs to be installed + private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog) + { foreach (var plugin in _applicationHost.Plugins) { - var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel); - var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version); - if (version != null - && !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase))) + var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version); + var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version); + if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase))) { yield return version; } @@ -209,7 +240,7 @@ namespace Emby.Server.Implementations.Updates } /// <inheritdoc /> - public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken) + public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken) { if (package == null) { @@ -218,11 +249,9 @@ namespace Emby.Server.Implementations.Updates var installationInfo = new InstallationInfo { - Id = Guid.NewGuid(), + Guid = package.guid, Name = package.name, - AssemblyGuid = package.guid, - UpdateClass = package.classification, - Version = package.versionStr + Version = package.version.ToString() }; var innerCancellationTokenSource = new CancellationTokenSource(); @@ -240,7 +269,7 @@ namespace Emby.Server.Implementations.Updates var installationEventArgs = new InstallationEventArgs { InstallationInfo = installationInfo, - PackageVersionInfo = package + VersionInfo = package }; PackageInstalling?.Invoke(this, installationEventArgs); @@ -265,7 +294,7 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Remove(tuple); } - _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr); + _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version); PackageInstallationCancelled?.Invoke(this, installationEventArgs); @@ -301,7 +330,7 @@ namespace Emby.Server.Implementations.Updates /// <param name="package">The package.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns><see cref="Task" />.</returns> - private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken) + private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) @@ -313,26 +342,26 @@ namespace Emby.Server.Implementations.Updates // Do plugin-specific processing if (plugin == null) { - _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); + _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version); - PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package)); + PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package)); } else { - _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification); + _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version); - PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package))); + PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package))); } _applicationHost.NotifyPendingRestart(); } - private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken) { - var extension = Path.GetExtension(package.targetFilename); + var extension = Path.GetExtension(package.filename); if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) { - _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename); + _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename); return; } @@ -379,7 +408,7 @@ namespace Emby.Server.Implementations.Updates } /// <summary> - /// Uninstalls a plugin + /// Uninstalls a plugin. /// </summary> /// <param name="plugin">The plugin.</param> public void UninstallPlugin(IPlugin plugin) @@ -437,7 +466,7 @@ namespace Emby.Server.Implementations.Updates { lock (_currentInstallationsLock) { - var install = _currentInstallations.Find(x => x.info.Id == id); + var install = _currentInstallations.Find(x => x.info.Guid == id.ToString()); if (install == default((InstallationInfo, CancellationTokenSource))) { return false; |
