aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs87
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs13
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs27
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs77
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs7
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs6
-rw-r--r--Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs4
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs50
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs12
11 files changed, 166 insertions, 122 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 3846de5fd..0512adf10 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -210,7 +210,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 +233,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 +249,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 +283,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.");
}
}
@@ -545,7 +522,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 +554,6 @@ namespace Emby.Server.Implementations
};
Certificate = GetCertificate(CertificateInfo);
- DiscoverTypes();
-
RegisterServices();
_pluginManager.RegisterServices(ServiceCollection);
@@ -579,7 +568,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 +596,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IServerApplicationHost>(this);
ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
- ServiceCollection.AddSingleton(ServerConfigurationManager);
-
ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
@@ -796,7 +784,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>();
@@ -818,13 +806,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();
@@ -928,7 +915,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)
@@ -937,10 +924,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;
}
@@ -1156,7 +1143,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)
@@ -1240,14 +1227,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/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 2ae805447..694805ebe 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -5415,7 +5415,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 +5440,7 @@ AND Type = @InternalPersonType)");
var outerQuery = new InternalItemsQuery(query.User)
{
+ IsPlayed = query.IsPlayed,
IsFavorite = query.IsFavorite,
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
IsLiked = query.IsLiked,
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 679795dd2..3893a1577 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -249,9 +249,18 @@ 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;
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index c18838caf..6a9f4174d 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -196,13 +196,13 @@ 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.
@@ -214,15 +214,15 @@ namespace Emby.Server.Implementations.Library
/// 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; }
@@ -1914,12 +1914,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 (InvalidOperationException)
+ catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
{
- _logger.LogWarning("Cannot fetch image from {0}", img.Path);
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
+ continue;
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode);
continue;
}
}
@@ -1932,7 +1937,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 +1949,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 +1959,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);
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index c76d41e5c..5f051321f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -63,7 +63,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/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 20a4d87fb..a7fda1d72 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -130,9 +130,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 +171,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 +183,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 +197,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 +237,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 +290,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 +353,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)
{
- returnVal = string.Empty;
+ return TryGetReturnValueOfGetSet(buffer, out var value)
+ && string.Equals(Encoding.UTF8.GetString(value), expected, StringComparison.OrdinalIgnoreCase);
+ }
- if (numBytes < 4)
+ internal static bool TryGetReturnValueOfGetSet(ReadOnlySpan<byte> buffer, out ReadOnlySpan<byte> value)
+ {
+ value = ReadOnlySpan<byte>.Empty;
+
+ 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/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 700396c4c..3a8296455 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -368,7 +369,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 +412,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/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index a145a8423..3cc2cefb9 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -178,7 +178,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);
}
}
}
@@ -578,7 +579,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/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 1f68a9c81..60698e803 100644
--- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -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/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 839b62448..d3f6fa34d 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -11,7 +10,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 +21,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)
@@ -200,13 +200,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 +221,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/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index fc34f93cd..653b1381b 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -22,6 +22,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 +195,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 +501,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 +517,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 +569,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 +578,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;