diff options
Diffstat (limited to 'Emby.Server.Implementations')
177 files changed, 1720 insertions, 1479 deletions
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 660bbb2de..6edfad575 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase CachePath = cacheDirectoryPath; WebPath = webDirectoryPath; - DataPath = Path.Combine(ProgramDataPath, "data"); + _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName; } /// <summary> @@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the data directory. /// </summary> /// <value>The data directory.</value> - public string DataPath - { - get => _dataPath; - private set => _dataPath = Directory.CreateDirectory(value).FullName; - } + public string DataPath => _dataPath; /// <inheritdoc /> public string VirtualDataPath => "%AppDataPath%"; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 4f72c8ce1..8c919db43 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 3f7076383..de770f59e 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,9 +1,6 @@ -#nullable enable - using System; using System.IO; using System.Linq; -using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 164e6d49d..82995deb3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -210,7 +212,7 @@ namespace Emby.Server.Implementations /// Gets or sets the configuration manager. /// </summary> /// <value>The configuration manager.</value> - protected IConfigurationManager ConfigurationManager { get; set; } + public ServerConfigurationManager ConfigurationManager { get; set; } /// <summary> /// Gets or sets the service provider. @@ -233,12 +235,6 @@ namespace Emby.Server.Implementations public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; /// <summary> - /// Gets the server configuration manager. - /// </summary> - /// <value>The server configuration manager.</value> - public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - - /// <summary> /// Initializes a new instance of the <see cref="ApplicationHost"/> class. /// </summary> /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> @@ -255,43 +251,26 @@ namespace Emby.Server.Implementations IFileSystem fileSystem, IServiceCollection serviceCollection) { - _xmlSerializer = new MyXmlSerializer(); - - ServiceCollection = serviceCollection; - ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; - _fileSystemManager = fileSystem; - - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); - // Have to migrate settings here as migration subsystem not yet initialised. - MigrateNetworkConfiguration(); - - // Have to pre-register the NetworkConfigurationFactory, as the configuration sub-system is not yet initialised. - ConfigurationManager.RegisterConfiguration<NetworkConfigurationFactory>(); - NetManager = new NetworkManager((IServerConfigurationManager)ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>()); - - Logger = LoggerFactory.CreateLogger<ApplicationHost>(); - _startupOptions = options; _startupConfig = startupConfig; + _fileSystemManager = fileSystem; + ServiceCollection = serviceCollection; - // Initialize runtime stat collection - if (ServerConfigurationManager.Configuration.EnableMetrics) - { - DotNetRuntimeStatsBuilder.Default().StartCollecting(); - } - + Logger = LoggerFactory.CreateLogger<ApplicationHost>(); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; ApplicationVersionString = ApplicationVersion.ToString(3); ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + _xmlSerializer = new MyXmlSerializer(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); _pluginManager = new PluginManager( LoggerFactory.CreateLogger<PluginManager>(), this, - ServerConfigurationManager.Configuration, + ConfigurationManager.Configuration, ApplicationPaths.PluginsPath, ApplicationVersion); } @@ -306,9 +285,9 @@ namespace Emby.Server.Implementations if (!File.Exists(path)) { var networkSettings = new NetworkConfiguration(); - ClassMigrationHelper.CopyProperties(ServerConfigurationManager.Configuration, networkSettings); + ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings); _xmlSerializer.SerializeToFile(networkSettings, path); - Logger?.LogDebug("Successfully migrated network settings."); + Logger.LogDebug("Successfully migrated network settings."); } } @@ -358,10 +337,7 @@ namespace Emby.Server.Implementations { get { - if (_deviceId == null) - { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); - } + _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); return _deviceId.Value; } @@ -393,10 +369,7 @@ namespace Emby.Server.Implementations /// <returns>System.Object.</returns> protected object CreateInstanceSafe(Type type) { - if (_creatingInstances == null) - { - _creatingInstances = new List<Type>(); - } + _creatingInstances ??= new List<Type>(); if (_creatingInstances.IndexOf(type) != -1) { @@ -545,7 +518,21 @@ namespace Emby.Server.Implementations /// <inheritdoc/> public void Init() { - var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); + DiscoverTypes(); + + ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); + + // Have to migrate settings here as migration subsystem not yet initialised. + MigrateNetworkConfiguration(); + NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>()); + + // Initialize runtime stat collection + if (ConfigurationManager.Configuration.EnableMetrics) + { + DotNetRuntimeStatsBuilder.Default().StartCollecting(); + } + + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); HttpPort = networkConfiguration.HttpServerPortNumber; HttpsPort = networkConfiguration.HttpsPortNumber; @@ -563,8 +550,6 @@ namespace Emby.Server.Implementations }; Certificate = GetCertificate(CertificateInfo); - DiscoverTypes(); - RegisterServices(); _pluginManager.RegisterServices(ServiceCollection); @@ -579,7 +564,8 @@ namespace Emby.Server.Implementations ServiceCollection.AddMemoryCache(); - ServiceCollection.AddSingleton(ConfigurationManager); + ServiceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager); + ServiceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager); ServiceCollection.AddSingleton<IApplicationHost>(this); ServiceCollection.AddSingleton<IPluginManager>(_pluginManager); ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); @@ -606,8 +592,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<IServerApplicationHost>(this); ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths); - ServiceCollection.AddSingleton(ServerConfigurationManager); - ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>(); ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); @@ -619,12 +603,8 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); - // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); - - // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>)); ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); + ServiceCollection.AddSingleton<EncodingHelper>(); // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); @@ -689,14 +669,14 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(); - ServiceCollection.AddSingleton<EncodingHelper>(); - ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); ServiceCollection.AddSingleton<TranscodingJobHelper>(); ServiceCollection.AddScoped<MediaInfoHelper>(); ServiceCollection.AddScoped<AudioHelper>(); ServiceCollection.AddScoped<DynamicHlsHelper>(); + + ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>(); } /// <summary> @@ -794,7 +774,7 @@ namespace Emby.Server.Implementations { // For now there's no real way to inject these properly BaseItem.Logger = Resolve<ILogger<BaseItem>>(); - BaseItem.ConfigurationManager = ServerConfigurationManager; + BaseItem.ConfigurationManager = ConfigurationManager; BaseItem.LibraryManager = Resolve<ILibraryManager>(); BaseItem.ProviderManager = Resolve<IProviderManager>(); BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); @@ -816,13 +796,12 @@ namespace Emby.Server.Implementations /// </summary> private void FindParts() { - if (!ServerConfigurationManager.Configuration.IsPortAuthorized) + if (!ConfigurationManager.Configuration.IsPortAuthorized) { - ServerConfigurationManager.Configuration.IsPortAuthorized = true; + ConfigurationManager.Configuration.IsPortAuthorized = true; ConfigurationManager.SaveConfiguration(); } - ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); _pluginManager.CreatePlugins(); _urlPrefixes = GetUrlPrefixes().ToArray(); @@ -926,7 +905,7 @@ namespace Emby.Server.Implementations protected void OnConfigurationUpdated(object sender, EventArgs e) { var requiresRestart = false; - var networkConfiguration = ServerConfigurationManager.GetNetworkConfiguration(); + var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); // Don't do anything if these haven't been set yet if (HttpPort != 0 && HttpsPort != 0) @@ -935,10 +914,10 @@ namespace Emby.Server.Implementations if (networkConfiguration.HttpServerPortNumber != HttpPort || networkConfiguration.HttpsPortNumber != HttpsPort) { - if (ServerConfigurationManager.Configuration.IsPortAuthorized) + if (ConfigurationManager.Configuration.IsPortAuthorized) { - ServerConfigurationManager.Configuration.IsPortAuthorized = false; - ServerConfigurationManager.SaveConfiguration(); + ConfigurationManager.Configuration.IsPortAuthorized = false; + ConfigurationManager.SaveConfiguration(); requiresRestart = true; } @@ -1154,7 +1133,7 @@ namespace Emby.Server.Implementations } /// <inheritdoc/> - public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.GetNetworkConfiguration().EnableHttps; + public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps; /// <inheritdoc/> public string GetSmartApiUrl(IPAddress ipAddress, int? port = null) @@ -1238,14 +1217,14 @@ namespace Emby.Server.Implementations Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), Host = host, Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort), - Path = ServerConfigurationManager.GetNetworkConfiguration().BaseUrl + Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl }.ToString().TrimEnd('/'); } public string FriendlyName => - string.IsNullOrEmpty(ServerConfigurationManager.Configuration.ServerName) + string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName) ? Environment.MachineName - : ServerConfigurationManager.Configuration.ServerName; + : ConfigurationManager.Configuration.ServerName; /// <summary> /// Shuts down. diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 87ebe960a..448f12403 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,9 +1,10 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c69a07e83..ca8409402 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections return null; }) .Where(i => i != null) - .GroupBy(x => x.Id) + .GroupBy(x => x!.Id) // We removed the null values .Select(x => x.First()) - .ToList(); + .ToList()!; // Again... the list doesn't contain any null values } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index db532ce5b..82d80fc83 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,6 +1,7 @@ +#nullable disable + using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -8,11 +9,9 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -124,7 +123,7 @@ namespace Emby.Server.Implementations.Collections private IEnumerable<BoxSet> GetCollections(User user) { - var folder = GetCollectionsFolder(false).Result; + var folder = GetCollectionsFolder(false).GetAwaiter().GetResult(); return folder == null ? Enumerable.Empty<BoxSet>() @@ -167,7 +166,7 @@ namespace Emby.Server.Implementations.Collections parentFolder.AddChild(collection, CancellationToken.None); - if (options.ItemIdList.Length > 0) + if (options.ItemIdList.Count > 0) { await AddToCollectionAsync( collection.Id, @@ -251,11 +250,7 @@ namespace Emby.Server.Implementations.Collections if (fireEvent) { - ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } } } @@ -307,11 +302,7 @@ namespace Emby.Server.Implementations.Collections }, RefreshPriority.High); - ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs - { - Collection = collection, - ItemsChanged = itemList - }); + ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList)); } /// <inheritdoc /> @@ -319,11 +310,11 @@ namespace Emby.Server.Implementations.Collections { var results = new Dictionary<Guid, BaseItem>(); - var allBoxsets = GetCollections(user).ToList(); + var allBoxSets = GetCollections(user).ToList(); foreach (var item in items) { - if (!(item is ISupportsBoxSetGrouping)) + if (item is not ISupportsBoxSetGrouping) { results[item.Id] = item; } @@ -331,34 +322,45 @@ namespace Emby.Server.Implementations.Collections { var itemId = item.Id; - var currentBoxSets = allBoxsets - .Where(i => i.ContainsLinkedChildByItemId(itemId)) - .ToList(); - - if (currentBoxSets.Count > 0) + var itemIsInBoxSet = false; + foreach (var boxSet in allBoxSets) { - foreach (var boxset in currentBoxSets) + if (!boxSet.ContainsLinkedChildByItemId(itemId)) { - results[boxset.Id] = boxset; + continue; } + + itemIsInBoxSet = true; + + results.TryAdd(boxSet.Id, boxSet); } - else + + // skip any item that is in a box set + if (itemIsInBoxSet) { - var alreadyInResults = false; - foreach (var child in item.GetMediaSources(true)) + continue; + } + + var alreadyInResults = false; + // this is kind of a performance hack because only Video has alternate versions that should be in a box set? + if (item is Video video) + { + foreach (var childId in video.GetLocalAlternateVersionIds()) { - if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id)) + if (!results.ContainsKey(childId)) { - alreadyInResults = true; - break; + continue; } - } - if (!alreadyInResults) - { - results[item.Id] = item; + alreadyInResults = true; + break; } } + + if (!alreadyInResults) + { + results[itemId] = item; + } } } diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 7a8ed8c29..ff5602f24 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using System.IO; diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index cd9dbb1bd..01dc728c1 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Emby.Server.Implementations.HttpServer; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 12a9e44e7..4a9b28085 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Security.Cryptography; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 8c756a7f4..6f23a0888 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -181,11 +183,9 @@ namespace Emby.Server.Implementations.Data foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) { - if (row[1].SQLiteType != SQLiteType.Null) + if (row.TryGetString(1, out var columnName)) { - var name = row[1].ToString(); - - columnNames.Add(name); + columnNames.Add(columnName); } } diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 5c094ddd2..afc8966f9 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data { public class ManagedConnection : IDisposable { - private SQLiteDatabaseConnection _db; + private SQLiteDatabaseConnection? _db; private readonly SemaphoreSlim _writeLock; private bool _disposed = false; @@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data return _db.RunInTransaction(action, mode); } - public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql) + public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql) { return _db.Query(sql); } - public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values) + public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql, params object[] values) { return _db.Query(sql, values); } diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a04d63088..3289e7609 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Data }); } - public static Guid ReadGuidFromBlob(this IResultSetValue result) + public static Guid ReadGuidFromBlob(this ResultSetValue result) { return new Guid(result.ToBlob()); } @@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data private static string GetDateTimeKindFormat(DateTimeKind kind) => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; - public static DateTime ReadDateTime(this IResultSetValue result) + public static DateTime ReadDateTime(this ResultSetValue result) { var dateText = result.ToString(); @@ -96,49 +97,139 @@ namespace Emby.Server.Implementations.Data DateTimeStyles.None).ToUniversalTime(); } - public static DateTime? TryReadDateTime(this IResultSetValue result) + public static bool TryReadDateTime(this IReadOnlyList<ResultSetValue> reader, int index, out DateTime result) { - var dateText = result.ToString(); + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + var dateText = item.ToString(); if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult)) { - return dateTimeResult.ToUniversalTime(); + result = dateTimeResult.ToUniversalTime(); + return true; + } + + result = default; + return false; + } + + public static bool TryGetGuid(this IReadOnlyList<ResultSetValue> reader, int index, out Guid result) + { + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; } - return null; + result = item.ReadGuidFromBlob(); + return true; } - public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index) + public static bool IsDbNull(this ResultSetValue result) { - return result[index].SQLiteType == SQLiteType.Null; + return result.SQLiteType == SQLiteType.Null; } - public static string GetString(this IReadOnlyList<IResultSetValue> result, int index) + public static string GetString(this IReadOnlyList<ResultSetValue> result, int index) { return result[index].ToString(); } - public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index) + public static bool TryGetString(this IReadOnlyList<ResultSetValue> reader, int index, out string result) + { + result = null; + var item = reader[index]; + if (item.IsDbNull()) + { + return false; + } + + result = item.ToString(); + return true; + } + + public static bool GetBoolean(this IReadOnlyList<ResultSetValue> result, int index) { return result[index].ToBool(); } - public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index) + public static bool TryGetBoolean(this IReadOnlyList<ResultSetValue> reader, int index, out bool result) + { + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + result = item.ToBool(); + return true; + } + + public static bool TryGetInt32(this IReadOnlyList<ResultSetValue> reader, int index, out int result) { - return result[index].ToInt(); + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + result = item.ToInt(); + return true; } - public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index) + public static long GetInt64(this IReadOnlyList<ResultSetValue> result, int index) { return result[index].ToInt64(); } - public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index) + public static bool TryGetInt64(this IReadOnlyList<ResultSetValue> reader, int index, out long result) + { + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + result = item.ToInt64(); + return true; + } + + public static bool TryGetSingle(this IReadOnlyList<ResultSetValue> reader, int index, out float result) + { + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + result = item.ToFloat(); + return true; + } + + public static bool TryGetDouble(this IReadOnlyList<ResultSetValue> reader, int index, out double result) { - return result[index].ToFloat(); + var item = reader[index]; + if (item.IsDbNull()) + { + result = default; + return false; + } + + result = item.ToDouble(); + return true; } - public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index) + public static Guid GetGuid(this IReadOnlyList<ResultSetValue> result, int index) { return result[index].ReadGuidFromBlob(); } @@ -350,7 +441,7 @@ namespace Emby.Server.Implementations.Data } } - public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement) + public static IEnumerable<IReadOnlyList<ResultSetValue>> ExecuteQuery(this IStatement statement) { while (statement.MoveNext()) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2ae805447..2d060dd65 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,6 +1,9 @@ +#nullable disable + #pragma warning disable CS1591 using System; +using System.Buffers.Text; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -502,7 +505,7 @@ namespace Emby.Server.Implementations.Data using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) { saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); - saveImagesStatement.TryBind("@Images", SerializeImages(item)); + saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); saveImagesStatement.MoveNext(); } @@ -897,8 +900,8 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); saveItemStatement.TryBind("@Tagline", item.Tagline); - saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); - saveItemStatement.TryBind("@Images", SerializeImages(item)); + saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds)); + saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos)); if (item.ProductionLocations.Length > 0) { @@ -968,10 +971,10 @@ namespace Emby.Server.Implementations.Data saveItemStatement.MoveNext(); } - private static string SerializeProviderIds(BaseItem item) + internal static string SerializeProviderIds(Dictionary<string, string> providerIds) { StringBuilder str = new StringBuilder(); - foreach (var i in item.ProviderIds) + foreach (var i in providerIds) { // Ideally we shouldn't need this IsNullOrWhiteSpace check, // but we're seeing some cases of bad data slip through @@ -995,35 +998,25 @@ namespace Emby.Server.Implementations.Data return str.ToString(); } - private static void DeserializeProviderIds(string value, BaseItem item) + internal static void DeserializeProviderIds(string value, IHasProviderIds item) { if (string.IsNullOrWhiteSpace(value)) { return; } - if (item.ProviderIds.Count > 0) - { - return; - } - - var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries); - - foreach (var part in parts) + foreach (var part in value.SpanSplit('|')) { - var idParts = part.Split('='); - - if (idParts.Length == 2) + var providerDelimiterIndex = part.IndexOf('='); + if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('=')) { - item.SetProviderId(idParts[0], idParts[1]); + item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString()); } } } - private string SerializeImages(BaseItem item) + internal string SerializeImages(ItemImageInfo[] images) { - var images = item.ImageInfos; - if (images.Length == 0) { return null; @@ -1045,21 +1038,15 @@ namespace Emby.Server.Implementations.Data return str.ToString(); } - private void DeserializeImages(string value, BaseItem item) + internal ItemImageInfo[] DeserializeImages(string value) { if (string.IsNullOrWhiteSpace(value)) { - return; + return Array.Empty<ItemImageInfo>(); } - if (item.ImageInfos.Length > 0) - { - return; - } - - var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries); var list = new List<ItemImageInfo>(); - foreach (var part in parts) + foreach (var part in value.SpanSplit('|')) { var image = ItemImageInfoFromValueString(part); @@ -1069,15 +1056,14 @@ namespace Emby.Server.Implementations.Data } } - item.ImageInfos = list.ToArray(); + return list.ToArray(); } - public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) + private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) { const char Delimiter = '*'; var path = image.Path ?? string.Empty; - var hash = image.BlurHash ?? string.Empty; bldr.Append(GetPathToSave(path)) .Append(Delimiter) @@ -1087,48 +1073,105 @@ namespace Emby.Server.Implementations.Data .Append(Delimiter) .Append(image.Width) .Append(Delimiter) - .Append(image.Height) - .Append(Delimiter) - // Replace delimiters with other characters. - // This can be removed when we migrate to a proper DB. - .Append(hash.Replace('*', '/').Replace('|', '\\')); + .Append(image.Height); + + var hash = image.BlurHash; + if (!string.IsNullOrEmpty(hash)) + { + bldr.Append(Delimiter) + // Replace delimiters with other characters. + // This can be removed when we migrate to a proper DB. + .Append(hash.Replace('*', '/').Replace('|', '\\')); + } } - public ItemImageInfo ItemImageInfoFromValueString(string value) + internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value) { - var parts = value.Split('*', StringSplitOptions.None); + var nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + return null; + } - if (parts.Length < 3) + ReadOnlySpan<char> path = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) { return null; } - var image = new ItemImageInfo(); + ReadOnlySpan<char> dateModified = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan<char> imageType = value[..nextSegment]; - image.Path = RestorePath(parts[0]); + var image = new ItemImageInfo + { + Path = RestorePath(path.ToString()) + }; - if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) + if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) { image.DateModified = new DateTime(ticks, DateTimeKind.Utc); } - if (Enum.TryParse(parts[2], true, out ImageType type)) + if (Enum.TryParse(imageType.ToString(), true, out ImageType type)) { image.Type = type; } - if (parts.Length >= 5) + // Optional parameters: width*height*blurhash + if (nextSegment + 1 < value.Length - 1) { - if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) - && int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1 || nextSegment == value.Length) + { + return image; + } + + ReadOnlySpan<char> widthSpan = value[..nextSegment]; + + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf('*'); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan<char> heightSpan = value[..nextSegment]; + + if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) + && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) { image.Width = width; image.Height = height; } - if (parts.Length >= 6) + if (nextSegment < value.Length - 1) { - image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|'); + value = value[(nextSegment + 1)..]; + var length = value.Length; + + Span<char> blurHashSpan = stackalloc char[length]; + for (int i = 0; i < length; i++) + { + var c = value[i]; + blurHashSpan[i] = c switch + { + '/' => '*', + '\\' => '|', + _ => c + }; + } + + image.BlurHash = new string(blurHashSpan); } } @@ -1241,12 +1284,12 @@ namespace Emby.Server.Implementations.Data return true; } - private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query) + private BaseItem GetItem(IReadOnlyList<ResultSetValue> reader, InternalItemsQuery query) { return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); } - private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) + private BaseItem GetItem(IReadOnlyList<ResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) { var typeString = reader.GetString(0); @@ -1291,27 +1334,30 @@ namespace Emby.Server.Implementations.Data if (queryHasStartDate) { - if (!reader.IsDBNull(index)) + if (item is IHasStartDate hasStartDate && reader.TryReadDateTime(index, out var startDate)) { - if (item is IHasStartDate hasStartDate) - { - hasStartDate.StartDate = reader[index].ReadDateTime(); - } + hasStartDate.StartDate = startDate; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var endDate)) { - item.EndDate = reader[index].TryReadDateTime(); + item.EndDate = endDate; } - index++; - - if (!reader.IsDBNull(index)) + var channelId = reader[index]; + if (!channelId.IsDbNull()) { - item.ChannelId = new Guid(reader.GetString(index)); + if (!Utf8Parser.TryParse(channelId.ToBlob(), out Guid value, out _, standardFormat: 'N')) + { + var str = reader.GetString(index); + Logger.LogWarning("{ChannelId} isn't in the expected format", str); + value = new Guid(str); + } + + item.ChannelId = value; } index++; @@ -1320,33 +1366,25 @@ namespace Emby.Server.Implementations.Data { if (item is IHasProgramAttributes hasProgramAttributes) { - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isMovie)) { - hasProgramAttributes.IsMovie = reader.GetBoolean(index); + hasProgramAttributes.IsMovie = isMovie; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isSeries)) { - hasProgramAttributes.IsSeries = reader.GetBoolean(index); + hasProgramAttributes.IsSeries = isSeries; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var episodeTitle)) { - hasProgramAttributes.EpisodeTitle = reader.GetString(index); + hasProgramAttributes.EpisodeTitle = episodeTitle; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isRepeat)) { - hasProgramAttributes.IsRepeat = reader.GetBoolean(index); + hasProgramAttributes.IsRepeat = isRepeat; } - - index++; } else { @@ -1354,242 +1392,190 @@ namespace Emby.Server.Implementations.Data } } - if (!reader.IsDBNull(index)) + if (reader.TryGetSingle(index++, out var communityRating)) { - item.CommunityRating = reader.GetFloat(index); + item.CommunityRating = communityRating; } - index++; - if (HasField(query, ItemFields.CustomRating)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var customRating)) { - item.CustomRating = reader.GetString(index); + item.CustomRating = customRating; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var indexNumber)) { - item.IndexNumber = reader.GetInt32(index); + item.IndexNumber = indexNumber; } - index++; - if (HasField(query, ItemFields.Settings)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isLocked)) { - item.IsLocked = reader.GetBoolean(index); + item.IsLocked = isLocked; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var preferredMetadataLanguage)) { - item.PreferredMetadataLanguage = reader.GetString(index); + item.PreferredMetadataLanguage = preferredMetadataLanguage; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var preferredMetadataCountryCode)) { - item.PreferredMetadataCountryCode = reader.GetString(index); + item.PreferredMetadataCountryCode = preferredMetadataCountryCode; } - - index++; } if (HasField(query, ItemFields.Width)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var width)) { - item.Width = reader.GetInt32(index); + item.Width = width; } - - index++; } if (HasField(query, ItemFields.Height)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var height)) { - item.Height = reader.GetInt32(index); + item.Height = height; } - - index++; } if (HasField(query, ItemFields.DateLastRefreshed)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) { - item.DateLastRefreshed = reader[index].ReadDateTime(); + item.DateLastRefreshed = dateLastRefreshed; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var name)) { - item.Name = reader.GetString(index); + item.Name = name; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var restorePath)) { - item.Path = RestorePath(reader.GetString(index)); + item.Path = RestorePath(restorePath); } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var premiereDate)) { - item.PremiereDate = reader[index].TryReadDateTime(); + item.PremiereDate = premiereDate; } - index++; - if (HasField(query, ItemFields.Overview)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var overview)) { - item.Overview = reader.GetString(index); + item.Overview = overview; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var parentIndexNumber)) { - item.ParentIndexNumber = reader.GetInt32(index); + item.ParentIndexNumber = parentIndexNumber; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var productionYear)) { - item.ProductionYear = reader.GetInt32(index); + item.ProductionYear = productionYear; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var officialRating)) { - item.OfficialRating = reader.GetString(index); + item.OfficialRating = officialRating; } - index++; - if (HasField(query, ItemFields.SortName)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var forcedSortName)) { - item.ForcedSortName = reader.GetString(index); + item.ForcedSortName = forcedSortName; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt64(index++, out var runTimeTicks)) { - item.RunTimeTicks = reader.GetInt64(index); + item.RunTimeTicks = runTimeTicks; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetInt64(index++, out var size)) { - item.Size = reader.GetInt64(index); + item.Size = size; } - index++; - if (HasField(query, ItemFields.DateCreated)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateCreated)) { - item.DateCreated = reader[index].ReadDateTime(); + item.DateCreated = dateCreated; } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateModified)) { - item.DateModified = reader[index].ReadDateTime(); + item.DateModified = dateModified; } - index++; - - item.Id = reader.GetGuid(index); - index++; + item.Id = reader.GetGuid(index++); if (HasField(query, ItemFields.Genres)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var genres)) { - item.Genres = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Genres = genres.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index++, out var parentId)) { - item.ParentId = reader.GetGuid(index); + item.ParentId = parentId; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var audioString)) { - if (Enum.TryParse(reader.GetString(index), true, out ProgramAudio audio)) + // TODO Span overload coming in the future https://github.com/dotnet/runtime/issues/1916 + if (Enum.TryParse(audioString, true, out ProgramAudio audio)) { item.Audio = audio; } } - index++; - // TODO: Even if not needed by apps, the server needs it internally // But get this excluded from contexts where it is not needed if (hasServiceName) { if (item is LiveTvChannel liveTvChannel) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var serviceName)) { - liveTvChannel.ServiceName = reader.GetString(index); + liveTvChannel.ServiceName = serviceName; } } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isInMixedFolder)) { - item.IsInMixedFolder = reader.GetBoolean(index); + item.IsInMixedFolder = isInMixedFolder; } - index++; - if (HasField(query, ItemFields.DateLastSaved)) { - if (!reader.IsDBNull(index)) + if (reader.TryReadDateTime(index++, out var dateLastSaved)) { - item.DateLastSaved = reader[index].ReadDateTime(); + item.DateLastSaved = dateLastSaved; } - - index++; } if (HasField(query, ItemFields.Settings)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var lockedFields)) { IEnumerable<MetadataField> GetLockedFields(string s) { @@ -1602,37 +1588,31 @@ namespace Emby.Server.Implementations.Data } } - item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray(); + item.LockedFields = GetLockedFields(lockedFields).ToArray(); } - - index++; } if (HasField(query, ItemFields.Studios)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var studios)) { - item.Studios = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Studios = studios.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (HasField(query, ItemFields.Tags)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var tags)) { - item.Tags = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + item.Tags = tags.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (hasTrailerTypes) { if (item is Trailer trailer) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var trailerTypes)) { IEnumerable<TrailerType> GetTrailerTypes(string s) { @@ -1645,7 +1625,7 @@ namespace Emby.Server.Implementations.Data } } - trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray(); + trailer.TrailerTypes = GetTrailerTypes(trailerTypes).ToArray(); } } @@ -1654,19 +1634,17 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.OriginalTitle)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var originalTitle)) { - item.OriginalTitle = reader.GetString(index); + item.OriginalTitle = originalTitle; } - - index++; } if (item is Video video) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var primaryVersionId)) { - video.PrimaryVersionId = reader.GetString(index); + video.PrimaryVersionId = primaryVersionId; } } @@ -1674,40 +1652,34 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.DateLastMediaAdded)) { - if (item is Folder folder && !reader.IsDBNull(index)) + if (item is Folder folder && reader.TryReadDateTime(index, out var dateLastMediaAdded)) { - folder.DateLastMediaAdded = reader[index].TryReadDateTime(); + folder.DateLastMediaAdded = dateLastMediaAdded; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var album)) { - item.Album = reader.GetString(index); + item.Album = album; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetSingle(index++, out var criticRating)) { - item.CriticRating = reader.GetFloat(index); + item.CriticRating = criticRating; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetBoolean(index++, out var isVirtualItem)) { - item.IsVirtualItem = reader.GetBoolean(index); + item.IsVirtualItem = isVirtualItem; } - index++; - if (item is IHasSeries hasSeriesName) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seriesName)) { - hasSeriesName.SeriesName = reader.GetString(index); + hasSeriesName.SeriesName = seriesName; } } @@ -1717,15 +1689,15 @@ namespace Emby.Server.Implementations.Data { if (item is Episode episode) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seasonName)) { - episode.SeasonName = reader.GetString(index); + episode.SeasonName = seasonName; } index++; - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var seasonId)) { - episode.SeasonId = reader.GetGuid(index); + episode.SeasonId = seasonId; } } else @@ -1741,9 +1713,9 @@ namespace Emby.Server.Implementations.Data { if (hasSeries != null) { - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var seriesId)) { - hasSeries.SeriesId = reader.GetGuid(index); + hasSeries.SeriesId = seriesId; } } @@ -1752,56 +1724,48 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.PresentationUniqueKey)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var presentationUniqueKey)) { - item.PresentationUniqueKey = reader.GetString(index); + item.PresentationUniqueKey = presentationUniqueKey; } - - index++; } if (HasField(query, ItemFields.InheritedParentalRatingValue)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var parentalRating)) { - item.InheritedParentalRatingValue = reader.GetInt32(index); + item.InheritedParentalRatingValue = parentalRating; } - - index++; } if (HasField(query, ItemFields.ExternalSeriesId)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var externalSeriesId)) { - item.ExternalSeriesId = reader.GetString(index); + item.ExternalSeriesId = externalSeriesId; } - - index++; } if (HasField(query, ItemFields.Taglines)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var tagLine)) { - item.Tagline = reader.GetString(index); + item.Tagline = tagLine; } - - index++; } - if (!reader.IsDBNull(index)) + if (item.ProviderIds.Count == 0 && reader.TryGetString(index, out var providerIds)) { - DeserializeProviderIds(reader.GetString(index), item); + DeserializeProviderIds(providerIds, item); } index++; if (query.DtoOptions.EnableImages) { - if (!reader.IsDBNull(index)) + if (item.ImageInfos.Length == 0 && reader.TryGetString(index, out var imageInfos)) { - DeserializeImages(reader.GetString(index), item); + item.ImageInfos = DeserializeImages(imageInfos); } index++; @@ -1809,72 +1773,62 @@ namespace Emby.Server.Implementations.Data if (HasField(query, ItemFields.ProductionLocations)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var productionLocations)) { - item.ProductionLocations = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries).ToArray(); + item.ProductionLocations = productionLocations.Split('|', StringSplitOptions.RemoveEmptyEntries); } - - index++; } if (HasField(query, ItemFields.ExtraIds)) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var extraIds)) { - item.ExtraIds = SplitToGuids(reader.GetString(index)); + item.ExtraIds = SplitToGuids(extraIds); } - - index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetInt32(index++, out var totalBitrate)) { - item.TotalBitrate = reader.GetInt32(index); + item.TotalBitrate = totalBitrate; } - index++; - - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var extraTypeString)) { - if (Enum.TryParse(reader.GetString(index), true, out ExtraType extraType)) + if (Enum.TryParse(extraTypeString, true, out ExtraType extraType)) { item.ExtraType = extraType; } } - index++; - if (hasArtistFields) { - if (item is IHasArtist hasArtists && !reader.IsDBNull(index)) + if (item is IHasArtist hasArtists && reader.TryGetString(index, out var artists)) { - hasArtists.Artists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + hasArtists.Artists = artists.Split('|', StringSplitOptions.RemoveEmptyEntries); } index++; - if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index)) + if (item is IHasAlbumArtist hasAlbumArtists && reader.TryGetString(index, out var albumArtists)) { - hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|', StringSplitOptions.RemoveEmptyEntries); + hasAlbumArtists.AlbumArtists = albumArtists.Split('|', StringSplitOptions.RemoveEmptyEntries); } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index++, out var externalId)) { - item.ExternalId = reader.GetString(index); + item.ExternalId = externalId; } - index++; - if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) { if (hasSeries != null) { - if (!reader.IsDBNull(index)) + if (reader.TryGetString(index, out var seriesPresentationUniqueKey)) { - hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); + hasSeries.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; } } @@ -1883,21 +1837,19 @@ namespace Emby.Server.Implementations.Data if (enableProgramAttributes) { - if (item is LiveTvProgram program && !reader.IsDBNull(index)) + if (item is LiveTvProgram program && reader.TryGetString(index, out var showId)) { - program.ShowId = reader.GetString(index); + program.ShowId = showId; } index++; } - if (!reader.IsDBNull(index)) + if (reader.TryGetGuid(index, out var ownerId)) { - item.OwnerId = reader.GetGuid(index); + item.OwnerId = ownerId; } - index++; - return item; } @@ -1977,21 +1929,21 @@ namespace Emby.Server.Implementations.Data /// <param name="reader">The reader.</param> /// <param name="item">The item.</param> /// <returns>ChapterInfo.</returns> - private ChapterInfo GetChapter(IReadOnlyList<IResultSetValue> reader, BaseItem item) + private ChapterInfo GetChapter(IReadOnlyList<ResultSetValue> reader, BaseItem item) { var chapter = new ChapterInfo { StartPositionTicks = reader.GetInt64(0) }; - if (!reader.IsDBNull(1)) + if (reader.TryGetString(1, out var chapterName)) { - chapter.Name = reader.GetString(1); + chapter.Name = chapterName; } - if (!reader.IsDBNull(2)) + if (reader.TryGetString(2, out var imagePath)) { - chapter.ImagePath = reader.GetString(2); + chapter.ImagePath = imagePath; if (!string.IsNullOrEmpty(chapter.ImagePath)) { @@ -2006,9 +1958,9 @@ namespace Emby.Server.Implementations.Data } } - if (!reader.IsDBNull(3)) + if (reader.TryReadDateTime(3, out var imageDateModified)) { - chapter.ImageDateModified = reader[3].ReadDateTime(); + chapter.ImageDateModified = imageDateModified; } return chapter; @@ -2116,30 +2068,7 @@ namespace Emby.Server.Implementations.Data || query.IsLiked.HasValue; } - private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToArray(); - - private string[] GetColumnNamesFromField(ItemFields field) - { - switch (field) - { - case ItemFields.Settings: - return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" }; - case ItemFields.ServiceName: - return new[] { "ExternalServiceId" }; - case ItemFields.SortName: - return new[] { "ForcedSortName" }; - case ItemFields.Taglines: - return new[] { "Tagline" }; - case ItemFields.Tags: - return new[] { "Tags" }; - case ItemFields.IsHD: - return Array.Empty<string>(); - default: - return new[] { field.ToString() }; - } - } + private readonly ItemFields[] _allFields = Enum.GetValues<ItemFields>(); private bool HasField(InternalItemsQuery query, ItemFields name) { @@ -2329,9 +2258,32 @@ namespace Emby.Server.Implementations.Data { if (!HasField(query, field)) { - foreach (var fieldToRemove in GetColumnNamesFromField(field)) + switch (field) { - list.Remove(fieldToRemove); + case ItemFields.Settings: + list.Remove("IsLocked"); + list.Remove("PreferredMetadataCountryCode"); + list.Remove("PreferredMetadataLanguage"); + list.Remove("LockedFields"); + break; + case ItemFields.ServiceName: + list.Remove("ExternalServiceId"); + break; + case ItemFields.SortName: + list.Remove("ForcedSortName"); + break; + case ItemFields.Taglines: + list.Remove("Tagline"); + break; + case ItemFields.Tags: + list.Remove("Tags"); + break; + case ItemFields.IsHD: + // do nothing + break; + default: + list.Remove(field.ToString()); + break; } } } @@ -2578,9 +2530,9 @@ namespace Emby.Server.Implementations.Data } var commandText = "select " - + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) - + GetFromText() - + GetJoinUserDataText(query); + + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + + GetFromText() + + GetJoinUserDataText(query); var whereClauses = GetWhereClauses(query, null); if (whereClauses.Count != 0) @@ -2721,87 +2673,22 @@ namespace Emby.Server.Implementations.Data private string FixUnicodeChars(string buffer) { - if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2013', '-'); // en dash - } - - if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2014', '-'); // em dash - } - - if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2015', '-'); // horizontal bar - } - - if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2017', '_'); // double low line - } - - if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2018', '\''); // left single quotation mark - } - - if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2019', '\''); // right single quotation mark - } - - if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark - } - - if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark - } - - if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark - } - - if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark - } - - if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark - } - - if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis - } - - if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2032', '\''); // prime - } - - if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u2033', '\"'); // double prime - } - - if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u0060', '\''); // grave accent - } - - if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1) - { - buffer = buffer.Replace('\u00B4', '\''); // acute accent - } - - return buffer; + buffer = buffer.Replace('\u2013', '-'); // en dash + buffer = buffer.Replace('\u2014', '-'); // em dash + buffer = buffer.Replace('\u2015', '-'); // horizontal bar + buffer = buffer.Replace('\u2017', '_'); // double low line + buffer = buffer.Replace('\u2018', '\''); // left single quotation mark + buffer = buffer.Replace('\u2019', '\''); // right single quotation mark + buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark + buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark + buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark + buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark + buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark + buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis + buffer = buffer.Replace('\u2032', '\''); // prime + buffer = buffer.Replace('\u2033', '\"'); // double prime + buffer = buffer.Replace('\u0060', '\''); // grave accent + return buffer.Replace('\u00B4', '\''); // acute accent } private void AddItem(List<BaseItem> items, BaseItem newItem) @@ -3245,12 +3132,8 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { var id = row.GetGuid(0); - string path = null; - if (!row.IsDBNull(1)) - { - path = row.GetString(1); - } + row.TryGetString(1, out var path); list.Add(new Tuple<Guid, string>(id, path)); } @@ -3584,11 +3467,11 @@ namespace Emby.Server.Implementations.Data statement?.TryBind("@IsFolder", query.IsFolder); } - var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); + var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); // Only specify excluded types if no included types are specified if (includeTypes.Length == 0) { - var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); + var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray(); if (excludeTypes.Length == 1) { whereClauses.Add("type<>@type"); @@ -4444,7 +4327,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(string.Join(" AND ", excludeIds)); } - if (query.ExcludeProviderIds.Count > 0) + if (query.ExcludeProviderIds != null && query.ExcludeProviderIds.Count > 0) { var excludeIds = new List<string>(); @@ -4474,7 +4357,7 @@ namespace Emby.Server.Implementations.Data } } - if (query.HasAnyProviderId.Count > 0) + if (query.HasAnyProviderId != null && query.HasAnyProviderId.Count > 0) { var hasProviderIds = new List<string>(); @@ -4532,7 +4415,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); + var includedItemByNameTypes = GetItemByNameTypesInQuery(query); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; var queryTopParentIds = query.TopParentIds; @@ -4790,27 +4673,27 @@ namespace Emby.Server.Implementations.Data if (IsTypeInQuery(nameof(Person), query)) { - list.Add(nameof(Person)); + list.Add(typeof(Person).FullName); } if (IsTypeInQuery(nameof(Genre), query)) { - list.Add(nameof(Genre)); + list.Add(typeof(Genre).FullName); } if (IsTypeInQuery(nameof(MusicGenre), query)) { - list.Add(nameof(MusicGenre)); + list.Add(typeof(MusicGenre).FullName); } if (IsTypeInQuery(nameof(MusicArtist), query)) { - list.Add(nameof(MusicArtist)); + list.Add(typeof(MusicArtist).FullName); } if (IsTypeInQuery(nameof(Studio), query)) { - list.Add(nameof(Studio)); + list.Add(typeof(Studio).FullName); } return list; @@ -4915,15 +4798,10 @@ namespace Emby.Server.Implementations.Data typeof(AggregateFolder) }; - public void UpdateInheritedValues(CancellationToken cancellationToken) - { - UpdateInheritedTags(cancellationToken); - } - - private void UpdateInheritedTags(CancellationToken cancellationToken) + public void UpdateInheritedValues() { string sql = string.Join( - ";", + ';', new string[] { "delete from itemvalues where type = 6", @@ -4946,37 +4824,38 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private static Dictionary<string, string[]> GetTypeMapDictionary() + private static Dictionary<string, string> GetTypeMapDictionary() { - var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); + var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (var t in _knownTypes) { - dict[t.Name] = new[] { t.FullName }; + dict[t.Name] = t.FullName ; } - dict["Program"] = new[] { typeof(LiveTvProgram).FullName }; - dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName }; + dict["Program"] = typeof(LiveTvProgram).FullName; + dict["TvChannel"] = typeof(LiveTvChannel).FullName; return dict; } // Not crazy about having this all the way down here, but at least it's in one place - private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary(); + private readonly Dictionary<string, string> _types = GetTypeMapDictionary(); - private string[] MapIncludeItemTypes(string value) + private string MapIncludeItemTypes(string value) { - if (_types.TryGetValue(value, out string[] result)) + if (_types.TryGetValue(value, out string result)) { return result; } if (IsValidType(value)) { - return new[] { value }; + return value; } - return Array.Empty<string>(); + Logger.LogWarning("Unknown item type: {ItemType}", value); + return null; } public void DeleteItem(Guid id) @@ -5279,31 +5158,46 @@ AND Type = @InternalPersonType)"); public List<string> GetStudioNames() { - return GetItemValueNames(new[] { 3 }, new List<string>(), new List<string>()); + return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>()); } public List<string> GetAllArtistNames() { - return GetItemValueNames(new[] { 0, 1 }, new List<string>(), new List<string>()); + return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>()); } public List<string> GetMusicGenreNames() { - return GetItemValueNames(new[] { 2 }, new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List<string>()); + return GetItemValueNames( + new[] { 2 }, + new string[] + { + typeof(Audio).FullName, + typeof(MusicVideo).FullName, + typeof(MusicAlbum).FullName, + typeof(MusicArtist).FullName + }, + Array.Empty<string>()); } public List<string> GetGenreNames() { - return GetItemValueNames(new[] { 2 }, new List<string>(), new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }); + return GetItemValueNames( + new[] { 2 }, + Array.Empty<string>(), + new string[] + { + typeof(Audio).FullName, + typeof(MusicVideo).FullName, + typeof(MusicAlbum).FullName, + typeof(MusicArtist).FullName + }); } - private List<string> GetItemValueNames(int[] itemValueTypes, List<string> withItemTypes, List<string> excludeItemTypes) + private List<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes) { CheckDisposed(); - withItemTypes = withItemTypes.SelectMany(MapIncludeItemTypes).ToList(); - excludeItemTypes = excludeItemTypes.SelectMany(MapIncludeItemTypes).ToList(); - var now = DateTime.UtcNow; var typeClause = itemValueTypes.Length == 1 ? @@ -5333,9 +5227,9 @@ AND Type = @InternalPersonType)"); { foreach (var row in statement.ExecuteQuery()) { - if (!row.IsDBNull(0)) + if (row.TryGetString(0, out var result)) { - list.Add(row.GetString(0)); + list.Add(result); } } } @@ -5415,7 +5309,6 @@ AND Type = @InternalPersonType)"); ItemIds = query.ItemIds, TopParentIds = query.TopParentIds, ParentId = query.ParentId, - IsPlayed = query.IsPlayed, IsAiring = query.IsAiring, IsMovie = query.IsMovie, IsSports = query.IsSports, @@ -5441,6 +5334,7 @@ AND Type = @InternalPersonType)"); var outerQuery = new InternalItemsQuery(query.User) { + IsPlayed = query.IsPlayed, IsFavorite = query.IsFavorite, IsFavoriteOrLiked = query.IsFavoriteOrLiked, IsLiked = query.IsLiked, @@ -5469,7 +5363,9 @@ AND Type = @InternalPersonType)"); commandText += whereText + " group by PresentationUniqueKey"; - if (query.SimilarTo != null || !string.IsNullOrEmpty(query.SearchTerm)) + if (query.OrderBy.Count != 0 + || query.SimilarTo != null + || !string.IsNullOrEmpty(query.SearchTerm)) { commandText += GetOrderByText(query); } @@ -5607,7 +5503,7 @@ AND Type = @InternalPersonType)"); return result; } - private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, string[] typesToCount) + private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, string[] typesToCount) { var counts = new ItemCounts(); @@ -5616,51 +5512,43 @@ AND Type = @InternalPersonType)"); return counts; } - var typeString = reader.IsDBNull(countStartColumn) ? null : reader.GetString(countStartColumn); - - if (string.IsNullOrWhiteSpace(typeString)) + if (!reader.TryGetString(countStartColumn, out var typeString)) { return counts; } - var allTypes = typeString.Split('|', StringSplitOptions.RemoveEmptyEntries) - .ToLookup(x => x); - - foreach (var type in allTypes) + foreach (var typeName in typeString.AsSpan().Split('|')) { - var value = type.Count(); - var typeName = type.Key; - - if (string.Equals(typeName, typeof(Series).FullName, StringComparison.OrdinalIgnoreCase)) + if (typeName.Equals(typeof(Series).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.SeriesCount = value; + counts.SeriesCount++; } - else if (string.Equals(typeName, typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.EpisodeCount = value; + counts.EpisodeCount++; } - else if (string.Equals(typeName, typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.MovieCount = value; + counts.MovieCount++; } - else if (string.Equals(typeName, typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.AlbumCount = value; + counts.AlbumCount++; } - else if (string.Equals(typeName, typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(MusicArtist).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.ArtistCount = value; + counts.ArtistCount++; } - else if (string.Equals(typeName, typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.SongCount = value; + counts.SongCount++; } - else if (string.Equals(typeName, typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) + else if (typeName.Equals(typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) { - counts.TrailerCount = value; + counts.TrailerCount++; } - counts.ItemCount += value; + counts.ItemCount++; } return counts; @@ -5809,7 +5697,10 @@ AND Type = @InternalPersonType)"); var endIndex = Math.Min(people.Count, startIndex + Limit); for (var i = startIndex; i < endIndex; i++) { - insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture)); + insertText.AppendFormat( + CultureInfo.InvariantCulture, + "(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", + i.ToString(CultureInfo.InvariantCulture)); } // Remove last comma @@ -5843,7 +5734,7 @@ AND Type = @InternalPersonType)"); } } - private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader) + private PersonInfo GetPerson(IReadOnlyList<ResultSetValue> reader) { var item = new PersonInfo { @@ -5851,19 +5742,19 @@ AND Type = @InternalPersonType)"); Name = reader.GetString(1) }; - if (!reader.IsDBNull(2)) + if (reader.TryGetString(2, out var role)) { - item.Role = reader.GetString(2); + item.Role = role; } - if (!reader.IsDBNull(3)) + if (reader.TryGetString(3, out var type)) { - item.Type = reader.GetString(3); + item.Type = type; } - if (!reader.IsDBNull(4)) + if (reader.TryGetInt32(4, out var sortOrder)) { - item.SortOrder = reader.GetInt32(4); + item.SortOrder = sortOrder; } return item; @@ -6050,7 +5941,7 @@ AND Type = @InternalPersonType)"); /// </summary> /// <param name="reader">The reader.</param> /// <returns>ChapterInfo.</returns> - private MediaStream GetMediaStream(IReadOnlyList<IResultSetValue> reader) + private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader) { var item = new MediaStream { @@ -6059,150 +5950,150 @@ AND Type = @InternalPersonType)"); item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true); - if (reader[3].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var codec)) { - item.Codec = reader[3].ToString(); + item.Codec = codec; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var language)) { - item.Language = reader[4].ToString(); + item.Language = language; } - if (reader[5].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(5, out var channelLayout)) { - item.ChannelLayout = reader[5].ToString(); + item.ChannelLayout = channelLayout; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var profile)) { - item.Profile = reader[6].ToString(); + item.Profile = profile; } - if (reader[7].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(7, out var aspectRatio)) { - item.AspectRatio = reader[7].ToString(); + item.AspectRatio = aspectRatio; } - if (reader[8].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(8, out var path)) { - item.Path = RestorePath(reader[8].ToString()); + item.Path = RestorePath(path); } item.IsInterlaced = reader.GetBoolean(9); - if (reader[10].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(10, out var bitrate)) { - item.BitRate = reader.GetInt32(10); + item.BitRate = bitrate; } - if (reader[11].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(11, out var channels)) { - item.Channels = reader.GetInt32(11); + item.Channels = channels; } - if (reader[12].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(12, out var sampleRate)) { - item.SampleRate = reader.GetInt32(12); + item.SampleRate = sampleRate; } item.IsDefault = reader.GetBoolean(13); item.IsForced = reader.GetBoolean(14); item.IsExternal = reader.GetBoolean(15); - if (reader[16].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(16, out var width)) { - item.Width = reader.GetInt32(16); + item.Width = width; } - if (reader[17].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(17, out var height)) { - item.Height = reader.GetInt32(17); + item.Height = height; } - if (reader[18].SQLiteType != SQLiteType.Null) + if (reader.TryGetSingle(18, out var averageFrameRate)) { - item.AverageFrameRate = reader.GetFloat(18); + item.AverageFrameRate = averageFrameRate; } - if (reader[19].SQLiteType != SQLiteType.Null) + if (reader.TryGetSingle(19, out var realFrameRate)) { - item.RealFrameRate = reader.GetFloat(19); + item.RealFrameRate = realFrameRate; } - if (reader[20].SQLiteType != SQLiteType.Null) + if (reader.TryGetSingle(20, out var level)) { - item.Level = reader.GetFloat(20); + item.Level = level; } - if (reader[21].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(21, out var pixelFormat)) { - item.PixelFormat = reader[21].ToString(); + item.PixelFormat = pixelFormat; } - if (reader[22].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(22, out var bitDepth)) { - item.BitDepth = reader.GetInt32(22); + item.BitDepth = bitDepth; } - if (reader[23].SQLiteType != SQLiteType.Null) + if (reader.TryGetBoolean(23, out var isAnamorphic)) { - item.IsAnamorphic = reader.GetBoolean(23); + item.IsAnamorphic = isAnamorphic; } - if (reader[24].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(24, out var refFrames)) { - item.RefFrames = reader.GetInt32(24); + item.RefFrames = refFrames; } - if (reader[25].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(25, out var codecTag)) { - item.CodecTag = reader.GetString(25); + item.CodecTag = codecTag; } - if (reader[26].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(26, out var comment)) { - item.Comment = reader.GetString(26); + item.Comment = comment; } - if (reader[27].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(27, out var nalLengthSize)) { - item.NalLengthSize = reader.GetString(27); + item.NalLengthSize = nalLengthSize; } - if (reader[28].SQLiteType != SQLiteType.Null) + if (reader.TryGetBoolean(28, out var isAVC)) { - item.IsAVC = reader[28].ToBool(); + item.IsAVC = isAVC; } - if (reader[29].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(29, out var title)) { - item.Title = reader[29].ToString(); + item.Title = title; } - if (reader[30].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(30, out var timeBase)) { - item.TimeBase = reader[30].ToString(); + item.TimeBase = timeBase; } - if (reader[31].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(31, out var codecTimeBase)) { - item.CodecTimeBase = reader[31].ToString(); + item.CodecTimeBase = codecTimeBase; } - if (reader[32].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(32, out var colorPrimaries)) { - item.ColorPrimaries = reader[32].ToString(); + item.ColorPrimaries = colorPrimaries; } - if (reader[33].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(33, out var colorSpace)) { - item.ColorSpace = reader[33].ToString(); + item.ColorSpace = colorSpace; } - if (reader[34].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(34, out var colorTransfer)) { - item.ColorTransfer = reader[34].ToString(); + item.ColorTransfer = colorTransfer; } if (item.Type == MediaStreamType.Subtitle) @@ -6261,7 +6152,7 @@ AND Type = @InternalPersonType)"); CheckDisposed(); if (id == Guid.Empty) { - throw new ArgumentException(nameof(id)); + throw new ArgumentException("Guid can't be empty.", nameof(id)); } if (attachments == null) @@ -6351,36 +6242,36 @@ AND Type = @InternalPersonType)"); /// </summary> /// <param name="reader">The reader.</param> /// <returns>MediaAttachment.</returns> - private MediaAttachment GetMediaAttachment(IReadOnlyList<IResultSetValue> reader) + private MediaAttachment GetMediaAttachment(IReadOnlyList<ResultSetValue> reader) { var item = new MediaAttachment { Index = reader[1].ToInt() }; - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(2, out var codec)) { - item.Codec = reader[2].ToString(); + item.Codec = codec; } - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var codecTag)) { - item.CodecTag = reader[3].ToString(); + item.CodecTag = codecTag; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var comment)) { - item.Comment = reader[4].ToString(); + item.Comment = comment; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(5, out var fileName)) { - item.FileName = reader[5].ToString(); + item.FileName = fileName; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var mimeType)) { - item.MimeType = reader[6].ToString(); + item.MimeType = mimeType; } return item; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 6574db607..ef9af1dcd 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -348,16 +350,16 @@ namespace Emby.Server.Implementations.Data /// Read a row from the specified reader into the provided userData object. /// </summary> /// <param name="reader"></param> - private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader) + private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader) { var userData = new UserItemData(); userData.Key = reader[0].ToString(); // userData.UserId = reader[1].ReadGuidFromBlob(); - if (reader[2].SQLiteType != SQLiteType.Null) + if (reader.TryGetDouble(2, out var rating)) { - userData.Rating = reader[2].ToDouble(); + userData.Rating = rating; } userData.Played = reader[3].ToBool(); @@ -365,19 +367,19 @@ namespace Emby.Server.Implementations.Data userData.IsFavorite = reader[5].ToBool(); userData.PlaybackPositionTicks = reader[6].ToInt64(); - if (reader[7].SQLiteType != SQLiteType.Null) + if (reader.TryReadDateTime(7, out var lastPlayedDate)) { - userData.LastPlayedDate = reader[7].TryReadDateTime(); + userData.LastPlayedDate = lastPlayedDate; } - if (reader[8].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(8, out var audioStreamIndex)) { - userData.AudioStreamIndex = reader[8].ToInt(); + userData.AudioStreamIndex = audioStreamIndex; } - if (reader[9].SQLiteType != SQLiteType.Null) + if (reader.TryGetInt32(9, out var subtitleStreamIndex)) { - userData.SubtitleStreamIndex = reader[9].ToInt(); + userData.SubtitleStreamIndex = subtitleStreamIndex; } return userData; diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs index 7044b1d19..7f1306d15 100644 --- a/Emby.Server.Implementations/Data/TypeMapper.cs +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data /// This holds all the types in the running assemblies /// so that we can de-serialize properly when we don't have strong types. /// </summary> - private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>(); + private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>(); /// <summary> /// Gets the type. @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data /// <param name="typeName">Name of the type.</param> /// <returns>Type.</returns> /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception> - public Type GetType(string typeName) + public Type? GetType(string typeName) { if (string.IsNullOrEmpty(typeName)) { @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data /// </summary> /// <param name="typeName">Name of the type.</param> /// <returns>Type.</returns> - private Type LookupType(string typeName) + private Type? LookupType(string typeName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index fa6ac95fd..3d15b3e76 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index da5047d24..2637addce 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 54b18a8c8..7411239a1 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -665,10 +667,7 @@ namespace Emby.Server.Implementations.Dto var tag = GetImageCacheTag(item, image); if (!string.IsNullOrEmpty(image.BlurHash)) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); - } + dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); if (!dto.ImageBlurHashes.ContainsKey(image.Type)) { @@ -702,10 +701,7 @@ namespace Emby.Server.Implementations.Dto if (hashes.Count > 0) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); - } + dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); dto.ImageBlurHashes[imageType] = hashes; } @@ -898,10 +894,7 @@ namespace Emby.Server.Implementations.Dto dto.Taglines = new string[] { item.Tagline }; } - if (dto.Taglines == null) - { - dto.Taglines = Array.Empty<string>(); - } + dto.Taglines ??= Array.Empty<string>(); } dto.Type = item.GetBaseItemKind(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index be552ef93..57e040338 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -9,6 +9,7 @@ <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" /> <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" /> + <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> @@ -27,11 +28,11 @@ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" /> <PackageReference Include="Mono.Nat" Version="3.0.1" /> - <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" /> - <PackageReference Include="sharpcompress" Version="0.28.1" /> - <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> + <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" /> + <PackageReference Include="sharpcompress" Version="0.28.2" /> + <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.0.1" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" /> </ItemGroup> @@ -44,8 +45,11 @@ <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 --> <NoWarn>AD0001</NoWarn> + <AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> </PropertyGroup> <!-- Code Analyzers--> @@ -55,10 +59,6 @@ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> - <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> - <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <EmbeddedResource Include="Localization\iso6392.txt" /> <EmbeddedResource Include="Localization\countries.json" /> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 14201ead2..cc3e4a2c2 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index ae1b51b4c..5bb4100ba 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 824bb85f4..e0ca02d98 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 3624e079f..211941f44 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Net.Sockets; using System.Threading; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1989e9ed2..332fb3385 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4a0fc8239..9afabf527 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 024404ceb..c87f7dbbd 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) { - return (AuthorizationInfo)cached; + return (AuthorizationInfo)cached!; // Cache should never contain null } return GetAuthorization(requestContext); @@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security } private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary<string, string> auth, + in Dictionary<string, string>? auth, in IHeaderDictionary headers, in IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? device = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Request.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// <summary> @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security auth = httpReq.Headers[HeaderNames.Authorization]; } - return GetAuthorization(auth); + return GetAuthorization(auth.Count > 0 ? auth[0] : null); } /// <summary> @@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="authorizationHeader">The authorization header.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorization(string authorizationHeader) + private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader) { if (authorizationHeader == null) { return null; } - var parts = authorizationHeader.Split(' ', 2); + var firstSpace = authorizationHeader.IndexOf(' '); - // There should be at least to parts - if (parts.Length != 2) + // There should be at least two parts + if (firstSpace == -1) { return null; } - var acceptedNames = new[] { "MediaBrowser", "Emby" }; + var name = authorizationHeader[..firstSpace]; - // It has to be a digest request - if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) + if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase) + && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase)) { return null; } - // Remove uptil the first space - authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); + authorizationHeader = authorizationHeader[(firstSpace + 1)..]; var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - foreach (var item in parts) + foreach (var item in authorizationHeader.Split(',')) { - var param = item.Trim().Split('=', 2); + var trimmedItem = item.Trim(); + var firstEqualsSign = trimmedItem.IndexOf('='); - if (param.Length == 2) + if (firstEqualsSign > 0) { - var value = NormalizeValue(param[1].Trim('"')); - result[param[0]] = value; + var key = trimmedItem[..firstEqualsSign].ToString(); + var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString()); + result[key] = value; } } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 040b6b9e4..c375f36ce 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.HttpServer.Security var authorization = _authContext.GetAuthorizationInfo(requestContext); var user = authorization.User; - return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp(), user); + return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user); } public SessionInfo GetSession(object requestContext) @@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public User? GetUser(HttpContext requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public User? GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb5606..8f7d60669 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Buffers; using System.IO.Pipelines; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index d6cf6233e..861c0a95e 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -14,15 +16,18 @@ namespace Emby.Server.Implementations.HttpServer public class WebSocketManager : IWebSocketManager { private readonly IWebSocketListener[] _webSocketListeners; + private readonly IAuthService _authService; private readonly ILogger<WebSocketManager> _logger; private readonly ILoggerFactory _loggerFactory; public WebSocketManager( + IAuthService authService, IEnumerable<IWebSocketListener> webSocketListeners, ILogger<WebSocketManager> logger, ILoggerFactory loggerFactory) { _webSocketListeners = webSocketListeners.ToArray(); + _authService = authService; _logger = logger; _loggerFactory = loggerFactory; } @@ -30,6 +35,7 @@ namespace Emby.Server.Implementations.HttpServer /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { + _ = _authService.Authenticate(context.Request); try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 7435e9d0b..47a83d77c 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3353fae9d..aa80bccd7 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index c0e757543..6a554e68a 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Text; +using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; @@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly string _tempPath; - private readonly bool _isEnvironmentCaseInsensitive; + private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public ManagedFileSystem( ILogger<ManagedFileSystem> logger, @@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO { Logger = logger; _tempPath = applicationPaths.TempDirectory; - - _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; } public virtual void AddShortcutHandler(IShortcutHandler handler) @@ -64,7 +61,7 @@ namespace Emby.Server.Implementations.IO /// <param name="filename">The filename.</param> /// <returns>System.String.</returns> /// <exception cref="ArgumentNullException">filename</exception> - public virtual string ResolveShortcut(string filename) + public virtual string? ResolveShortcut(string filename) { if (string.IsNullOrEmpty(filename)) { @@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO } var extension = Path.GetExtension(filename); - var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); + var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); return handler?.Resolve(filename); } @@ -249,13 +246,20 @@ namespace Emby.Server.Implementations.IO // Issue #2354 get the size of files behind symbolic links if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)) { - using (Stream thisFileStream = File.OpenRead(fileInfo.FullName)) + try { - result.Length = thisFileStream.Length; + using (Stream thisFileStream = File.OpenRead(fileInfo.FullName)) + { + result.Length = thisFileStream.Length; + } + } + catch (FileNotFoundException ex) + { + // Dangling symlinks cannot be detected before opening the file unfortunately... + Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName); + result.Exists = false; } } - - result.DirectoryName = fileInfo.DirectoryName; } result.CreationTimeUtc = GetCreationTimeUtc(info); @@ -294,16 +298,37 @@ namespace Emby.Server.Implementations.IO /// <param name="filename">The filename.</param> /// <returns>System.String.</returns> /// <exception cref="ArgumentNullException">The filename is null.</exception> - public virtual string GetValidFilename(string filename) + public string GetValidFilename(string filename) { - var builder = new StringBuilder(filename); - - foreach (var c in Path.GetInvalidFileNameChars()) + var invalid = Path.GetInvalidFileNameChars(); + var first = filename.IndexOfAny(invalid); + if (first == -1) { - builder = builder.Replace(c, ' '); + // Fast path for clean strings + return filename; } - return builder.ToString(); + return string.Create( + filename.Length, + (filename, invalid, first), + (chars, state) => + { + state.filename.AsSpan().CopyTo(chars); + + chars[state.first++] = ' '; + + var len = chars.Length; + foreach (var c in state.invalid) + { + for (int i = state.first; i < len; i++) + { + if (chars[i] == c) + { + chars[i] = ' '; + } + } + } + }); } /// <summary> @@ -487,26 +512,9 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(path)); } - var separatorChar = Path.DirectorySeparatorChar; - - return path.IndexOf(parentPath.TrimEnd(separatorChar) + separatorChar, StringComparison.OrdinalIgnoreCase) != -1; - } - - public virtual bool IsRootPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var parent = Path.GetDirectoryName(path); - - if (!string.IsNullOrEmpty(parent)) - { - return false; - } - - return true; + return path.Contains( + Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar, + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } public virtual string NormalizePath(string path) @@ -521,7 +529,7 @@ namespace Emby.Server.Implementations.IO return path; } - return path.TrimEnd(Path.DirectorySeparatorChar); + return Path.TrimEndingDirectorySeparator(path); } public virtual bool AreEqual(string path1, string path2) @@ -536,7 +544,10 @@ namespace Emby.Server.Implementations.IO return false; } - return string.Equals(NormalizePath(path1), NormalizePath(path2), StringComparison.OrdinalIgnoreCase); + return string.Equals( + NormalizePath(path1), + NormalizePath(path2), + _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } public virtual string GetFileNameWithoutExtension(FileSystemMetadata info) @@ -590,7 +601,7 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); @@ -644,7 +655,7 @@ namespace Emby.Server.Implementations.IO return GetFilePaths(path, null, false, recursive); } - public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); @@ -689,20 +700,5 @@ namespace Emby.Server.Implementations.IO AttributesToSkip = 0 }; } - - private static void RunProcess(string path, string args, string workingDirectory) - { - using (var process = Process.Start(new ProcessStartInfo - { - Arguments = args, - FileName = path, - CreateNoWindow = true, - WorkingDirectory = workingDirectory, - WindowStyle = ProcessWindowStyle.Normal - })) - { - process.WaitForExit(); - } - } } } diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index e6696b8c4..76c58d5dc 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO public string Extension => ".mblink"; - public string Resolve(string shortcutPath) + public string? Resolve(string shortcutPath) { if (string.IsNullOrEmpty(shortcutPath)) { diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index c16ebd61b..e4f5f4cf0 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { - public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken) { byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); try diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 0b823ff06..a430b9e72 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,6 +1,4 @@ #pragma warning disable CS1591 -#nullable enable -using System; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs index afa4ec7b1..e96b64595 100644 --- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs +++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs @@ -2,20 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Images { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 5f7e51858..833fb0b7a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -191,7 +193,7 @@ namespace Emby.Server.Implementations.Images InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray() }; - if (options.InputPaths.Length == 0) + if (options.InputPaths.Count == 0) { return null; } diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 161b4c452..ff5f26ce0 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 462eb03a8..900b3fd9c 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -1,10 +1,11 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index 0224ab32a..859017f86 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 381788231..6da431c68 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs index 0ce1b91e8..b8f0f0d65 100644 --- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; @@ -29,9 +31,7 @@ namespace Emby.Server.Implementations.Images { var subItem = i.Item2; - 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/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 236453e80..6c65b5899 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index e30a67593..5384c04b3 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Linq; using DotNet.Globbing; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 46a7feb7f..f8d8197d4 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -48,6 +50,7 @@ using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; using VideoResolver = Emby.Naming.Video.VideoResolver; @@ -175,10 +178,7 @@ namespace Emby.Server.Implementations.Library { lock (_rootFolderSyncLock) { - if (_rootFolder == null) - { - _rootFolder = CreateRootFolder(); - } + _rootFolder ??= CreateRootFolder(); } } @@ -196,33 +196,33 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the postscan tasks. /// </summary> /// <value>The postscan tasks.</value> - private ILibraryPostScanTask[] PostscanTasks { get; set; } + private ILibraryPostScanTask[] PostscanTasks { get; set; } = Array.Empty<ILibraryPostScanTask>(); /// <summary> /// Gets or sets the intro providers. /// </summary> /// <value>The intro providers.</value> - private IIntroProvider[] IntroProviders { get; set; } + private IIntroProvider[] IntroProviders { get; set; } = Array.Empty<IIntroProvider>(); /// <summary> /// Gets or sets the list of entity resolution ignore rules. /// </summary> /// <value>The entity resolution ignore rules.</value> - private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } + private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } = Array.Empty<IResolverIgnoreRule>(); /// <summary> /// Gets or sets the list of currently registered entity resolvers. /// </summary> /// <value>The entity resolvers enumerable.</value> - private IItemResolver[] EntityResolvers { get; set; } + private IItemResolver[] EntityResolvers { get; set; } = Array.Empty<IItemResolver>(); - private IMultiItemResolver[] MultiItemResolvers { get; set; } + private IMultiItemResolver[] MultiItemResolvers { get; set; } = Array.Empty<IMultiItemResolver>(); /// <summary> /// Gets or sets the comparers. /// </summary> /// <value>The comparers.</value> - private IBaseItemComparer[] Comparers { get; set; } + private IBaseItemComparer[] Comparers { get; set; } = Array.Empty<IBaseItemComparer>(); public bool IsScanRunning { get; private set; } @@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) { Parent = parent, - Path = fullPath, FileInfo = fileInfo, CollectionType = collectionType, LibraryOptions = libraryOptions @@ -684,7 +683,7 @@ namespace Emby.Server.Implementations.Library foreach (var item in items) { - ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); + ResolverHelper.SetInitialItemValues(item, parent, this, directoryService); } items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); @@ -1163,7 +1162,7 @@ namespace Emby.Server.Implementations.Library progress.Report(percent * 100); } - _itemRepository.UpdateInheritedValues(cancellationToken); + _itemRepository.UpdateInheritedValues(); progress.Report(100); } @@ -1914,12 +1913,17 @@ namespace Emby.Server.Implementations.Library } catch (ArgumentException) { - _logger.LogWarning("Cannot get image index for {0}", img.Path); + _logger.LogWarning("Cannot get image index for {ImagePath}", img.Path); + continue; + } + catch (Exception ex) when (ex is InvalidOperationException || ex is IOException) + { + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path); continue; } - catch (InvalidOperationException) + catch (HttpRequestException ex) { - _logger.LogWarning("Cannot fetch image from {0}", img.Path); + _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode); continue; } } @@ -1932,7 +1936,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path); + _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); image.Width = 0; image.Height = 0; continue; @@ -1944,7 +1948,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path); + _logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path); image.BlurHash = string.Empty; } @@ -1954,7 +1958,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path); + _logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path); } } @@ -2512,7 +2516,7 @@ namespace Emby.Server.Implementations.Library public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) { var series = episode.Series; - bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); + bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); if (!isAbsoluteNaming.Value) { // In other words, no filter applied @@ -2524,9 +2528,23 @@ namespace Emby.Server.Implementations.Library var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; // TODO nullable - what are we trying to do there with empty episodeInfo? - var episodeInfo = episode.IsFileProtocol - ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) - : new Naming.TV.EpisodeInfo(episode.Path); + EpisodeInfo episodeInfo = null; + if (episode.IsFileProtocol) + { + episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); + // Resolve from parent folder if it's not the Season folder + if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder)) + { + episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming); + if (episodeInfo != null) + { + // add the container + episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.'); + } + } + } + + episodeInfo ??= new EpisodeInfo(episode.Path); try { @@ -2876,12 +2894,20 @@ namespace Emby.Server.Implementations.Library public void UpdatePeople(BaseItem item, List<PersonInfo> people) { + UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult(); + } + + /// <inheritdoc /> + public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken) + { if (!item.SupportsPeople) { return; } _itemRepository.UpdatePeople(item.Id, people); + + await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false); } public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) @@ -2985,6 +3011,58 @@ namespace Emby.Server.Implementations.Library } } + private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) + { + var personsToSave = new List<BaseItem>(); + + foreach (var person in people) + { + cancellationToken.ThrowIfCancellationRequested(); + + var itemUpdateType = ItemUpdateType.MetadataDownload; + var saveEntity = false; + var personEntity = GetPerson(person.Name); + + // if PresentationUniqueKey is empty it's likely a new item. + if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey)) + { + personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey(); + saveEntity = true; + } + + foreach (var id in person.ProviderIds) + { + if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) + { + personEntity.SetProviderId(id.Key, id.Value); + saveEntity = true; + } + } + + if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) + { + personEntity.SetImage( + new ItemImageInfo + { + Path = person.ImageUrl, + Type = ImageType.Primary + }, + 0); + + saveEntity = true; + itemUpdateType = ItemUpdateType.ImageUpdate; + } + + if (saveEntity) + { + personsToSave.Add(personEntity); + await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + } + } + + CreateItems(personsToSave, null, CancellationToken.None); + } + private void StartScanInBackground() { Task.Run(() => diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c2951dd15..4ef7923db 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index b2943020c..38e81d14c 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -199,10 +201,15 @@ namespace Emby.Server.Implementations.Library { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } + else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); + source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); + } } } - return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); + return SortMediaSources(list); } public MediaProtocol GetPathProtocol(string path) @@ -436,7 +443,7 @@ namespace Emby.Server.Implementations.Library } } - private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) + private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) { return sources.OrderBy(i => { @@ -451,8 +458,9 @@ namespace Emby.Server.Implementations.Library { var stream = i.VideoStream; - return stream == null || stream.Width == null ? 0 : stream.Width.Value; + return stream?.Width ?? 0; }) + .Where(i => i.Type != MediaSourceType.Placeholder) .ToList(); } @@ -584,18 +592,9 @@ namespace Emby.Server.Implementations.Library public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) { - var info = _openStreams.Values.FirstOrDefault(i => - { - var liveStream = i as ILiveStream; - if (liveStream != null) - { - return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); - } - - return false; - }); + var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase)); - return Task.FromResult(info as IDirectStreamProvider); + return Task.FromResult(info.Value as IDirectStreamProvider); } public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 28fa06239..b833122ea 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 658c53f28..06300adeb 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -100,8 +102,7 @@ namespace Emby.Server.Implementations.Library public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) { - var genre = item as MusicGenre; - if (genre != null) + if (item is MusicGenre genre) { return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); } diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 57d0c26b9..86b8039fa 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,9 +1,6 @@ -#nullable enable - using System; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text.RegularExpressions; +using MediaBrowser.Common.Providers; namespace Emby.Server.Implementations.Library { @@ -43,8 +40,8 @@ namespace Emby.Server.Implementations.Library // for imdbid we also accept pattern matching if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase)) { - var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase); - return m.Success ? m.Value : null; + var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId); + return match ? imdbId.ToString() : null; } return null; @@ -97,8 +94,14 @@ namespace Emby.Server.Implementations.Library // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // when the sub path matches a similar but in-complete subpath var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; - if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase) - || (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar)) + if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (path.Length > subPath.Length + && !oldSubPathEndsWithSeparator + && path[subPath.Length] != newDirectorySeparatorChar) { return false; } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 4e4cac75b..ac75e5d3a 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -18,11 +18,10 @@ namespace Emby.Server.Implementations.Library /// </summary> /// <param name="item">The item.</param> /// <param name="parent">The parent.</param> - /// <param name="fileSystem">The file system.</param> /// <param name="libraryManager">The library manager.</param> /// <param name="directoryService">The directory service.</param> - /// <exception cref="ArgumentException">Item must have a path</exception> - public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) + /// <exception cref="ArgumentException">Item must have a path.</exception> + public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService) { // This version of the below method has no ItemResolveArgs, so we have to require the path already being set if (string.IsNullOrEmpty(item.Path)) @@ -43,9 +42,14 @@ namespace Emby.Server.Implementations.Library // Make sure DateCreated and DateModified have values var fileInfo = directoryService.GetFile(item.Path); - SetDateCreated(item, fileSystem, fileInfo); + if (fileInfo == null) + { + throw new FileNotFoundException("Can't find item path.", item.Path); + } - EnsureName(item, item.Path, fileInfo); + SetDateCreated(item, fileInfo); + + EnsureName(item, fileInfo); } /// <summary> @@ -72,9 +76,9 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); // Make sure the item has a name - EnsureName(item, item.Path, args.FileInfo); + EnsureName(item, args.FileInfo); - item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || item.GetParents().Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values @@ -84,29 +88,16 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Ensures the name. /// </summary> - private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) + private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo) { // If the subclass didn't supply a name, add it here - if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) + if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path)) { - var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; - - item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory); + item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name); } } /// <summary> - /// Gets the display name. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="isDirectory">if set to <c>true</c> [is directory].</param> - /// <returns>System.String.</returns> - private static string GetDisplayName(string path, bool isDirectory) - { - return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); - } - - /// <summary> /// Ensures DateCreated and DateModified have values. /// </summary> /// <param name="fileSystem">The file system.</param> @@ -114,21 +105,6 @@ namespace Emby.Server.Implementations.Library /// <param name="args">The args.</param> private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) { - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } - - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - // See if a different path came out of the resolver than what went in if (!fileSystem.AreEqual(args.Path, item.Path)) { @@ -136,7 +112,7 @@ namespace Emby.Server.Implementations.Library if (childData != null) { - SetDateCreated(item, fileSystem, childData); + SetDateCreated(item, childData); } else { @@ -144,17 +120,17 @@ namespace Emby.Server.Implementations.Library if (fileData.Exists) { - SetDateCreated(item, fileSystem, fileData); + SetDateCreated(item, fileData); } } } else { - SetDateCreated(item, fileSystem, args.FileInfo); + SetDateCreated(item, args.FileInfo); } } - private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) + private static void SetDateCreated(BaseItem item, FileSystemMetadata? info) { var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); @@ -163,7 +139,7 @@ namespace Emby.Server.Implementations.Library // directoryService.getFile may return null if (info != null) { - var dateCreated = fileSystem.GetCreationTimeUtc(info); + var dateCreated = info.CreationTimeUtc; if (dateCreated.Equals(DateTime.MinValue)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 90b6a8a7d..e893d6335 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -201,6 +203,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio continue; } + if (resolvedItem.Files.Count == 0) + { + continue; + } + var firstMedia = resolvedItem.Files[0]; var libraryItem = new T diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index bf32381eb..8e1eccb10 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 60f82806f..3d2ae95d2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 6e688693b..a3dcdc944 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -165,13 +167,13 @@ namespace Emby.Server.Implementations.Library.Resolvers protected void SetVideoType(Video video, VideoFileInfo videoInfo) { - var extension = Path.GetExtension(video.Path); - video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) || - string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ? - VideoType.Iso : - VideoType.VideoFile; + var extension = Path.GetExtension(video.Path.AsSpan()); + video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".img", StringComparison.OrdinalIgnoreCase) + ? VideoType.Iso + : VideoType.VideoFile; - video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); + video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase); video.IsPlaceHolder = videoInfo.IsStub; if (videoInfo.IsStub) @@ -193,11 +195,11 @@ namespace Emby.Server.Implementations.Library.Resolvers { if (video.VideoType == VideoType.Iso) { - if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1) + if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase)) { video.IsoType = IsoType.Dvd; } - else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1) + else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase)) { video.IsoType = IsoType.BluRay; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0525c7e30..68076730b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs index 7dbce7a6e..7aaee017d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 9ca76095b..fa45ccf84 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; @@ -12,6 +14,12 @@ namespace Emby.Server.Implementations.Library.Resolvers where T : BaseItem, new() { /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public virtual ResolverPriority Priority => ResolverPriority.First; + + /// <summary> /// Resolves the specified args. /// </summary> /// <param name="args">The args.</param> @@ -22,12 +30,6 @@ namespace Emby.Server.Implementations.Library.Resolvers } /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public virtual ResolverPriority Priority => ResolverPriority.First; - - /// <summary> /// Sets initial values on the newly resolved item. /// </summary> /// <param name="item">The item.</param> diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 295e9e120..69d71d0d9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 714bc3a84..02c528764 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -376,7 +378,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies { var multiDiscFolders = new List<FileSystemMetadata>(); - var libraryOptions = args.GetLibraryOptions(); + var libraryOptions = args.LibraryOptions; var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos; var photos = new List<FileSystemMetadata>(); diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 3ac837057..534bc80dd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -13,7 +15,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public class PhotoAlbumResolver : FolderResolver<PhotoAlbum> { private readonly IImageProcessor _imageProcessor; - private ILibraryManager _libraryManager; + private readonly ILibraryManager _libraryManager; /// <summary> /// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class. @@ -26,6 +28,9 @@ namespace Emby.Server.Implementations.Library.Resolvers _libraryManager = libraryManager; } + /// <inheritdoc /> + public override ResolverPriority Priority => ResolverPriority.Second; + /// <summary> /// Resolves the specified args. /// </summary> @@ -39,8 +44,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); - if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || - (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos)) { if (HasPhotos(args)) { @@ -84,8 +89,5 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - - /// <inheritdoc /> - public override ResolverPriority Priority => ResolverPriority.Second; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index bcfcee9c6..57bf40e9e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -47,7 +49,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var collectionType = args.CollectionType; if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) - || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) + || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index c76d41e5c..ecd44be47 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -63,7 +65,8 @@ namespace Emby.Server.Implementations.Library.Resolvers { Path = args.Path, Name = Path.GetFileNameWithoutExtension(args.Path), - IsInMixedFolder = true + IsInMixedFolder = true, + PlaylistMediaType = MediaType.Audio }; } } diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 99f304190..7b4e14334 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 9b4cd7a3d..d6ae91056 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Controller.Entities; @@ -35,14 +37,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - var season = parent as Season; - // Just in case the user decided to nest episodes. // Not officially supported but in some cases we can handle it. - if (season == null) - { - season = parent.GetParents().OfType<Season>().FirstOrDefault(); - } + + var season = parent as Season ?? parent.GetParents().OfType<Season>().FirstOrDefault(); // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders @@ -55,11 +53,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if (episode != null) { - var series = parent as Series; - if (series == null) - { - series = parent.GetParents().OfType<Series>().FirstOrDefault(); - } + var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); if (series != null) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 3332e1806..7d707df18 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Globalization; using Emby.Naming.TV; using MediaBrowser.Controller.Entities.TV; @@ -88,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV CultureInfo.InvariantCulture, _localization.GetLocalizedString("NameSeasonNumber"), seasonNumber, - args.GetLibraryOptions().PreferredMetadataLanguage); + args.LibraryOptions.PreferredMetadataLanguage); } return season; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 732bfd94d..a1562abd3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -19,19 +21,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// </summary> public class SeriesResolver : FolderResolver<Series> { - private readonly IFileSystem _fileSystem; private readonly ILogger<SeriesResolver> _logger; private readonly ILibraryManager _libraryManager; /// <summary> /// Initializes a new instance of the <see cref="SeriesResolver"/> class. /// </summary> - /// <param name="fileSystem">The file system.</param> /// <param name="logger">The logger.</param> /// <param name="libraryManager">The library manager.</param> - public SeriesResolver(IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager) + public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager) { - _fileSystem = fileSystem; _logger = logger; _libraryManager = libraryManager; } @@ -59,15 +58,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - // if (args.ContainsFileSystemEntryByName("tvshow.nfo")) - //{ - // return new Series - // { - // Path = args.Path, - // Name = Path.GetFileName(args.Path) - // }; - //} - var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { @@ -100,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false)) { return new Series { @@ -117,8 +107,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV public static bool IsSeriesFolder( string path, IEnumerable<FileSystemMetadata> fileSystemChildren, - IDirectoryService directoryService, - IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager, bool isTvContentType) @@ -127,7 +115,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { if (child.IsDirectory) { - if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager)) + if (IsSeasonFolder(child.FullName, isTvContentType)) { logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName); return true; @@ -161,31 +149,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV } /// <summary> - /// Determines whether [is place holder] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <returns><c>true</c> if [is place holder] [the specified path]; otherwise, <c>false</c>.</returns> - /// <exception cref="ArgumentNullException">path</exception> - private static bool IsVideoPlaceHolder(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var extension = Path.GetExtension(path); - - return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase); - } - - /// <summary> /// Determines whether [is season folder] [the specified path]. /// </summary> /// <param name="path">The path.</param> /// <param name="isTvContentType">if set to <c>true</c> [is tv content type].</param> - /// <param name="libraryManager">The library manager.</param> /// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns> - private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager) + private static bool IsSeasonFolder(string path, bool isTvContentType) { var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber; diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce9..9599faea4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 94602582b..26e615fa0 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -12,7 +14,6 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; -using Microsoft.Extensions.Logging; using Genre = MediaBrowser.Controller.Entities.Genre; using Person = MediaBrowser.Controller.Entities.Person; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index d16275b19..8aa605a90 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -13,8 +15,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using Book = MediaBrowser.Controller.Entities.Book; using AudioBook = MediaBrowser.Controller.Entities.AudioBook; +using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library { @@ -220,7 +222,7 @@ namespace Emby.Server.Implementations.Library var hasRuntime = runtimeTicks > 0; // If a position has been reported, and if we know the duration - if (positionTicks > 0 && hasRuntime && !(item is AudioBook)) + if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book) { var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100; @@ -239,7 +241,7 @@ namespace Emby.Server.Implementations.Library { // Enforce MinResumeDuration var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds; - if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book)) + if (durationSeconds < _config.Configuration.MinResumeDurationSeconds) { positionTicks = 0; data.Played = playedToCompletion = true; @@ -248,15 +250,15 @@ namespace Emby.Server.Implementations.Library } else if (positionTicks > 0 && hasRuntime && item is AudioBook) { - var minIn = TimeSpan.FromTicks(positionTicks).TotalMinutes; - var minOut = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes; + var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes; + var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes; - if (minIn > _config.Configuration.MinAudiobookResume) + if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume) { // ignore progress during the beginning positionTicks = 0; } - else if (minOut < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks) + else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks) { // mark as completed close to the end positionTicks = 0; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b6b7ea949..e2da672a3 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,8 +1,9 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 7a6b1d8b6..3fcadf5b1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 91a21db60..797063120 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -17,7 +19,6 @@ using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -802,22 +803,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public ActiveRecordingInfo GetActiveRecordingInfo(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty) { return null; } - foreach (var recording in _activeRecordings.Values) + foreach (var (_, recordingInfo) in _activeRecordings) { - if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested) + if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested) { - var timer = recording.Timer; + var timer = recordingInfo.Timer; if (timer.Status != RecordingStatus.InProgress) { return null; } - return recording; + return recordingInfo; } } @@ -1622,9 +1623,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } return _activeRecordings - .Values - .ToList() - .Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase)); + .Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase)); } private IRecorder GetRecorder(MediaSourceInfo mediaSource) @@ -2240,14 +2239,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var enabledTimersForSeries = new List<TimerInfo>(); foreach (var timer in allTimers) { - var existingTimer = _timerProvider.GetTimer(timer.Id); - - if (existingTimer == null) - { - existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) + var existingTimer = _timerProvider.GetTimer(timer.Id) + ?? (string.IsNullOrWhiteSpace(timer.ProgramId) ? null - : _timerProvider.GetTimerByProgramId(timer.ProgramId); - } + : _timerProvider.GetTimerByProgramId(timer.ProgramId)); if (existingTimer == null) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 44a8cdee4..26e4ef1ed 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -10,6 +12,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -307,13 +310,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { using (var reader = new StreamReader(source)) { - while (!reader.EndOfStream) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.WriteAsync(bytes.AsMemory()).ConfigureAwait(false); await target.FlushAsync().ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 463d0ed0a..0ec52a959 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -6,58 +6,46 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class EpgChannelData { + + private readonly Dictionary<string, ChannelInfo> _channelsById; + + private readonly Dictionary<string, ChannelInfo> _channelsByNumber; + + private readonly Dictionary<string, ChannelInfo> _channelsByName; + public EpgChannelData(IEnumerable<ChannelInfo> channels) { - ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); - ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); - ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); + _channelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); + _channelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); + _channelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase); foreach (var channel in channels) { - ChannelsById[channel.Id] = channel; + _channelsById[channel.Id] = channel; if (!string.IsNullOrEmpty(channel.Number)) { - ChannelsByNumber[channel.Number] = channel; + _channelsByNumber[channel.Number] = channel; } var normalizedName = NormalizeName(channel.Name ?? string.Empty); if (!string.IsNullOrWhiteSpace(normalizedName)) { - ChannelsByName[normalizedName] = channel; + _channelsByName[normalizedName] = channel; } } } - private Dictionary<string, ChannelInfo> ChannelsById { get; set; } + public ChannelInfo? GetChannelById(string id) + => _channelsById.GetValueOrDefault(id); - private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; } + public ChannelInfo? GetChannelByNumber(string number) + => _channelsByNumber.GetValueOrDefault(number); - private Dictionary<string, ChannelInfo> ChannelsByName { get; set; } - - public ChannelInfo GetChannelById(string id) - { - ChannelsById.TryGetValue(id, out var result); - - return result; - } - - public ChannelInfo GetChannelByNumber(string number) - { - ChannelsByNumber.TryGetValue(number, out var result); - - return result; - } - - public ChannelInfo GetChannelByName(string name) - { - ChannelsByName.TryGetValue(name, out var result); - - return result; - } + public ChannelInfo? GetChannelByName(string name) + => _channelsByName.GetValueOrDefault(name); public static string NormalizeName(string value) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index c20b08088..bdab8c3e4 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,12 +1,12 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; using MediaBrowser.Common.Json; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 142c59542..108863869 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -6,7 +8,7 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class RecordingHelper + internal static class RecordingHelper { public static DateTime GetStartTime(TimerInfo timer) { @@ -70,17 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private static string GetDateString(DateTime date) { - date = date.ToLocalTime(); - - return string.Format( - CultureInfo.InvariantCulture, - "{0}_{1}_{2}_{3}_{4}_{5}", - date.Year.ToString("0000", CultureInfo.InvariantCulture), - date.Month.ToString("00", CultureInfo.InvariantCulture), - date.Day.ToString("00", CultureInfo.InvariantCulture), - date.Hour.ToString("00", CultureInfo.InvariantCulture), - date.Minute.ToString("00", CultureInfo.InvariantCulture), - date.Second.ToString("00", CultureInfo.InvariantCulture)); + return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs index da707fec6..b1259de23 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs @@ -2,7 +2,6 @@ using System; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 1efa90e25..6c52a9a73 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f..00d02873c 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -787,14 +789,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station + var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) + ?? new ScheduleDirect.Station { stationID = channel.stationID }; - } var channelInfo = new ChannelInfo { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 76c875737..ebad4eddf 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,10 +1,11 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Threading; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6af49dd45..21e1409ac 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 63a3146aa..1f1628900 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -987,10 +989,7 @@ namespace Emby.Server.Implementations.LiveTv var externalProgramId = programTuple.Item2; string externalSeriesId = programTuple.Item3; - if (timerList == null) - { - timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); var foundSeriesTimer = false; @@ -1018,10 +1017,7 @@ namespace Emby.Server.Implementations.LiveTv continue; } - if (seriesTimerList == null) - { - seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); @@ -1974,10 +1970,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - if (service == null) - { - service = _services[0]; - } + service ??= _services[0]; var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 3a738fd5d..ecd28097d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index fbcd4ef37..00a37bb02 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs index 740cbb66e..0f0453189 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { internal class Channels diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 09d77f838..42068cd34 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 68173a0ef..c5700db71 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -8,10 +10,8 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; @@ -19,7 +19,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -77,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken) .ConfigureAwait(false) ?? new List<Channels>(); @@ -185,16 +184,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); using var sr = new StreamReader(stream, System.Text.Encoding.UTF8); var tuners = new List<LiveTvTunerInfo>(); - while (!sr.EndOfStream) + await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false)) { - string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel", StringComparison.Ordinal)) + string stripedLine = StripXML(line); + if (stripedLine.Contains("Channel", StringComparison.Ordinal)) { LiveTvTunerStatus status; - var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = line.Substring(0, index - 1); - var currentChannel = line.Substring(index + 7); - if (currentChannel != "none") + var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); + var name = stripedLine.Substring(0, index - 1); + var currentChannel = stripedLine.Substring(index + 7); + if (string.Equals(currentChannel, "none", StringComparison.Ordinal)) { status = LiveTvTunerStatus.LiveTv; } @@ -424,10 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun string audioCodec = channelInfo.AudioCodec; - if (!videoBitrate.HasValue) - { - videoBitrate = isHd ? 15000000 : 2000000; - } + videoBitrate ??= isHd ? 15000000 : 2000000; int? audioBitrate = isHd ? 448000 : 192000; @@ -664,7 +660,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _modelCache.Clear(); } - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token; + using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs); + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken); + cancellationToken = linkedCancellationTokenSource.Token; var list = new List<TunerHostInfo>(); // Create udp broadcast discovery message diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 20a4d87fb..3016eeda2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -130,9 +132,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - ParseReturnMessage(buffer, receivedBytes, out string returnVal); - - return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); + return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none"); } finally { @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { continue; } @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); continue; @@ -199,7 +199,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false); continue; @@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked - if (!ParseReturnMessage(buffer, receivedBytes, out _)) + if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _)) { return; } @@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return FinishPacket(buffer, offset); } - private static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey) + internal static int WriteSetMessage(Span<byte> buffer, int tuner, string name, string value, uint? lockkey) { var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name); int offset = WriteHeaderAndPayload(buffer, byteName); @@ -355,60 +355,65 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return offset + 4; } - private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) + internal static bool VerifyReturnValueOfGetSet(ReadOnlySpan<byte> buffer, string expected) + { + return TryGetReturnValueOfGetSet(buffer, out var value) + && string.Equals(Encoding.UTF8.GetString(value), expected, StringComparison.OrdinalIgnoreCase); + } + + internal static bool TryGetReturnValueOfGetSet(ReadOnlySpan<byte> buffer, out ReadOnlySpan<byte> value) { - returnVal = string.Empty; + value = ReadOnlySpan<byte>.Empty; - if (numBytes < 4) + if (buffer.Length < 8) { return false; } - var flipEndian = BitConverter.IsLittleEndian; - int offset = 0; - byte[] msgTypeBytes = new byte[2]; - Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length); - - if (flipEndian) + uint crc = BinaryPrimitives.ReadUInt32LittleEndian(buffer[^4..]); + if (crc != Crc32.Compute(buffer[..^4])) { - Array.Reverse(msgTypeBytes); + return false; } - var msgType = BitConverter.ToUInt16(msgTypeBytes, 0); - offset += 2; - - if (msgType != GetSetReply) + if (BinaryPrimitives.ReadUInt16BigEndian(buffer) != GetSetReply) { return false; } - byte[] msgLengthBytes = new byte[2]; - Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length); - if (flipEndian) + var msgLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2)); + if (buffer.Length != 2 + 2 + 4 + msgLength) { - Array.Reverse(msgLengthBytes); + return false; } - var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0); - offset += 2; - - if (numBytes < msgLength + 8) + var offset = 4; + if (buffer[offset++] != GetSetName) { return false; } - offset++; // Name Tag - - var nameLength = buf[offset++]; + var nameLength = buffer[offset++]; + if (buffer.Length < 4 + 1 + offset + nameLength) + { + return false; + } - // skip the name field to get to value for return offset += nameLength; - offset++; // Value Tag + if (buffer[offset++] != GetSetValue) + { + return false; + } - var valueLength = buf[offset++]; + var valueLength = buffer[offset++]; + if (buffer.Length < 4 + offset + valueLength) + { + return false; + } - returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator + // remove null terminator + value = buffer.Slice(offset, valueLength - 1); return true; } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561..50a2d9abb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 78e62ff0a..96a678c1d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -150,7 +152,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token); + cancellationToken = linkedCancellationTokenSource.Token; // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 4b170b2e4..69035dac9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index c4f173c7a..48a0c3cd3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,10 +1,11 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; @@ -36,16 +37,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // Read the file and display it line by line. using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false))) { - return GetChannels(reader, channelIdPrefix, info.Id); - } - } - - public List<ChannelInfo> ParseString(string text, string channelIdPrefix, string tunerHostId) - { - // Read the file and display it line by line. - using (var reader = new StringReader(text)) - { - return GetChannels(reader, channelIdPrefix, tunerHostId); + return await GetChannelsAsync(reader, channelIdPrefix, info.Id).ConfigureAwait(false); } } @@ -71,43 +63,42 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private const string ExtInfPrefix = "#EXTINF:"; - private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId) + private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) { var channels = new List<ChannelInfo>(); - string line; string extInf = string.Empty; - while ((line = reader.ReadLine()) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - line = line.Trim(); - if (string.IsNullOrWhiteSpace(line)) + var trimmedLine = line.Trim(); + if (string.IsNullOrWhiteSpace(trimmedLine)) { continue; } - if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase)) { continue; } - if (line.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) + if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase)) { - extInf = line.Substring(ExtInfPrefix.Length).Trim(); + extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim(); _logger.LogInformation("Found m3u channel: {0}", extInf); } - else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#')) + else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#')) { - var channel = GetChannelnfo(extInf, tunerHostId, line); + var channel = GetChannelnfo(extInf, tunerHostId, trimmedLine); if (string.IsNullOrWhiteSpace(channel.Id)) { - channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture); + channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture); } - channel.Path = line; + channel.Path = trimmedLine; channels.Add(channel); extInf = string.Empty; } @@ -133,6 +124,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.ImageUrl = value; } + if (attributes.TryGetValue("group-title", out string groupTitle)) + { + channel.ChannelGroup = groupTitle; + } + channel.Name = GetChannelName(extInf, attributes); channel.Number = GetChannelNumber(extInf, attributes, mediaUrl); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4..137ed27e2 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index b029b7042..4f21c66bc 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -115,5 +115,7 @@ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde", "Undefined": "Ongedefineerd", "Forced": "Geforseer", - "Default": "Oorspronklik" + "Default": "Oorspronklik", + "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", + "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon" } diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4b898e6fe..3d6e159b1 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -113,5 +113,10 @@ "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.", "TaskRefreshPeople": "إعادة تحميل الأشخاص", "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.", - "TaskCleanLogs": "حذف دليل السجل" + "TaskCleanLogs": "حذف دليل السجل", + "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.", + "TaskCleanActivityLog": "حذف سجل الأنشطة", + "Default": "الإعدادات الافتراضية", + "Undefined": "غير معرف", + "Forced": "ملحقة" } diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 9db3b50d9..bc25531d3 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -39,7 +39,7 @@ "MixedContent": "Смесено съдържание", "Movies": "Филми", "Music": "Музика", - "MusicVideos": "Музикални клипове", + "MusicVideos": "Музикални видеа", "NameInstallFailed": "{0} не можа да се инсталира", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Неразпознат сезон", @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "Photos": "Снимки", "Playlists": "Списъци", - "Plugin": "Приставка", + "Plugin": "Добавка", "PluginInstalledWithName": "{0} е инсталиранa", "PluginUninstalledWithName": "{0} е деинсталиранa", "PluginUpdatedWithName": "{0} е обновенa", @@ -116,5 +116,7 @@ "TasksMaintenanceCategory": "Поддръжка", "Undefined": "Неопределено", "Forced": "Принудително", - "Default": "По подразбиране" + "Default": "По подразбиране", + "TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.", + "TaskCleanActivityLog": "Изчисти дневника с активност" } diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index a23037af8..c3fbe2408 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -1,7 +1,7 @@ { "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", - "Collections": "কলেক্শন", + "Collections": "সংগ্রহ", "ChapterNameValue": "অধ্যায় {0}", "Channels": "চ্যানেল", "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", @@ -115,7 +115,7 @@ "TaskRefreshLibraryDescription": "নতুন ফাইলের জন্য মিডিয়া লাইব্রেরি স্ক্যান এবং মেটাডাটা রিফ্রেশ করুন।", "Undefined": "অসঙ্গায়িত", "Forced": "জোরকরে", - "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন", + "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.", "TaskCleanActivityLog": "কাজের ফাইল খালি করুন", "Default": "প্রাথমিক" } diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 775267183..ff14c1929 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -39,7 +39,7 @@ "MixedContent": "Smíšený obsah", "Movies": "Filmy", "Music": "Hudba", - "MusicVideos": "Hudební klipy", + "MusicVideos": "Hudební videa", "NameInstallFailed": "Instalace {0} selhala", "NameSeasonNumber": "Sezóna {0}", "NameSeasonUnknown": "Neznámá sezóna", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 051d6d009..3453507d9 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -39,7 +39,7 @@ "MixedContent": "Blandet indhold", "Movies": "Film", "Music": "Musik", - "MusicVideos": "Musikvideoer", + "MusicVideos": "Musik videoer", "NameInstallFailed": "{0} installationen mislykkedes", "NameSeasonNumber": "Sæson {0}", "NameSeasonUnknown": "Ukendt Sæson", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index f8f595faa..65964f6d9 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -117,5 +117,7 @@ "TaskRefreshChannels": "Refresh Channels", "TaskRefreshChannelsDescription": "Refreshes internet channel information.", "TaskDownloadMissingSubtitles": "Download missing subtitles", - "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration." + "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", + "TaskOptimizeDatabase": "Optimize database", + "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance." } diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 16fde325f..91939843f 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -118,5 +118,7 @@ "TaskCleanActivityLog": "Limpiar registro de actividad", "Undefined": "Indefinido", "Forced": "Forzado", - "Default": "Predeterminado" + "Default": "Predeterminado", + "TaskOptimizeDatabase": "Optimizar la base de datos", + "TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento." } diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index e9e4f61b8..8ab657e5b 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -34,7 +34,7 @@ "Latest": "جدیدترینها", "MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد", "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد", - "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", + "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MixedContent": "محتوای مخلوط", "Movies": "فیلمها", diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index fd6148e78..633968d26 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "Live TV", + "HeaderLiveTV": "Suora TV", "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon kausi", "NameSeasonNumber": "Kausi {0}", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 1e195378f..ce1493be8 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -15,7 +15,7 @@ "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes", + "HeaderAlbumArtists": "Artistes de l'album", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", @@ -39,7 +39,7 @@ "MixedContent": "Contenu mixte", "Movies": "Films", "Music": "Musique", - "MusicVideos": "Vidéos musicales", + "MusicVideos": "Clips musicaux", "NameInstallFailed": "{0} échec de l'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", @@ -99,7 +99,7 @@ "TaskRefreshChannels": "Rafraîchir les chaines", "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.", "TaskCleanTranscode": "Nettoyer les dossier des transcodages", - "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.", + "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurées 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", @@ -107,7 +107,7 @@ "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": "Scanner toutes les Bibliothèques", - "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.", + "TaskRefreshChapterImagesDescription": "Crée des vignettes pour les vidéos ayant des chapitres.", "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", diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json index 11139d32a..0398e1c9e 100644 --- a/Emby.Server.Implementations/Localization/Core/gl.json +++ b/Emby.Server.Implementations/Localization/Core/gl.json @@ -79,5 +79,14 @@ "PluginUninstalledWithName": "{0} foi desinstalado", "PluginInstalledWithName": "{0} foi instalado", "Playlists": "Listas de reproducción", - "Photos": "Fotos" + "Photos": "Fotos", + "UserLockedOutWithName": "O usuario {0} foi bloqueado", + "UserDownloadingItemWithValues": "{0} está a ser transferido {1}", + "UserDeletedWithName": "O usuario {0} foi borrado", + "UserCreatedWithName": "O usuario {0} foi creado", + "Plugin": "Plugin", + "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo parada", + "NotificationOptionVideoPlayback": "Reproducción de vídeo iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla na tarefa axendada" } diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index ef3697b15..82dc601bc 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -51,5 +51,14 @@ "Latest": "सबसे नया", "LabelIpAddressValue": "आई पी एड्रेस: {0}", "ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है", - "HomeVideos": "होम वीडियोस" + "HomeVideos": "होम वीडियोस", + "NotificationOptionVideoPlayback": "वीडियो प्लेबैक शुरू हुआ", + "NotificationOptionUserLockedOut": "उपयोगकर्ता लॉक हो गया", + "NotificationOptionTaskFailed": "निर्धारित कार्य विफलता", + "NotificationOptionServerRestartRequired": "सर्वर पुनरारंभ आवश्यक है", + "NotificationOptionPluginUpdateInstalled": "प्लगइन अद्यतन स्थापित", + "NotificationOptionNewLibraryContent": "नई सामग्री जोड़ी गई", + "LabelRunningTimeValue": "चलने का समय: {0}", + "ItemAddedWithName": "{0} को लाइब्रेरी में जोड़ा गया", + "Inherit": "इनहेरिट" } diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index ef8070503..85848fed6 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -39,7 +39,7 @@ "MixedContent": "Vegyes tartalom", "Movies": "Filmek", "Music": "Zene", - "MusicVideos": "Zenei videók", + "MusicVideos": "Zenei videóklippek", "NameInstallFailed": "{0} sikertelen telepítés", "NameSeasonNumber": "{0}. évad", "NameSeasonUnknown": "Ismeretlen évad", @@ -74,7 +74,7 @@ "Songs": "Dalok", "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", + "SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}", "Sync": "Szinkronizál", "System": "Rendszer", "TvShows": "TV műsorok", @@ -82,12 +82,12 @@ "UserCreatedWithName": "{0} felhasználó létrehozva", "UserDeletedWithName": "{0} felhasználó törölve", "UserDownloadingItemWithValues": "{0} letölti {1}", - "UserLockedOutWithName": "{0} felhasználó zárolva van", - "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", + "UserLockedOutWithName": "{0} felhasználó zárolva van", + "UserOfflineFromDevice": "{0} kijelentkezett innen: {1}", "UserOnlineFromDevice": "{0} online innen: {1}", "UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}", "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}", - "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", + "UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}", "UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}", "ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz", "ValueSpecialEpisodeName": "Special - {0}", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 0f769eaad..b262a8b42 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -25,7 +25,7 @@ "Channels": "Stöðvar", "CameraImageUploadedFrom": "Ný ljósmynd frá myndavél hefur verið hlaðið upp frá {0}", "Books": "Bækur", - "AuthenticationSucceededWithUserName": "{0} náði að auðkennast", + "AuthenticationSucceededWithUserName": "{0} auðkenning tókst", "Artists": "Listamaður", "Application": "Forrit", "AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}", @@ -106,5 +106,6 @@ "TasksChannelsCategory": "Netrásir", "TasksApplicationCategory": "Forrit", "TasksLibraryCategory": "Miðlasafn", - "TasksMaintenanceCategory": "Viðhald" + "TasksMaintenanceCategory": "Viðhald", + "Default": "Sjálfgefið" } diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 110f8043d..bd06f0a25 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -62,7 +62,7 @@ "NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta", "Photos": "Foto", "Playlists": "Playlist", - "Plugin": "Plug-in", + "Plugin": "Plugin", "PluginInstalledWithName": "{0} è stato Installato", "PluginUninstalledWithName": "{0} è stato disinstallato", "PluginUpdatedWithName": "{0} è stato aggiornato", @@ -87,7 +87,7 @@ "UserOnlineFromDevice": "{0} è online su {1}", "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", - "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}", + "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di \"{1}\" su {2}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 829a29ad4..4eee36989 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -5,23 +5,23 @@ "Artists": "Oryndauşylar", "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy", "Books": "Kıtaptar", - "CameraImageUploadedFrom": "{0} kamerasynan jaŋa suret jüktep salyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jaña suret jüktep salyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jiyntyqtar", "DeviceOfflineWithName": "{0} ajyratylğan", "DeviceOnlineWithName": "{0} qosylğan", "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy", - "Favorites": "Taŋdaulylar", + "Favorites": "Tañdaulylar", "Folders": "Qaltalar", "Genres": "Janrlar", "HeaderAlbumArtists": "Älbom oryndauşylary", "HeaderContinueWatching": "Qaraudy jalğastyru", - "HeaderFavoriteAlbums": "Taŋdauly älbomdar", - "HeaderFavoriteArtists": "Taŋdauly oryndauşylar", - "HeaderFavoriteEpisodes": "Taŋdauly telebölımder", - "HeaderFavoriteShows": "Taŋdauly körsetımder", - "HeaderFavoriteSongs": "Taŋdauly äuender", + "HeaderFavoriteAlbums": "Tañdauly älbomdar", + "HeaderFavoriteArtists": "Tañdauly oryndauşylar", + "HeaderFavoriteEpisodes": "Tañdauly telebölımder", + "HeaderFavoriteShows": "Tañdauly körsetımder", + "HeaderFavoriteSongs": "Tañdauly äuender", "HeaderLiveTV": "Efir", "HeaderNextUp": "Kezektı", "HeaderRecordingGroups": "Jazba toptary", @@ -31,11 +31,11 @@ "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy", "LabelIpAddressValue": "IP-mekenjaiy: {0}", "LabelRunningTimeValue": "Oinatu uaqyty: {0}", - "Latest": "Eŋ keiıngı", - "MessageApplicationUpdated": "Jellyfin Serverı jaŋartyldy", - "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jaŋartyldy", - "MessageNamedServerConfigurationUpdatedWithValue": "Server teŋşelımderınıŋ {0} bölımı jaŋartyldy", - "MessageServerConfigurationUpdated": "Server teŋşelımderı jaŋartyldy", + "Latest": "Eñ keiıngı", + "MessageApplicationUpdated": "Jellyfin Serverı jañartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jañartyldy", + "MessageNamedServerConfigurationUpdatedWithValue": "Server teñşelımderınıñ {0} bölımı jañartyldy", + "MessageServerConfigurationUpdated": "Server teñşelımderı jañartyldy", "MixedContent": "Aralas mazmūn", "Movies": "Filmder", "Music": "Muzyka", @@ -43,18 +43,18 @@ "NameInstallFailed": "{0} ornatyluy sätsız", "NameSeasonNumber": "{0}-mausym", "NameSeasonUnknown": "Belgısız mausym", - "NewVersionIsAvailable": "Jaŋa Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", - "NotificationOptionApplicationUpdateAvailable": "Qoldanba jaŋartuy qoljetımdı", - "NotificationOptionApplicationUpdateInstalled": "Qoldanba jaŋartuy ornatyldy", + "NewVersionIsAvailable": "Jaña Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.", + "NotificationOptionApplicationUpdateAvailable": "Qoldanba jañartuy qoljetımdı", + "NotificationOptionApplicationUpdateInstalled": "Qoldanba jañartuy ornatyldy", "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy", "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy", "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan", "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı", - "NotificationOptionNewLibraryContent": "Jaŋa mazmūn üstelıngen", + "NotificationOptionNewLibraryContent": "Jaña mazmūn üstelıngen", "NotificationOptionPluginError": "Plagin sätsızdıgı", "NotificationOptionPluginInstalled": "Plagin ornatyldy", "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady", - "NotificationOptionPluginUpdateInstalled": "Plagin jaŋartuy ornatyldy", + "NotificationOptionPluginUpdateInstalled": "Plagin jañartuy ornatyldy", "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet", "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı", "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly", @@ -65,14 +65,14 @@ "Plugin": "Plagin", "PluginInstalledWithName": "{0} ornatyldy", "PluginUninstalledWithName": "{0} joiyldy", - "PluginUpdatedWithName": "{0} jaŋartyldy", + "PluginUpdatedWithName": "{0} jañartyldy", "ProviderValue": "Jetkızuşı: {0}", "ScheduledTaskFailedWithName": "{0} sätsız", "ScheduledTaskStartedWithName": "{0} ıske qosyldy", "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet", "Shows": "Körsetımder", "Songs": "Äuender", - "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalaŋyz.", + "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalañyz.", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız", "Sync": "Ündestıru", @@ -86,7 +86,7 @@ "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy", "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy", "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı", - "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jaŋartyldy", + "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jañartyldy", "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda", "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty", "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı", @@ -94,10 +94,10 @@ "VersionNumber": "Nūsqasy {0}", "Default": "Ädepkı", "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu", - "TaskRefreshChannels": "Arnalardy jaŋğyrtu", + "TaskRefreshChannels": "Arnalardy jañğyrtu", "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau", - "TaskUpdatePlugins": "Plaginderdı jaŋartu", - "TaskRefreshPeople": "Adamdardy jaŋğyrtu", + "TaskUpdatePlugins": "Plaginderdı jañartu", + "TaskRefreshPeople": "Adamdardy jañğyrtu", "TaskCleanLogs": "Jūrnal katalogyn tazalau", "TaskRefreshLibrary": "Tasyğyşhanany skanerleu", "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu", @@ -109,14 +109,14 @@ "TasksMaintenanceCategory": "Qyzmet körsetu", "Undefined": "Anyqtalmağan", "Forced": "Mäjbürlı", - "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", - "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.", + "TaskDownloadMissingSubtitlesDescription": "Metaderekter teñşelımderı negızınde joq subtitrlerdı İnternetten ızdeidı.", + "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jañğyrtady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.", - "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.", - "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.", + "TaskUpdatePluginsDescription": "Avtomatty türde jañartuğa teñşelgen plaginder üşın jañartulardy jüktep alady jäne ornatady.", + "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jañartady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.", - "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.", + "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaña faildardy skanerleidі jäne metaderekterdı jañğyrtady.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.", - "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady." + "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teñşelgen jasynan asqan jazbalary joiady." } diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 9920ef4d5..f3a131d40 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -114,8 +114,9 @@ "TasksApplicationCategory": "Programa", "TasksLibraryCategory": "Mediateka", "TasksMaintenanceCategory": "Priežiūra", - "TaskCleanActivityLog": "Švarus veiklos žurnalas", + "TaskCleanActivityLog": "Išvalyti veiklos žurnalą", "Undefined": "Neapibrėžtas", "Forced": "Priverstas", - "Default": "Numatytas" + "Default": "Numatytas", + "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius." } diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 5e3d095ff..5b4c8ae10 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -39,29 +39,29 @@ "MixedContent": "Kandungan campuran", "Movies": "Filem", "Music": "Muzik", - "MusicVideos": "Video muzik", + "MusicVideos": "Muzik video", "NameInstallFailed": "{0} pemasangan gagal", "NameSeasonNumber": "Musim {0}", "NameSeasonUnknown": "Musim Tidak Diketahui", "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.", "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionApplicationUpdateInstalled": "Kemas kini aplikasi telah dipasang", + "NotificationOptionAudioPlayback": "Ulangmain audio bermula", + "NotificationOptionAudioPlaybackStopped": "Ulangmain audio dihentikan", + "NotificationOptionCameraImageUploaded": "Imej kamera telah dimuatnaik", "NotificationOptionInstallationFailed": "Pemasangan gagal", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", + "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", + "NotificationOptionPluginError": "Kegagalan plugin", + "NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginUninstalled": "Plugin uninstalled", "NotificationOptionPluginUpdateInstalled": "Plugin update installed", "NotificationOptionServerRestartRequired": "Server restart required", "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", + "Photos": "Gambar-gambar", + "Playlists": "Senarai main", "Plugin": "Plugin", "PluginInstalledWithName": "{0} was installed", "PluginUninstalledWithName": "{0} was uninstalled", @@ -71,10 +71,10 @@ "ScheduledTaskStartedWithName": "{0} bermula", "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Lagu-lagu", + "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "Sync": "Sync", "System": "Sistem", "TvShows": "TV Shows", @@ -82,14 +82,24 @@ "UserCreatedWithName": "User {0} has been created", "UserDeletedWithName": "User {0} has been deleted", "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserLockedOutWithName": "Pengguna {0} telah dikunci", + "UserOfflineFromDevice": "{0} telah terputus dari {1}", + "UserOnlineFromDevice": "{0} berada dalam talian dari {1}", + "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", + "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueSpecialEpisodeName": "Khas - {0}", - "VersionNumber": "Versi {0}" + "VersionNumber": "Versi {0}", + "TaskCleanActivityLog": "Log Aktiviti Bersih", + "TasksChannelsCategory": "Saluran Internet", + "TasksApplicationCategory": "Aplikasi", + "TasksLibraryCategory": "Perpustakaan", + "TasksMaintenanceCategory": "Penyelenggaraan", + "Undefined": "Tidak ditentukan", + "Forced": "Paksa", + "Default": "Asal", + "TaskCleanCache": "Bersihkan Direktori Cache", + "TaskCleanActivityLogDescription": "Padamkan entri log aktiviti yang lebih tua daripada usia yang dikonfigurasi." } diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index d5bca9f6c..fbe1f7c4d 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -30,20 +30,20 @@ "ItemAddedWithName": "{0} ble lagt til i biblioteket", "ItemRemovedWithName": "{0} ble fjernet fra biblioteket", "LabelIpAddressValue": "IP-adresse: {0}", - "LabelRunningTimeValue": "Kjøretid {0}", + "LabelRunningTimeValue": "Spilletid {0}", "Latest": "Siste", - "MessageApplicationUpdated": "Jellyfin Server har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Server ble oppdatert til {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjon seksjon {0} har blitt oppdatert", - "MessageServerConfigurationUpdated": "Serverkonfigurasjon er oppdatert", + "MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert", "MixedContent": "Blandet innhold", "Movies": "Filmer", "Music": "Musikk", "MusicVideos": "Musikkvideoer", - "NameInstallFailed": "{0}-installasjonen mislyktes", + "NameInstallFailed": "Installasjonen av {0} mislyktes", "NameSeasonNumber": "Sesong {0}", - "NameSeasonUnknown": "Sesong ukjent", - "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.", + "NameSeasonUnknown": "Ukjent sesong", + "NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.", "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", "NotificationOptionAudioPlayback": "Lydavspilling startet", @@ -51,18 +51,18 @@ "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", "NotificationOptionInstallationFailed": "Installasjonen feilet", "NotificationOptionNewLibraryContent": "Nytt innhold lagt til", - "NotificationOptionPluginError": "Pluginfeil", - "NotificationOptionPluginInstalled": "Plugin installert", - "NotificationOptionPluginUninstalled": "Plugin avinstallert", - "NotificationOptionPluginUpdateInstalled": "Pluginoppdatering installert", - "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig", + "NotificationOptionPluginError": "Programvareutvidelsesfeil", + "NotificationOptionPluginInstalled": "Programvareutvidelse installert", + "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", + "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", + "NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig", "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", "NotificationOptionUserLockedOut": "Bruker er utestengt", "NotificationOptionVideoPlayback": "Videoavspilling startet", "NotificationOptionVideoPlaybackStopped": "Videoavspilling stoppet", "Photos": "Bilder", "Playlists": "Spillelister", - "Plugin": "Plugin", + "Plugin": "Programvareutvidelse", "PluginInstalledWithName": "{0} ble installert", "PluginUninstalledWithName": "{0} ble avinstallert", "PluginUpdatedWithName": "{0} ble oppdatert", @@ -72,7 +72,7 @@ "ServerNameNeedsToBeRestarted": "{0} må startes på nytt", "Shows": "Program", "Songs": "Sanger", - "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.", + "StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.", "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}", "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", "Sync": "Synkroniser", @@ -86,37 +86,37 @@ "UserOfflineFromDevice": "{0} har koblet fra {1}", "UserOnlineFromDevice": "{0} er tilkoblet fra {1}", "UserPasswordChangedWithName": "Passordet for {0} er oppdatert", - "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}", + "UserPolicyUpdatedWithName": "Brukerretningslinjene har blitt oppdatert for {0}", "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}", - "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", + "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}", "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt", "ValueSpecialEpisodeName": "Spesialepisode - {0}", "VersionNumber": "Versjon {0}", - "TasksChannelsCategory": "Internett kanaler", + "TasksChannelsCategory": "Internettkanaler", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", "TasksMaintenanceCategory": "Vedlikehold", - "TaskCleanCache": "Tøm buffer katalog", + "TaskCleanCache": "Tøm hurtigbuffer", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.", - "TaskRefreshChapterImages": "Trekk ut Kapittelbilder", + "TaskRefreshChapterImages": "Trekk ut kapittelbilder", "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet.", - "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende underteksting på nett basert på metadatakonfigurasjon.", - "TaskDownloadMissingSubtitles": "Last ned manglende underteksting", - "TaskRefreshChannelsDescription": "Frisker opp internettkanalinformasjon.", - "TaskRefreshChannels": "Oppfrisk kanaler", + "TaskDownloadMissingSubtitlesDescription": "Søker etter manglende undertekster på nett basert på metadatakonfigurasjon.", + "TaskDownloadMissingSubtitles": "Last ned manglende undertekster", + "TaskRefreshChannelsDescription": "Oppdaterer internettkanalinformasjon.", + "TaskRefreshChannels": "Oppdater kanaler", "TaskCleanTranscodeDescription": "Sletter omkodede filer som er mer enn én dag gamle.", "TaskCleanTranscode": "Tøm transkodingmappe", - "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for utvidelser som er stilt inn til å oppdatere automatisk.", - "TaskUpdatePlugins": "Oppdater utvidelser", + "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringer for programvareutvidelser som er stilt inn til å oppdatere automatisk.", + "TaskUpdatePlugins": "Oppdater programvareutvidelse", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skuespillere og regissører i mediebiblioteket ditt.", - "TaskRefreshPeople": "Oppfrisk personer", + "TaskRefreshPeople": "Oppdater personer", "TaskCleanLogsDescription": "Sletter loggfiler som er eldre enn {0} dager gamle.", "TaskCleanLogs": "Tøm loggmappe", "TaskRefreshLibraryDescription": "Skanner mediebibliotekene dine for nye filer og oppdaterer metadata.", "TaskCleanActivityLog": "Tøm aktivitetslogg", "Undefined": "Udefinert", - "Forced": "Tvungen", + "Forced": "Tvunget", "Default": "Standard", "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen." } diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index ffc329e35..2973c8c6e 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -3,9 +3,9 @@ "AppDeviceValues": "App: {0}, Apparaat: {1}", "Application": "Applicatie", "Artists": "Artiesten", - "AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd", + "AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd", "Books": "Boeken", - "CameraImageUploadedFrom": "Er is een nieuwe camera afbeelding toegevoegd via {0}", + "CameraImageUploadedFrom": "Nieuwe camera afbeelding toegevoegd vanaf {0}", "Channels": "Kanalen", "ChapterNameValue": "Hoofdstuk {0}", "Collections": "Verzamelingen", diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index 6236515b2..32d4f3a8b 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -1,25 +1,25 @@ { - "MessageServerConfigurationUpdated": "Tenar konfigurasjonen har blitt oppdatert", + "MessageServerConfigurationUpdated": "Tenarkonfigurasjonen har blitt oppdatert", "MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert", - "MessageApplicationUpdatedTo": "Jellyfin Tenaren har blitt oppdatert til {0}", - "MessageApplicationUpdated": "Jellyfin Tenaren har blitt oppdatert", + "MessageApplicationUpdatedTo": "Jellyfin-tenaren har blitt oppdatert til {0}", + "MessageApplicationUpdated": "Jellyfin-tenaren har blitt oppdatert", "Latest": "Nyaste", "LabelRunningTimeValue": "Speletid: {0}", - "LabelIpAddressValue": "IP adresse: {0}", + "LabelIpAddressValue": "IP-adresse: {0}", "ItemRemovedWithName": "{0} vart fjerna frå biblioteket", "ItemAddedWithName": "{0} vart lagt til i biblioteket", - "Inherit": "Arv", - "HomeVideos": "Heime Videoar", + "Inherit": "Arve", + "HomeVideos": "Heimevideoar", "HeaderRecordingGroups": "Innspelingsgrupper", "HeaderNextUp": "Neste", "HeaderLiveTV": "Direkte TV", - "HeaderFavoriteSongs": "Favoritt Songar", - "HeaderFavoriteShows": "Favoritt Seriar", - "HeaderFavoriteEpisodes": "Favoritt Episodar", - "HeaderFavoriteArtists": "Favoritt Artistar", - "HeaderFavoriteAlbums": "Favoritt Album", + "HeaderFavoriteSongs": "Favorittsongar", + "HeaderFavoriteShows": "Favorittseriar", + "HeaderFavoriteEpisodes": "Favorittepisodar", + "HeaderFavoriteArtists": "Favorittartistar", + "HeaderFavoriteAlbums": "Favorittalbum", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderAlbumArtists": "Album Artist", + "HeaderAlbumArtists": "Albumartist", "Genres": "Sjangrar", "Folders": "Mapper", "Favorites": "Favorittar", @@ -29,18 +29,18 @@ "Collections": "Samlingar", "ChapterNameValue": "Kapittel {0}", "Channels": "Kanalar", - "CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}", + "CameraImageUploadedFrom": "Eit nytt kamerabilete har blitt lasta opp frå {0}", "Books": "Bøker", - "AuthenticationSucceededWithUserName": "{0} Har logga inn", + "AuthenticationSucceededWithUserName": "{0} har logga inn", "Artists": "Artistar", "Application": "Program", "AppDeviceValues": "App: {0}, Eining: {1}", "Albums": "Album", "NotificationOptionServerRestartRequired": "Tenaren krev omstart", - "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert", - "NotificationOptionPluginUninstalled": "Tilleggsprogram avinstallert", - "NotificationOptionPluginInstalled": "Tilleggsprogram installert", - "NotificationOptionPluginError": "Tilleggsprogram feila", + "NotificationOptionPluginUpdateInstalled": "Programvaretilleggoppdatering vart installert", + "NotificationOptionPluginUninstalled": "Programvaretillegg avinstallert", + "NotificationOptionPluginInstalled": "Programvaretillegg installert", + "NotificationOptionPluginError": "Programvaretillegg feila", "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til", "NotificationOptionInstallationFailed": "Installasjonsfeil", "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp", @@ -48,33 +48,33 @@ "NotificationOptionAudioPlayback": "Lydavspilling påbyrja", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering er installert", "NotificationOptionApplicationUpdateAvailable": "Applikasjonsoppdatering er tilgjengeleg", - "NewVersionIsAvailable": "Ein ny versjon av Jellyfin serveren er tilgjengeleg for nedlasting.", + "NewVersionIsAvailable": "Ein ny versjon av Jellyfin-tjenaren er tilgjengeleg for nedlasting.", "NameSeasonUnknown": "Ukjend sesong", "NameSeasonNumber": "Sesong {0}", - "NameInstallFailed": "{0} Installasjonen feila", + "NameInstallFailed": "Installasjonen av {0} feila", "MusicVideos": "Musikkvideoar", "Music": "Musikk", "Movies": "Filmar", "MixedContent": "Blanda innhald", - "Sync": "Synkronisera", + "Sync": "Synkroniser", "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.", "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar", "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.", "TaskRefreshChannels": "Oppdater kanalar", - "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.", - "TaskCleanTranscode": "Reins transkodemappe", + "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gammal.", + "TaskCleanTranscode": "Fjern transkodemappe", "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.", - "TaskUpdatePlugins": "Oppdaterer programtillegg", + "TaskUpdatePlugins": "Oppdaterer programvaretillegg", "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.", "TaskRefreshPeople": "Oppdater personar", "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.", - "TaskCleanLogs": "Reins loggmappe", + "TaskCleanLogs": "Slett loggmappa", "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.", "TaskRefreshLibrary": "Skann mediebibliotek", "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.", "TaskRefreshChapterImages": "Trekk ut kapittelbilete", - "TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.", - "TaskCleanCache": "Rens mappe for hurtiglager", + "TaskCleanCacheDescription": "Sletter mellomlagra filer som ikkje lengre trengst av systemet.", + "TaskCleanCache": "Fjern hurtigbuffer", "TasksChannelsCategory": "Internettkanalar", "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", @@ -96,9 +96,9 @@ "TvShows": "TV-seriar", "System": "System", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", - "StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.", - "Songs": "Songar", - "Shows": "Program", + "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", + "Songs": "Sangar", + "Shows": "Seriar", "ServerNameNeedsToBeRestarted": "{0} må omstartast", "ScheduledTaskStartedWithName": "{0} starta", "ScheduledTaskFailedWithName": "{0} feila", @@ -106,11 +106,16 @@ "PluginUpdatedWithName": "{0} blei oppdatert", "PluginUninstalledWithName": "{0} blei avinstallert", "PluginInstalledWithName": "{0} blei installert", - "Plugin": "Programtillegg", - "Playlists": "Speleliste", - "Photos": "Foto", + "Plugin": "Programvaretillegg", + "Playlists": "Spelelister", + "Photos": "Bilete", "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlayback": "Videoavspeling starta", "NotificationOptionUserLockedOut": "Brukar er utestengd", - "NotificationOptionTaskFailed": "Planlagt oppgåve feila" + "NotificationOptionTaskFailed": "Planlagt oppgåve feila", + "TaskCleanActivityLogDescription": "Sletter aktivitetslogginnlegg som er eldre enn den konfigurerte alderen.", + "TaskCleanActivityLog": "Slett aktivitetslogg", + "Undefined": "Udefinert", + "Forced": "Tvungen", + "Default": "Standard" } diff --git a/Emby.Server.Implementations/Localization/Core/pa.json b/Emby.Server.Implementations/Localization/Core/pa.json index 469fa89b6..d1db09232 100644 --- a/Emby.Server.Implementations/Localization/Core/pa.json +++ b/Emby.Server.Implementations/Localization/Core/pa.json @@ -1,5 +1,5 @@ { - "TaskRefreshChapterImages": "ਐਬਸਟਰੈਕਟ ਅਧਿਆਇ ਅਧਿਆਇ", + "TaskRefreshChapterImages": "ਐਕਸਟਰੈਕਟ ਚੈਪਟਰ ਚਿੱਤਰ", "TaskDownloadMissingSubtitlesDescription": "ਮੈਟਾਡੇਟਾ ਕੌਂਫਿਗਰੇਸ਼ਨ ਦੇ ਅਧਾਰ ਤੇ ਗਾਇਬ ਉਪਸਿਰਲੇਖਾਂ ਲਈ ਇੰਟਰਨੈਟ ਦੀ ਭਾਲ ਕਰਦਾ ਹੈ.", "TaskDownloadMissingSubtitles": "ਗਾਇਬ ਉਪਸਿਰਲੇਖ ਡਾ Download ਨਲੋਡ ਕਰੋ", "TaskRefreshChannelsDescription": "ਇੰਟਰਨੈੱਟ ਚੈਨਲ ਦੀ ਜਾਣਕਾਰੀ ਨੂੰ ਤਾਜ਼ਾ ਕਰਦਾ ਹੈ.", diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 46b47cf4a..e58f8c39d 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -39,7 +39,7 @@ "MixedContent": "Смешанное содержимое", "Movies": "Кино", "Music": "Музыка", - "MusicVideos": "Музыкальные клипы", + "MusicVideos": "Муз. видео", "NameInstallFailed": "Установка {0} неудачна", "NameSeasonNumber": "Сезон {0}", "NameSeasonUnknown": "Сезон неопознан", @@ -75,7 +75,7 @@ "StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.", "SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить", "SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}", - "Sync": "Синхронизация", + "Sync": "Синхро", "System": "Система", "TvShows": "ТВ", "User": "Пользователь", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index b5a7fa5b8..d992bf79b 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -39,7 +39,7 @@ "MixedContent": "Blandat innehåll", "Movies": "Filmer", "Music": "Musik", - "MusicVideos": "Musikvideos", + "MusicVideos": "Musikvideor", "NameInstallFailed": "{0} installationen misslyckades", "NameSeasonNumber": "Säsong {0}", "NameSeasonUnknown": "Okänd säsong", diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index c737ba42b..129986ed0 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -69,7 +69,7 @@ "NameSeasonUnknown": "அறியப்படாத பருவம்", "NameSeasonNumber": "பருவம் {0}", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", - "MusicVideos": "இசைப்படங்கள்", + "MusicVideos": "இசை கானொளி", "Music": "இசை", "Movies": "திரைப்படங்கள்", "Latest": "புதியவை", diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 5bf58baf8..e26010423 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -113,5 +113,9 @@ "Sync": "ซิงค์", "SubtitleDownloadFailureFromForItem": "ไม่สามารถดาวน์โหลดคำบรรยายจาก {0} สำหรับ {1} ได้", "StartupEmbyServerIsLoading": "กำลังโหลดเซิร์ฟเวอร์ Jellyfin โปรดลองอีกครั้งในอีกสักครู่", - "Default": "ค่าเริ่มต้น" + "Default": "ค่าเริ่มต้น", + "TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้", + "TaskCleanActivityLog": "ล้างบันทึกกิจกรรม", + "Undefined": "ไม่ได้กำหนด", + "Forced": "บังคับใช้" } diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index b6073bf6a..5a2069df5 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -1,5 +1,5 @@ { - "MusicVideos": "Музичні кліпи", + "MusicVideos": "Музичні відеокліпи", "Music": "Музика", "Movies": "Фільми", "MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}", @@ -113,7 +113,7 @@ "MessageNamedServerConfigurationUpdatedWithValue": "Розділ конфігурації сервера {0} оновлено", "Inherit": "Успадкувати", "HeaderRecordingGroups": "Групи запису", - "Forced": "Примусово", + "Forced": "Форсовані", "TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.", "TaskCleanActivityLog": "Очистити журнал активності", "Undefined": "Не визначено", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index affb0e099..c3b223f63 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -37,7 +37,7 @@ "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", - "MusicVideos": "音樂MV", + "MusicVideos": "音樂錄影帶", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 98de848bc..dd5dee1d1 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -7,11 +9,11 @@ using System.Linq; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Localization @@ -73,8 +75,7 @@ namespace Emby.Server.Implementations.Localization using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { - string line; - while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { if (string.IsNullOrWhiteSpace(line)) { @@ -119,10 +120,8 @@ namespace Emby.Server.Implementations.Localization using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { - while (!reader.EndOfStream) + await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(line)) { continue; @@ -180,7 +179,7 @@ namespace Emby.Server.Implementations.Localization /// <inheritdoc /> public IEnumerable<CountryInfo> GetCountries() { - StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); + using StreamReader reader = new StreamReader(_assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); return JsonSerializer.Deserialize<IEnumerable<CountryInfo>>(reader.ReadToEnd(), _jsonOptions); } @@ -316,10 +315,9 @@ namespace Emby.Server.Implementations.Localization } const string Prefix = "Core"; - var key = Prefix + culture; return _dictionaries.GetOrAdd( - key, + culture, f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index c6e931448..8aaa1f7bb 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -15,7 +17,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.MediaEncoder @@ -82,11 +83,6 @@ namespace Emby.Server.Implementations.MediaEncoder return false; } - if (video.VideoType == VideoType.Dvd) - { - return false; - } - if (video.IsShortcut) { return false; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 0781a0e33..137728616 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 4e25768cf..a8b18d292 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 932f721ab..9a1ca9946 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -215,7 +217,7 @@ namespace Emby.Server.Implementations.Playlists // Create a list of the new linked children to add to the playlist var childrenToAdd = newItems - .Select(i => LinkedChild.Create(i)) + .Select(LinkedChild.Create) .ToList(); // Log duplicates that have been ignored, if any diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 700396c4c..48281b75f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,7 +1,6 @@ -#nullable enable - using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; @@ -43,12 +42,7 @@ namespace Emby.Server.Implementations.Plugins { get { - if (_httpClientFactory == null) - { - _httpClientFactory = _appHost.Resolve<IHttpClientFactory>(); - } - - return _httpClientFactory; + return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve<IHttpClientFactory>()); } } @@ -165,9 +159,7 @@ namespace Emby.Server.Implementations.Plugins /// </summary> public void CreatePlugins() { - _ = _appHost.GetExports<IPlugin>(CreatePluginInstance) - .Where(i => i != null) - .ToArray(); + _ = _appHost.GetExports<IPlugin>(CreatePluginInstance); } /// <summary> @@ -277,11 +269,7 @@ namespace Emby.Server.Implementations.Plugins // If no version is given, return the current instance. var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); - plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null) - { - plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); - } + plugin = plugins.FirstOrDefault(p => p.Instance != null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); } else { @@ -368,7 +356,7 @@ namespace Emby.Server.Implementations.Plugins } /// <inheritdoc/> - public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path) + public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status) { if (packageInfo == null) { @@ -411,9 +399,9 @@ namespace Emby.Server.Implementations.Plugins Overview = packageInfo.Overview, Owner = packageInfo.Owner, TargetAbi = versionInfo.TargetAbi ?? string.Empty, - Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp), + Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture), Version = versionInfo.Version, - Status = PluginStatus.Active, + Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state. AutoUpdate = true, ImagePath = imagePath }; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de3..7cfd1fced 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,9 +1,10 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Security.Cryptography; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; @@ -258,20 +259,17 @@ namespace Emby.Server.Implementations.QuickConnect } // Expire stale connection requests - var code = string.Empty; - var values = _currentRequests.Values.ToList(); - - for (int i = 0; i < values.Count; i++) + foreach (var (_, currentRequest) in _currentRequests) { - var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) + var added = currentRequest.DateAdded ?? DateTime.UnixEpoch; + if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout)) { - code = values[i].Code; - _logger.LogDebug("Removing expired request {code}", code); + var code = currentRequest.Code; + _logger.LogDebug("Removing expired request {Code}", code); if (!_currentRequests.TryRemove(code, out _)) { - _logger.LogWarning("Request {code} already expired", code); + _logger.LogWarning("Request {Code} already expired", code); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index a145a8423..ccbd4289e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,10 +1,11 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -178,7 +179,8 @@ namespace Emby.Server.Implementations.ScheduledTasks lock (_lastExecutionResultSyncLock) { using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - JsonSerializer.SerializeAsync(createStream, value, _jsonOptions); + using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonStream, value, _jsonOptions); } } } @@ -301,12 +303,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { - if (_id == null) - { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - return _id; + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } } @@ -348,9 +345,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var trigger = (ITaskTrigger)sender; - var configurableTask = ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null && !configurableTask.IsEnabled) + if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled) { return; } @@ -578,7 +573,8 @@ namespace Emby.Server.Implementations.ScheduledTasks Directory.CreateDirectory(Path.GetDirectoryName(path)); using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - JsonSerializer.SerializeAsync(createStream, triggers, _jsonOptions); + using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions); } /// <summary> diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index af316e108..4f0df75bf 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 649305fd5..baeb86a22 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -12,9 +12,9 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks { @@ -140,8 +140,10 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); - - Directory.CreateDirectory(parentPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 4abbf784b..50ba9bc89 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -75,4 +75,4 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Enumerable.Empty<TaskTriggerInfo>(); } } -}
\ No newline at end of file +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs new file mode 100644 index 000000000..1ad1d0f50 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.Implementations; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// <summary> + /// Optimizes Jellyfin's database by issuing a VACUUM command. + /// </summary> + public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILogger<OptimizeDatabaseTask> _logger; + private readonly ILocalizationManager _localization; + private readonly JellyfinDbProvider _provider; + + /// <summary> + /// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class. + /// </summary> + public OptimizeDatabaseTask( + ILogger<OptimizeDatabaseTask> logger, + ILocalizationManager localization, + JellyfinDbProvider provider) + { + _logger = logger; + _localization = localization; + _provider = provider; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskOptimizeDatabase"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskOptimizeDatabaseDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "OptimizeDatabaseTask"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <summary> + /// Creates the triggers that define when the task will run. + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] + { + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } + }; + } + + /// <summary> + /// Returns the task to be executed. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + _logger.LogInformation("Optimizing and vacuuming jellyfin.db..."); + + try + { + using var context = _provider.CreateContext(); + if (context.Database.IsSqlite()) + { + context.Database.ExecuteSqlRaw("PRAGMA optimize"); + context.Database.ExecuteSqlRaw("VACUUM"); + _logger.LogInformation("jellyfin.db optimized successfully!"); + } + else + { + _logger.LogInformation("This database doesn't support optimization"); + } + } + catch (Exception e) + { + _logger.LogError(e, "Error while optimizing jellyfin.db"); + } + + return Task.CompletedTask; + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index c384cf4bb..57d294a40 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index a69380cbb..11a5fb79f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index e470adcf4..51b620404 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -6,8 +6,8 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b40320ab..3b63536a4 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index b04fd7c7e..e13782fe0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 7cd5493da..ced14195b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 0c0ebec08..a67f940b7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4bc12f44a..e8eac315b 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -289,7 +291,7 @@ namespace Emby.Server.Implementations.Security return result; } - private static AuthenticationInfo Get(IReadOnlyList<IResultSetValue> reader) + private static AuthenticationInfo Get(IReadOnlyList<ResultSetValue> reader) { var info = new AuthenticationInfo { @@ -297,50 +299,49 @@ namespace Emby.Server.Implementations.Security AccessToken = reader[1].ToString() }; - if (reader[2].SQLiteType != SQLiteType.Null) - { - info.DeviceId = reader[2].ToString(); - } - - if (reader[3].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(2, out var deviceId)) { - info.AppName = reader[3].ToString(); + info.DeviceId = deviceId; } - if (reader[4].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(3, out var appName)) { - info.AppVersion = reader[4].ToString(); + info.AppName = appName; } - if (reader[5].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(4, out var appVersion)) { - info.DeviceName = reader[5].ToString(); + info.AppVersion = appVersion; } - if (reader[6].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(6, out var userId)) { - info.UserId = new Guid(reader[6].ToString()); + info.UserId = new Guid(userId); } - if (reader[7].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(7, out var userName)) { - info.UserName = reader[7].ToString(); + info.UserName = userName; } info.DateCreated = reader[8].ReadDateTime(); - if (reader[9].SQLiteType != SQLiteType.Null) + if (reader.TryReadDateTime(9, out var dateLastActivity)) { - info.DateLastActivity = reader[9].ReadDateTime(); + info.DateLastActivity = dateLastActivity; } else { info.DateLastActivity = info.DateCreated; } - if (reader[10].SQLiteType != SQLiteType.Null) + if (reader.TryGetString(10, out var customName)) + { + info.DeviceName = customName; + } + else if (reader.TryGetString(5, out var deviceName)) { - info.DeviceName = reader[10].ToString(); + info.DeviceName = deviceName; } return info; @@ -361,9 +362,9 @@ namespace Emby.Server.Implementations.Security foreach (var row in statement.ExecuteQuery()) { - if (row[0].SQLiteType != SQLiteType.Null) + if (row.TryGetString(0, out var customName)) { - result.CustomName = row[0].ToString(); + result.CustomName = customName; } } diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 27024e4e1..8d8b82f0a 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Serialization new ConcurrentDictionary<string, XmlSerializer>(); private static XmlSerializer GetSerializer(Type type) - => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + _ => new XmlSerializer(type)); /// <summary> /// Serializes to writer. @@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="stream">The stream.</param> /// <returns>System.Object.</returns> - public object DeserializeFromStream(Type type, Stream stream) + public object? DeserializeFromStream(Type type, Stream stream) { using (var reader = XmlReader.Create(stream)) { @@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="file">The file.</param> /// <returns>System.Object.</returns> - public object DeserializeFromFile(Type type, string file) + public object? DeserializeFromFile(Type type, string file) { using (var stream = File.OpenRead(file)) { @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="buffer">The buffer.</param> /// <returns>System.Object.</returns> - public object DeserializeFromBytes(Type type, byte[] buffer) + public object? DeserializeFromBytes(Type type, byte[] buffer) { using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a..62df354fd 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -1475,17 +1477,14 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserById(request.UserId); } - if (user == null) - { - user = _userManager.GetUserByName(request.Username); - } + user ??= _userManager.GetUserByName(request.Username); if (enforcePassword) { user = await _userManager.AuthenticateUser( request.Username, request.Password, - request.PasswordSha1, + null, request.RemoteEndPoint, true).ConfigureAwait(false); } @@ -1543,23 +1542,26 @@ namespace Emby.Server.Implementations.Session Limit = 1 }).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get( - new AuthenticationInfoQuery - { - DeviceId = deviceId - }).Items; - - foreach (var auth in allExistingForDevice) + if (!string.IsNullOrEmpty(deviceId)) { - if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) - { - try + var allExistingForDevice = _authRepo.Get( + new AuthenticationInfoQuery { - Logout(auth); - } - catch (Exception ex) + DeviceId = deviceId + }).Items; + + foreach (var auth in allExistingForDevice) + { + if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) { - _logger.LogError(ex, "Error while logging out."); + try + { + Logout(auth); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while logging out."); + } } } } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01..e9e3ca7f4 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a653b58c2..ed1dfca59 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -#nullable enable using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 1f68a9c81..2b0ab536f 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { @@ -131,11 +131,11 @@ namespace Emby.Server.Implementations.Sorting return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y)); } - private static int GetSpecialCompareValue(Episode item) + private static long GetSpecialCompareValue(Episode item) { // First sort by season number // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough) - var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000; + var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L; // Second sort order is if it airs after the season if (item.AirsAfterSeasonNumber.HasValue) diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 7657cc74e..42e644970 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as IHasAlbumArtist; diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 7dfdd9ecf..1db3f5e9c 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as Audio; diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 756d3c5b6..98bee3fd9 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting public string Name => ItemSortBy.Artist; /// <inheritdoc /> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { if (!(x is Audio audio)) { diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 980954ba0..5f142fa4b 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index fa136c36d..d20dedc2d 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } - private static float GetValue(BaseItem x) + private static float GetValue(BaseItem? x) { - return x.CriticRating ?? 0; + return x?.CriticRating ?? 0; } /// <summary> diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index cbca300d2..d3f10f78c 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 03ff19d21..b1cb123ce 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 16bd2aff8..08a44319f 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 0c4e82d01..73e628cf7 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index a35192eff..3c5ddeefa 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -30,9 +30,9 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { - return x.IsFolder ? 0 : 1; + return x?.IsFolder ?? true ? 0 : 1; } } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index d95948406..7d77a8bc5 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 1632c5a7a..926835f90 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index da020d8d8..4de81a69e 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 76bb798b5..a81f78ebf 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 5c2830322..04e4865cb 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 92ac04dc6..c98f97bf1 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetDate(x).CompareTo(GetDate(y)); } @@ -26,8 +26,13 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>DateTime.</returns> - private static DateTime GetDate(BaseItem x) + private static DateTime GetDate(BaseItem? x) { + if (x == null) + { + return DateTime.MinValue; + } + if (x.PremiereDate.HasValue) { return x.PremiereDate.Value; diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index e2857df0b..df9f9957d 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -25,8 +25,13 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>DateTime.</returns> - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { + if (x == null) + { + return 0; + } + if (x.ProductionYear.HasValue) { return x.ProductionYear.Value; diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index 7739d0418..af3bc2750 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index dde44333d..129315303 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b9205ee07..4123a59f8 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index f745e193b..8d30716d3 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index 558a3d351..c3df7c47e 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 5766dc542..01445c525 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477..12efff261 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index aee959c53..993456196 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -87,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; _logger = loggerFactory.CreateLogger<SyncPlayManager>(); - _sessionManager.SessionControllerConnected += OnSessionControllerConnected; + _sessionManager.SessionEnded += OnSessionEnded; } /// <inheritdoc /> @@ -269,14 +271,17 @@ namespace Emby.Server.Implementations.SyncPlay var user = _userManager.GetUserById(session.UserId); List<GroupInfoDto> list = new List<GroupInfoDto>(); - foreach (var group in _groups.Values) + lock (_groupsLock) { - // Locking required as group is not thread-safe. - lock (group) + foreach (var (_, group) in _groups) { - if (group.HasAccessToPlayQueue(user)) + // Locking required as group is not thread-safe. + lock (group) { - list.Add(group.GetInfo()); + if (group.HasAccessToPlayQueue(user)) + { + list.Add(group.GetInfo()); + } } } } @@ -352,18 +357,18 @@ namespace Emby.Server.Implementations.SyncPlay return; } - _sessionManager.SessionControllerConnected -= OnSessionControllerConnected; + _sessionManager.SessionEnded -= OnSessionEnded; _disposed = true; } - private void OnSessionControllerConnected(object sender, SessionEventArgs e) + private void OnSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) { - var request = new JoinGroupRequest(group.GroupId); - JoinGroup(session, request, CancellationToken.None); + var leaveGroupRequest = new LeaveGroupRequest(); + LeaveGroup(session, leaveGroupRequest, CancellationToken.None); } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 839b62448..a837f09ca 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,8 +1,9 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -11,7 +12,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Series = MediaBrowser.Controller.Entities.TV.Series; @@ -23,12 +23,14 @@ namespace Emby.Server.Implementations.TV private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _configurationManager; - public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; + _configurationManager = configurationManager; } public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions) @@ -43,9 +45,7 @@ namespace Emby.Server.Implementations.TV string presentationUniqueKey = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); } @@ -95,9 +95,7 @@ namespace Emby.Server.Implementations.TV int? limit = null; if (!string.IsNullOrEmpty(request.SeriesId)) { - var series = _libraryManager.GetItemById(request.SeriesId) as Series; - - if (series != null) + if (_libraryManager.GetItemById(request.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); limit = 1; @@ -200,13 +198,10 @@ namespace Emby.Server.Implementations.TV ParentIndexNumberNotEquals = 0, DtoOptions = new DtoOptions { - Fields = new ItemFields[] - { - ItemFields.SortName - }, + Fields = new[] { ItemFields.SortName }, EnableImages = false } - }).FirstOrDefault(); + }).Cast<Episode>().FirstOrDefault(); Func<Episode> getEpisode = () => { @@ -224,6 +219,43 @@ namespace Emby.Server.Implementations.TV DtoOptions = dtoOptions }).Cast<Episode>().FirstOrDefault(); + if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) + { + var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = null, + SeriesPresentationUniqueKey = seriesKey, + ParentIndexNumber = 0, + IncludeItemTypes = new[] { nameof(Episode) }, + IsPlayed = false, + IsVirtualItem = false, + DtoOptions = dtoOptions + }) + .Cast<Episode>() + .Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null) + .ToList(); + + if (lastWatchedEpisode != null) + { + // Last watched episode is added, because there could be specials that aired before the last watched episode + consideredEpisodes.Add(lastWatchedEpisode); + } + + if (nextEpisode != null) + { + consideredEpisodes.Add(nextEpisode); + } + + var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) }) + .Cast<Episode>(); + if (lastWatchedEpisode != null) + { + sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1); + } + + nextEpisode = sortedConsideredEpisodes.FirstOrDefault(); + } + if (nextEpisode != null) { var userData = _userDataManager.GetUserData(user, nextEpisode); diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index d01184e0b..750f00168 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Net.Sockets; @@ -53,12 +55,7 @@ namespace Emby.Server.Implementations.Udp if (!string.IsNullOrEmpty(localUrl)) { - var response = new ServerDiscoveryInfo - { - Address = localUrl, - Id = _appHost.SystemId, - Name = _appHost.FriendlyName - }; + var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName); try { diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index fc34f93cd..2351b7d8c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable using System; @@ -22,6 +24,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Logging; @@ -194,7 +197,7 @@ namespace Emby.Server.Implementations.Updates var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber); if (plugin != null) { - await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path); + await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false); } // Remove versions with a target ABI greater then the current application version. @@ -500,7 +503,8 @@ namespace Emby.Server.Implementations.Updates var plugins = _pluginManager.Plugins; foreach (var plugin in plugins) { - if (plugin.Manifest?.AutoUpdate == false) + // Don't auto update when plugin marked not to, or when it's disabled. + if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled) { continue; } @@ -515,7 +519,7 @@ namespace Emby.Server.Implementations.Updates } } - private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken) { var extension = Path.GetExtension(package.SourceUrl); if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) @@ -567,7 +571,7 @@ namespace Emby.Server.Implementations.Updates stream.Position = 0; _zipClient.ExtractAllFromZip(stream, targetDir, true); - await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir); + await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false); _pluginManager.ImportPluginFrom(targetDir); } @@ -576,7 +580,7 @@ namespace Emby.Server.Implementations.Updates LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version)) ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); - await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); + await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false); _logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version); return plugin != null; |
