aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs1
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs1
-rw-r--r--Emby.Dlna/PlayTo/Device.cs6
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs47
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs7
-rw-r--r--Emby.Dlna/PlayTo/PlaylistItemFactory.cs4
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs3
-rw-r--r--Emby.Dlna/PlayTo/TransportCommands.cs16
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs41
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs8
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs7
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs14
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json3
-rw-r--r--Emby.Server.Implementations/Localization/countries.json6
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs6
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs31
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs43
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs8
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs2
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs8
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs8
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs4
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs2
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs8
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs22
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs9
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs3
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs13
-rw-r--r--Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs78
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs8
-rw-r--r--Jellyfin.Server/Startup.cs6
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs51
-rw-r--r--MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs9
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs215
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj1
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs16
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs10
-rw-r--r--MediaBrowser.Model/Dlna/ContainerProfile.cs2
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs2
-rw-r--r--MediaBrowser.Model/Search/SearchQuery.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs23
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs2
54 files changed, 517 insertions, 297 deletions
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index b93651746..27f1fdaba 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1681,7 +1681,6 @@ namespace Emby.Dlna.ContentDirectory
private ServerItem GetItemFromObjectId(string id)
{
return DidlBuilder.IsIdRoot(id)
-
? new ServerItem(_libraryManager.GetUserRootFolder())
: ParseItemId(id);
}
diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
index 1dc9c79c1..56788ae22 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
-using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar
{
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index f8ff03076..938ce5fbf 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -12,8 +12,6 @@ using System.Xml;
using System.Xml.Linq;
using Emby.Dlna.Common;
using Emby.Dlna.Ssdp;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
@@ -345,7 +343,7 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
- private string CreateDidlMeta(string value)
+ private static string CreateDidlMeta(string value)
{
if (string.IsNullOrEmpty(value))
{
@@ -962,7 +960,7 @@ namespace Emby.Dlna.PlayTo
url = "/dmr/" + url;
}
- if (!url.StartsWith("/", StringComparison.Ordinal))
+ if (!url.StartsWith('/'))
{
url = "/" + url;
}
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 3907b2a39..b7cd91a5c 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -9,7 +9,6 @@ using System.Threading.Tasks;
using Emby.Dlna.Didl;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
@@ -41,7 +40,6 @@ namespace Emby.Dlna.PlayTo
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IConfigurationManager _config;
private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery;
@@ -68,7 +66,6 @@ namespace Emby.Dlna.PlayTo
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
- IConfigurationManager config,
IMediaEncoder mediaEncoder)
{
_session = session;
@@ -84,7 +81,6 @@ namespace Emby.Dlna.PlayTo
_userDataManager = userDataManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;
- _config = config;
_mediaEncoder = mediaEncoder;
}
@@ -337,25 +333,17 @@ namespace Emby.Dlna.PlayTo
}
var startIndex = command.StartIndex ?? 0;
+ int len = items.Count - startIndex;
if (startIndex > 0)
{
- items = items.GetRange(startIndex, items.Count - startIndex);
+ items = items.GetRange(startIndex, len);
}
- var playlist = new List<PlaylistItem>();
- var isFirst = true;
-
- foreach (var item in items)
+ var playlist = new PlaylistItem[len];
+ playlist[0] = CreatePlaylistItem(items[0], user, command.StartPositionTicks.Value, command.MediaSourceId, command.AudioStreamIndex, command.SubtitleStreamIndex);
+ for (int i = 1; i < len; i++)
{
- if (isFirst && command.StartPositionTicks.HasValue)
- {
- playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, command.MediaSourceId, command.AudioStreamIndex, command.SubtitleStreamIndex));
- isFirst = false;
- }
- else
- {
- playlist.Add(CreatePlaylistItem(item, user, 0, null, null, null));
- }
+ playlist[i] = CreatePlaylistItem(items[i], user, 0, null, null, null);
}
_logger.LogDebug("{0} - Playlist created", _session.DeviceName);
@@ -468,8 +456,8 @@ namespace Emby.Dlna.PlayTo
_dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources
- ? _mediaSourceManager.GetStaticMediaSources(item, true, user)
- : new List<MediaSourceInfo>();
+ ? _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray()
+ : Array.Empty<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
@@ -548,7 +536,7 @@ namespace Emby.Dlna.PlayTo
return null;
}
- private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
+ private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
{
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
@@ -557,7 +545,7 @@ namespace Emby.Dlna.PlayTo
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions
{
ItemId = item.Id,
- MediaSources = mediaSources.ToArray(),
+ MediaSources = mediaSources,
Profile = profile,
DeviceId = deviceId,
MaxBitrate = profile.MaxStreamingBitrate,
@@ -577,7 +565,7 @@ namespace Emby.Dlna.PlayTo
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions
{
ItemId = item.Id,
- MediaSources = mediaSources.ToArray(),
+ MediaSources = mediaSources,
Profile = profile,
DeviceId = deviceId,
MaxBitrate = profile.MaxStreamingBitrate,
@@ -590,7 +578,7 @@ namespace Emby.Dlna.PlayTo
if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
- return new PlaylistItemFactory().Create((Photo)item, profile);
+ return PlaylistItemFactory.Create((Photo)item, profile);
}
throw new ArgumentException("Unrecognized item type.");
@@ -774,13 +762,14 @@ namespace Emby.Dlna.PlayTo
private async Task SeekAfterTransportChange(long positionTicks, CancellationToken cancellationToken)
{
- const int maxWait = 15000000;
- const int interval = 500;
+ const int MaxWait = 15000000;
+ const int Interval = 500;
+
var currentWait = 0;
- while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
+ while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
{
- await Task.Delay(interval).ConfigureAwait(false);
- currentWait += interval;
+ await Task.Delay(Interval).ConfigureAwait(false);
+ currentWait += Interval;
}
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index cb183ce71..f34332d62 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -90,10 +90,10 @@ namespace Emby.Dlna.PlayTo
string location = info.Location.ToString();
// It has to report that it's a media renderer
- if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
- nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
+ if (!usn.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase)
+ && !nt.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase))
{
- // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
+ _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return;
}
@@ -203,7 +203,6 @@ namespace Emby.Dlna.PlayTo
_userDataManager,
_localization,
_mediaSourceManager,
- _config,
_mediaEncoder);
sessionInfo.AddController(controller);
diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
index bedc8b9ad..e28840a89 100644
--- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
+++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
@@ -8,9 +8,9 @@ using MediaBrowser.Model.Session;
namespace Emby.Dlna.PlayTo
{
- public class PlaylistItemFactory
+ public static class PlaylistItemFactory
{
- public PlaylistItem Create(Photo item, DeviceProfile profile)
+ public static PlaylistItem Create(Photo item, DeviceProfile profile)
{
var playlistItem = new PlaylistItem
{
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index f4d793790..557bc69a7 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -4,7 +4,6 @@ using System;
using System.Globalization;
using System.IO;
using System.Net.Http;
-using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Threading;
@@ -60,7 +59,7 @@ namespace Emby.Dlna.PlayTo
return serviceUrl;
}
- if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
+ if (!serviceUrl.StartsWith('/'))
{
serviceUrl = "/" + serviceUrl;
}
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index fda17a8b4..0865968ad 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -78,7 +78,7 @@ namespace Emby.Dlna.PlayTo
private static StateVariable FromXml(XElement container)
{
- var allowedValues = new List<string>();
+ var allowedValues = Array.Empty<string>();
var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList")
.FirstOrDefault();
@@ -86,14 +86,14 @@ namespace Emby.Dlna.PlayTo
{
var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue");
- allowedValues.AddRange(values.Select(child => child.Value));
+ allowedValues = values.Select(child => child.Value).ToArray();
}
return new StateVariable
{
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
- AllowedValues = allowedValues.ToArray()
+ AllowedValues = allowedValues
};
}
@@ -103,12 +103,12 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
- if (arg.Direction == "out")
+ if (string.Equals(arg.Direction, "out", StringComparison.Ordinal))
{
continue;
}
- if (arg.Name == "InstanceID")
+ if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
{
stateString += BuildArgumentXml(arg, "0");
}
@@ -127,12 +127,12 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
- if (arg.Direction == "out")
+ if (string.Equals(arg.Direction, "out", StringComparison.Ordinal))
{
continue;
}
- if (arg.Name == "InstanceID")
+ if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
{
stateString += BuildArgumentXml(arg, "0");
}
@@ -151,7 +151,7 @@ namespace Emby.Dlna.PlayTo
foreach (var arg in action.ArgumentList)
{
- if (arg.Name == "InstanceID")
+ if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
{
stateString += BuildArgumentXml(arg, "0");
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 29c93faa6..d74ea0352 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1,21 +1,18 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml.Serialization;
using Emby.Dlna;
using Emby.Dlna.Main;
using Emby.Dlna.Ssdp;
@@ -52,7 +49,6 @@ using Jellyfin.Networking.Manager;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@@ -85,7 +81,6 @@ using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
@@ -100,7 +95,6 @@ using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.Tmdb;
using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
-using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@@ -128,7 +122,6 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager;
- private IHttpClientFactory _httpClientFactory;
private string[] _urlPrefixes;
/// <summary>
@@ -190,6 +183,8 @@ namespace Emby.Server.Implementations
private IPlugin[] _plugins;
+ private IReadOnlyList<LocalPlugin> _pluginsManifests;
+
/// <summary>
/// Gets the plugins.
/// </summary>
@@ -659,7 +654,6 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>();
- _httpClientFactory = Resolve<IHttpClientFactory>();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
@@ -780,17 +774,27 @@ namespace Emby.Server.Implementations
if (Plugins != null)
{
- var pluginBuilder = new StringBuilder();
-
foreach (var plugin in Plugins)
{
- pluginBuilder.Append(plugin.Name)
- .Append(' ')
- .Append(plugin.Version)
- .AppendLine();
- }
+ if (_pluginsManifests != null && plugin is IPluginAssembly assemblyPlugin)
+ {
+ // Ensure the version number matches the Plugin Manifest information.
+ foreach (var item in _pluginsManifests)
+ {
+ if (Path.GetDirectoryName(plugin.AssemblyFilePath).Equals(item.Path, StringComparison.OrdinalIgnoreCase))
+ {
+ // Update version number to that of the manifest.
+ assemblyPlugin.SetAttributes(
+ plugin.AssemblyFilePath,
+ Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(plugin.AssemblyFilePath)),
+ item.Version);
+ break;
+ }
+ }
+ }
- Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
+ Logger.LogInformation("Loaded plugin: {PluginName} {PluginVersion}", plugin.Name, plugin.Version);
+ }
}
_urlPrefixes = GetUrlPrefixes().ToArray();
@@ -1045,7 +1049,7 @@ namespace Emby.Server.Implementations
metafile = dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)[^1];
int versionIndex = dir.LastIndexOf('_');
- if (versionIndex != -1 && Version.TryParse(dir.Substring(versionIndex + 1), out Version parsedVersion))
+ if (versionIndex != -1 && Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version parsedVersion))
{
// Versioned folder.
versions.Add(new LocalPlugin(Guid.Empty, metafile, parsedVersion, dir));
@@ -1104,7 +1108,8 @@ namespace Emby.Server.Implementations
{
if (Directory.Exists(ApplicationPaths.PluginsPath))
{
- foreach (var plugin in GetLocalPlugins(ApplicationPaths.PluginsPath))
+ _pluginsManifests = GetLocalPlugins(ApplicationPaths.PluginsPath).ToList();
+ foreach (var plugin in _pluginsManifests)
{
foreach (var file in plugin.DllFiles)
{
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 3d97a6ca8..57684a429 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -540,18 +540,18 @@ namespace Emby.Server.Implementations.Channels
{
IncludeItemTypes = new[] { nameof(Channel) },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
- }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
+ }).Select(i => GetChannelFeatures(i)).ToArray();
}
/// <inheritdoc />
- public ChannelFeatures GetChannelFeatures(string id)
+ public ChannelFeatures GetChannelFeatures(Guid? id)
{
- if (string.IsNullOrEmpty(id))
+ if (!id.HasValue)
{
throw new ArgumentNullException(nameof(id));
}
- var channel = GetChannel(id);
+ var channel = GetChannel(id.Value);
var channelProvider = GetChannelProvider(channel);
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index ff64e217a..ae1b51b4c 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
private readonly List<BaseItem> _itemsRemoved = new List<BaseItem>();
private readonly List<BaseItem> _itemsUpdated = new List<BaseItem>();
- private readonly Dictionary<Guid, DateTime> _lastProgressMessageTimes = new Dictionary<Guid, DateTime>();
+ private readonly ConcurrentDictionary<Guid, DateTime> _lastProgressMessageTimes = new ConcurrentDictionary<Guid, DateTime>();
public LibraryChangedNotifier(
ILibraryManager libraryManager,
@@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- _lastProgressMessageTimes[item.Id] = DateTime.UtcNow;
+ _lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
@@ -140,6 +141,8 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnProviderRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
{
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
+
+ _lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
}
private static bool EnableRefreshMessage(BaseItem item)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 013781258..5b926b0f4 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2462,9 +2462,19 @@ namespace Emby.Server.Implementations.Library
public BaseItem GetParentItem(string parentId, Guid? userId)
{
- if (!string.IsNullOrEmpty(parentId))
+ if (string.IsNullOrEmpty(parentId))
{
- return GetItemById(new Guid(parentId));
+ return GetParentItem((Guid?)null, userId);
+ }
+
+ return GetParentItem(new Guid(parentId), userId);
+ }
+
+ public BaseItem GetParentItem(Guid? parentId, Guid? userId)
+ {
+ if (parentId.HasValue)
+ {
+ return GetItemById(parentId.Value);
}
if (userId.HasValue && userId != Guid.Empty)
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index c850e3a08..1d9529dff 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -156,8 +156,8 @@ namespace Emby.Server.Implementations.Library
ExcludeItemTypes = excludeItemTypes.ToArray(),
IncludeItemTypes = includeItemTypes.ToArray(),
Limit = query.Limit,
- IncludeItemsByName = string.IsNullOrEmpty(query.ParentId),
- ParentId = string.IsNullOrEmpty(query.ParentId) ? Guid.Empty : new Guid(query.ParentId),
+ IncludeItemsByName = !query.ParentId.HasValue,
+ ParentId = query.ParentId ?? Guid.Empty,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Recursive = true,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index c0a4d1228..b6444b172 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -237,8 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!inside)
{
- buffer[bufferIndex] = let;
- bufferIndex++;
+ buffer[bufferIndex++] = let;
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 1d6c26c13..c82b67b41 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
extInf = line.Substring(ExtInfPrefix.Length).Trim();
_logger.LogInformation("Found m3u channel: {0}", extInf);
}
- else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
+ else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#'))
{
var channel = GetChannelnfo(extInf, tunerHostId, line);
if (string.IsNullOrWhiteSpace(channel.Id))
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index ab54c0ea6..05181116d 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -113,5 +113,9 @@
"TasksChannelsCategory": "Canales de Internet",
"TasksApplicationCategory": "Aplicación",
"TasksLibraryCategory": "Biblioteca",
- "TasksMaintenanceCategory": "Mantenimiento"
+ "TasksMaintenanceCategory": "Mantenimiento",
+ "TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.",
+ "TaskCleanActivityLog": "Limpiar registro de actividades",
+ "Undefined": "Sin definir",
+ "Forced": "Forzado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 61bef29ed..954759b5c 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -114,5 +114,8 @@
"TasksApplicationCategory": "Sovellus",
"TasksLibraryCategory": "Kirjasto",
"Forced": "Pakotettu",
- "Default": "Oletus"
+ "Default": "Oletus",
+ "TaskCleanActivityLogDescription": "Poistaa määritettyä vanhemmat tapahtumat aktiviteettilokista.",
+ "TaskCleanActivityLog": "Tyhjennä aktiviteettiloki",
+ "Undefined": "Määrittelemätön"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 3d7592e3c..5aa65a525 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -113,5 +113,6 @@
"TaskCleanCache": "Nettoyer le répertoire des fichiers temporaires",
"TasksApplicationCategory": "Application",
"TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système.",
- "TasksChannelsCategory": "Canaux Internet"
+ "TasksChannelsCategory": "Canaux Internet",
+ "Default": "Par défaut"
}
diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json
index 581e9f835..b08a3ae79 100644
--- a/Emby.Server.Implementations/Localization/countries.json
+++ b/Emby.Server.Implementations/Localization/countries.json
@@ -558,6 +558,12 @@
"TwoLetterISORegionName": "OM"
},
{
+ "DisplayName": "Palestine",
+ "Name": "PS",
+ "ThreeLetterISORegionName": "PSE",
+ "TwoLetterISORegionName": "PS"
+ },
+ {
"DisplayName": "Panama",
"Name": "PA",
"ThreeLetterISORegionName": "PAN",
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 3cfcecbd1..447c587f9 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -56,13 +56,11 @@ namespace Emby.Server.Implementations.TV
return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request);
}
- var parentIdGuid = string.IsNullOrEmpty(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
-
BaseItem[] parents;
- if (parentIdGuid.HasValue)
+ if (request.ParentId.HasValue)
{
- var parent = _libraryManager.GetItemById(parentIdGuid.Value);
+ var parent = _libraryManager.GetItemById(request.ParentId.Value);
if (parent != null)
{
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index c65dc8620..fed7ed3e5 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
@@ -87,7 +86,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -119,16 +118,11 @@ namespace Jellyfin.Api.Controllers
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
- BaseItem parentItem;
+ BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
if (userId.HasValue && !userId.Equals(Guid.Empty))
{
user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
}
var query = new InternalItemsQuery(user)
@@ -157,15 +151,15 @@ namespace Jellyfin.Api.Controllers
EnableTotalRecordCount = enableTotalRecordCount
};
- if (!string.IsNullOrWhiteSpace(parentId))
+ if (parentId.HasValue)
{
if (parentItem is Folder)
{
- query.AncestorIds = new[] { new Guid(parentId) };
+ query.AncestorIds = new[] { parentId.Value };
}
else
{
- query.ItemIds = new[] { new Guid(parentId) };
+ query.ItemIds = new[] { parentId.Value };
}
}
@@ -291,7 +285,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -323,16 +317,11 @@ namespace Jellyfin.Api.Controllers
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
- BaseItem parentItem;
+ BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
if (userId.HasValue && !userId.Equals(Guid.Empty))
{
user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
}
var query = new InternalItemsQuery(user)
@@ -361,15 +350,15 @@ namespace Jellyfin.Api.Controllers
EnableTotalRecordCount = enableTotalRecordCount
};
- if (!string.IsNullOrWhiteSpace(parentId))
+ if (parentId.HasValue)
{
if (parentItem is Folder)
{
- query.AncestorIds = new[] { new Guid(parentId) };
+ query.AncestorIds = new[] { parentId.Value };
}
else
{
- query.ItemIds = new[] { new Guid(parentId) };
+ query.ItemIds = new[] { parentId.Value };
}
}
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index c4dc44cc3..b70c76e80 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Channel features returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
[HttpGet("{channelId}/Features")]
- public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] string channelId)
+ public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] Guid channelId)
{
return _channelManager.GetChannelFeatures(channelId);
}
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 31cb9e273..9220b988f 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -50,33 +50,24 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
[FromQuery] Guid? userId,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes)
{
- var parentItem = string.IsNullOrEmpty(parentId)
- ? null
- : _libraryManager.GetItemById(parentId);
-
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
- if (includeItemTypes.Length == 1
- && (string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase)))
+ BaseItem? item = null;
+ if (includeItemTypes.Length != 1
+ || !(string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase)))
{
- parentItem = null;
+ item = _libraryManager.GetParentItem(parentId, user?.Id);
}
- var item = string.IsNullOrEmpty(parentId)
- ? user == null
- ? _libraryManager.RootFolder
- : _libraryManager.GetUserRootFolder()
- : parentItem;
-
var query = new InternalItemsQuery
{
User = user,
@@ -92,7 +83,12 @@ namespace Jellyfin.Api.Controllers
}
};
- var itemList = ((Folder)item!).GetItemList(query);
+ if (item is not Folder folder)
+ {
+ return new QueryFiltersLegacy();
+ }
+
+ var itemList = folder.GetItemList(query);
return new QueryFiltersLegacy
{
Years = itemList.Select(i => i.ProductionYear ?? -1)
@@ -140,7 +136,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryFilters> GetQueryFilters(
[FromQuery] Guid? userId,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
[FromQuery] bool? isAiring,
[FromQuery] bool? isMovie,
@@ -150,14 +146,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isSeries,
[FromQuery] bool? recursive)
{
- var parentItem = string.IsNullOrEmpty(parentId)
- ? null
- : _libraryManager.GetItemById(parentId);
-
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
: null;
+ BaseItem? parentItem = null;
if (includeItemTypes.Length == 1
&& (string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
|| string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase)
@@ -166,6 +159,10 @@ namespace Jellyfin.Api.Controllers
{
parentItem = null;
}
+ else if (parentId.HasValue)
+ {
+ parentItem = _libraryManager.GetItemById(parentId.Value);
+ }
var filters = new QueryFilters();
var genreQuery = new InternalItemsQuery(user)
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index d2b41e0a8..b6755ed5e 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -109,15 +109,15 @@ namespace Jellyfin.Api.Controllers
EnableTotalRecordCount = enableTotalRecordCount
};
- if (!string.IsNullOrWhiteSpace(parentId))
+ if (parentId.HasValue)
{
if (parentItem is Folder)
{
- query.AncestorIds = new[] { new Guid(parentId) };
+ query.AncestorIds = new[] { parentId.Value };
}
else
{
- query.ItemIds = new[] { new Guid(parentId) };
+ query.ItemIds = new[] { parentId.Value };
}
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index b0979fbcf..7e9035f80 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
[FromQuery] string? sortOrder,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -239,14 +239,8 @@ namespace Jellyfin.Api.Controllers
parentId = null;
}
- BaseItem? item = null;
+ var item = _libraryManager.GetParentItem(parentId, userId);
QueryResult<BaseItem> result;
- if (!string.IsNullOrEmpty(parentId))
- {
- item = _libraryManager.GetItemById(parentId);
- }
-
- item ??= _libraryManager.GetUserRootFolder();
if (!(item is Folder folder))
{
@@ -343,7 +337,7 @@ namespace Jellyfin.Api.Controllers
ItemIds = ids,
MinCommunityRating = minCommunityRating,
MinCriticRating = minCriticRating,
- ParentId = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId),
+ ParentId = parentId ?? Guid.Empty,
ParentIndexNumber = parentIndexNumber,
EnableTotalRecordCount = enableTotalRecordCount,
ExcludeItemIds = excludeItemIds,
@@ -615,7 +609,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
[FromQuery] string? sortOrder,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -773,7 +767,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery] bool? enableUserData,
@@ -785,7 +779,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableImages = true)
{
var user = _userManager.GetUserById(userId);
- var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
+ var parentIdGuid = parentId ?? Guid.Empty;
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 3ff77e8e0..184843b39 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -362,7 +362,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] ids)
+ public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
{
if (ids.Length == 0)
{
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index 75dfd4e68..4d788ad7d 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Recommendations")]
public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
[FromQuery] Guid? userId,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] int categoryLimit = 5,
[FromQuery] int itemLimit = 8)
@@ -78,7 +78,7 @@ namespace Jellyfin.Api.Controllers
var categories = new List<RecommendationDto>();
- var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
+ var parentIdGuid = parentId ?? Guid.Empty;
var query = new InternalItemsQuery(user)
{
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index e7d0a61c5..2608a9cd0 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -109,15 +109,15 @@ namespace Jellyfin.Api.Controllers
EnableTotalRecordCount = enableTotalRecordCount
};
- if (!string.IsNullOrWhiteSpace(parentId))
+ if (parentId.HasValue)
{
if (parentItem is Folder)
{
- query.AncestorIds = new[] { new Guid(parentId) };
+ query.AncestorIds = new[] { parentId.Value };
}
else
{
- query.ItemIds = new[] { new Guid(parentId) };
+ query.ItemIds = new[] { parentId.Value };
}
}
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 83b359766..6295dfc05 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -45,13 +45,13 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PackageInfo>> GetPackageInfo(
[FromRoute, Required] string name,
- [FromQuery] string? assemblyGuid)
+ [FromQuery] Guid? assemblyGuid)
{
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
var result = _installationManager.FilterPackages(
packages,
name,
- string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid))
+ assemblyGuid ?? default)
.FirstOrDefault();
if (result == null)
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> InstallPackage(
[FromRoute, Required] string name,
- [FromQuery] string? assemblyGuid,
+ [FromQuery] Guid? assemblyGuid,
[FromQuery] string? version,
[FromQuery] string? repositoryUrl)
{
@@ -106,7 +106,7 @@ namespace Jellyfin.Api.Controllers
var package = _installationManager.GetCompatibleVersions(
packages,
name,
- string.IsNullOrEmpty(assemblyGuid) ? Guid.Empty : Guid.Parse(assemblyGuid),
+ assemblyGuid ?? Guid.Empty,
specificVersion: string.IsNullOrEmpty(version) ? null : Version.Parse(version))
.FirstOrDefault();
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index aaad36551..17e631197 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludePersonTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
- [FromQuery] string? appearsInItemId,
+ [FromQuery] Guid? appearsInItemId,
[FromQuery] Guid? userId,
[FromQuery] bool? enableImages = true)
{
@@ -102,7 +102,7 @@ namespace Jellyfin.Api.Controllers
NameContains = searchTerm,
User = user,
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
- AppearsInItemId = string.IsNullOrEmpty(appearsInItemId) ? Guid.Empty : Guid.Parse(appearsInItemId),
+ AppearsInItemId = appearsInItemId ?? Guid.Empty,
Limit = limit ?? 0
});
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 076fe58f1..08255ff8f 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
[FromQuery] bool? isNews,
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 5090bf1de..bb54c59f6 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -109,15 +109,15 @@ namespace Jellyfin.Api.Controllers
EnableTotalRecordCount = enableTotalRecordCount
};
- if (!string.IsNullOrWhiteSpace(parentId))
+ if (parentId.HasValue)
{
if (parentItem is Folder)
{
- query.AncestorIds = new[] { new Guid(parentId) };
+ query.AncestorIds = new[] { parentId.Value };
}
else
{
- query.ItemIds = new[] { new Guid(parentId) };
+ query.ItemIds = new[] { parentId.Value };
}
}
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 5b71fed5a..8e9ece14f 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? recursive,
[FromQuery] string? searchTerm,
[FromQuery] string? sortOrder,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 57b056f50..03fd1846d 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -76,7 +76,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] string? seriesId,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery] bool? enableImges,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery] bool? enableImges,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
- var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId);
+ var parentIdGuid = parentId ?? Guid.Empty;
var options = new DtoOptions { Fields = fields }
.AddClientFields(Request)
@@ -194,14 +194,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
- [FromRoute, Required] string seriesId,
+ [FromRoute, Required] Guid seriesId,
[FromQuery] Guid? userId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] int? season,
- [FromQuery] string? seasonId,
+ [FromQuery] Guid? seasonId,
[FromQuery] bool? isMissing,
[FromQuery] string? adjacentTo,
- [FromQuery] string? startItemId,
+ [FromQuery] Guid? startItemId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] bool? enableImages,
@@ -220,9 +220,9 @@ namespace Jellyfin.Api.Controllers
.AddClientFields(Request)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes!);
- if (!string.IsNullOrWhiteSpace(seasonId)) // Season id was supplied. Get episodes by season id.
+ if (seasonId.HasValue) // Season id was supplied. Get episodes by season id.
{
- var item = _libraryManager.GetItemById(new Guid(seasonId));
+ var item = _libraryManager.GetItemById(seasonId.Value);
if (!(item is Season seasonItem))
{
return NotFound("No season exists with Id " + seasonId);
@@ -264,10 +264,10 @@ namespace Jellyfin.Api.Controllers
.ToList();
}
- if (!string.IsNullOrWhiteSpace(startItemId))
+ if (startItemId.HasValue)
{
episodes = episodes
- .SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), startItemId, StringComparison.OrdinalIgnoreCase))
+ .SkipWhile(i => startItemId.Value.Equals(i.Id))
.ToList();
}
@@ -316,7 +316,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
- [FromRoute, Required] string seriesId,
+ [FromRoute, Required] Guid seriesId,
[FromQuery] Guid? userId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool? isSpecialSeason,
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index ec7c3de97..48c639b08 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? sortOrder,
- [FromQuery] string? parentId,
+ [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
@@ -89,16 +89,11 @@ namespace Jellyfin.Api.Controllers
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = null;
- BaseItem parentItem;
+ BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
if (userId.HasValue && !userId.Equals(Guid.Empty))
{
user = _userManager.GetUserById(userId.Value);
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(parentId);
- }
- else
- {
- parentItem = string.IsNullOrEmpty(parentId) ? _libraryManager.RootFolder : _libraryManager.GetItemById(parentId);
}
IList<BaseItem> items;
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index 1a5614b7b..85da927fb 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -256,7 +256,7 @@ namespace Jellyfin.Networking.Manager
}
catch (ArgumentException e)
{
- _logger.LogWarning(e, "Ignoring LAN value {value}.", v);
+ _logger.LogWarning(e, "Ignoring LAN value {Value}.", v);
}
}
@@ -668,7 +668,6 @@ namespace Jellyfin.Networking.Manager
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
int i = str.IndexOf("%", StringComparison.OrdinalIgnoreCase);
-
if (i != -1)
{
str = str.Substring(0, i);
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index e5b9620f7..618a4e92b 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -236,18 +236,6 @@ namespace Jellyfin.Server.Extensions
Description = "API key header parameter"
});
- var securitySchemeRef = new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = AuthenticationSchemes.CustomAuthentication },
- };
-
- // TODO: Apply this with an operation filter instead of globally
- // https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements
- c.AddSecurityRequirement(new OpenApiSecurityRequirement
- {
- { securitySchemeRef, Array.Empty<string>() }
- });
-
// Add all xml doc files to swagger generator.
var xmlFiles = Directory.GetFiles(
AppContext.BaseDirectory,
@@ -277,6 +265,7 @@ namespace Jellyfin.Server.Extensions
// TODO - remove when all types are supported in System.Text.Json
c.AddSwaggerTypeMappings();
+ c.OperationFilter<SecurityRequirementsOperationFilter>();
c.OperationFilter<FileResponseFilter>();
c.DocumentFilter<WebsocketModelFilter>();
});
diff --git a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
new file mode 100644
index 000000000..802662ce2
--- /dev/null
+++ b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Jellyfin.Api.Constants;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters
+{
+ /// <summary>
+ /// Security requirement operation filter.
+ /// </summary>
+ public class SecurityRequirementsOperationFilter : IOperationFilter
+ {
+ /// <inheritdoc />
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ var requiredScopes = new List<string>();
+
+ // Add all method scopes.
+ foreach (var attribute in context.MethodInfo.GetCustomAttributes(true))
+ {
+ if (attribute is AuthorizeAttribute authorizeAttribute
+ && authorizeAttribute.Policy != null
+ && !requiredScopes.Contains(authorizeAttribute.Policy, StringComparer.Ordinal))
+ {
+ requiredScopes.Add(authorizeAttribute.Policy);
+ }
+ }
+
+ // Add controller scopes if any.
+ var controllerAttributes = context.MethodInfo.DeclaringType?.GetCustomAttributes(true);
+ if (controllerAttributes != null)
+ {
+ foreach (var attribute in controllerAttributes)
+ {
+ if (attribute is AuthorizeAttribute authorizeAttribute
+ && authorizeAttribute.Policy != null
+ && !requiredScopes.Contains(authorizeAttribute.Policy, StringComparer.Ordinal))
+ {
+ requiredScopes.Add(authorizeAttribute.Policy);
+ }
+ }
+ }
+
+ if (requiredScopes.Count != 0)
+ {
+ if (!operation.Responses.ContainsKey("401"))
+ {
+ operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
+ }
+
+ if (!operation.Responses.ContainsKey("403"))
+ {
+ operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
+ }
+
+ var scheme = new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = AuthenticationSchemes.CustomAuthentication
+ }
+ };
+
+ operation.Security = new List<OpenApiSecurityRequirement>
+ {
+ new OpenApiSecurityRequirement
+ {
+ [scheme] = requiredScopes
+ }
+ };
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs
index 42b87ec5f..9137ea234 100644
--- a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs
@@ -35,8 +35,14 @@ namespace Jellyfin.Server.Migrations.Routines
_logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries");
foreach (var virtualFolder in virtualFolders)
{
+ // Some virtual folders don't have a proper item id.
+ if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
+ {
+ continue;
+ }
+
var libraryOptions = virtualFolder.LibraryOptions;
- var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(virtualFolder.ItemId);
+ var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(folderId);
// The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed.
collectionFolder.UpdateLibraryOptions(libraryOptions);
_logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name);
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index f144dd12e..7f1d332ee 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -104,7 +104,8 @@ namespace Jellyfin.Server
app.UseBaseUrlRedirection();
// Wrap rest of configuration so everything only listens on BaseUrl.
- app.Map(_serverConfigurationManager.GetNetworkConfiguration().BaseUrl, mainApp =>
+ var config = _serverConfigurationManager.GetNetworkConfiguration();
+ app.Map(config.BaseUrl, mainApp =>
{
if (env.IsDevelopment())
{
@@ -122,8 +123,7 @@ namespace Jellyfin.Server
mainApp.UseCors();
- if (_serverConfigurationManager.GetNetworkConfiguration().RequireHttps
- && _serverApplicationHost.ListenWithHttps)
+ if (config.RequireHttps && _serverApplicationHost.ListenWithHttps)
{
mainApp.UseHttpsRedirection();
}
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index 67aa7f338..085f769d0 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -1,5 +1,7 @@
-using System;
+using System;
using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -12,6 +14,8 @@ namespace MediaBrowser.Controller.BaseItemManager
{
private readonly IServerConfigurationManager _serverConfigurationManager;
+ private int _metadataRefreshConcurrency = 0;
+
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
/// </summary>
@@ -19,9 +23,17 @@ namespace MediaBrowser.Controller.BaseItemManager
public BaseItemManager(IServerConfigurationManager serverConfigurationManager)
{
_serverConfigurationManager = serverConfigurationManager;
+
+ _metadataRefreshConcurrency = GetMetadataRefreshConcurrency();
+ SetupMetadataThrottler();
+
+ _serverConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
}
/// <inheritdoc />
+ public SemaphoreSlim MetadataRefreshThrottler { get; private set; }
+
+ /// <inheritdoc />
public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
{
if (baseItem is Channel)
@@ -82,5 +94,42 @@ namespace MediaBrowser.Controller.BaseItemManager
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
}
+
+ /// <summary>
+ /// Called when the configuration is updated.
+ /// It will refresh the metadata throttler if the relevant config changed.
+ /// </summary>
+ private void OnConfigurationUpdated(object sender, EventArgs e)
+ {
+ int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
+ if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
+ {
+ _metadataRefreshConcurrency = newMetadataRefreshConcurrency;
+ SetupMetadataThrottler();
+ }
+ }
+
+ /// <summary>
+ /// Creates the metadata refresh throttler.
+ /// </summary>
+ private void SetupMetadataThrottler()
+ {
+ MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
+ }
+
+ /// <summary>
+ /// Returns the metadata refresh concurrency.
+ /// </summary>
+ private int GetMetadataRefreshConcurrency()
+ {
+ var concurrency = _serverConfigurationManager.Configuration.LibraryMetadataRefreshConcurrency;
+
+ if (concurrency <= 0)
+ {
+ concurrency = Environment.ProcessorCount;
+ }
+
+ return concurrency;
+ }
}
}
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index ee4d3dcdc..e1f5d05a6 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Entities;
+using System;
+using System.Threading;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.BaseItemManager
@@ -9,6 +11,11 @@ namespace MediaBrowser.Controller.BaseItemManager
public interface IBaseItemManager
{
/// <summary>
+ /// Gets the semaphore used to limit the amount of concurrent metadata refreshes.
+ /// </summary>
+ SemaphoreSlim MetadataRefreshThrottler { get; }
+
+ /// <summary>
/// Is metadata fetcher enabled.
/// </summary>
/// <param name="baseItem">The base item.</param>
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index 9a9d22d33..ddae7dbd3 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Channels
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>ChannelFeatures.</returns>
- ChannelFeatures GetChannelFeatures(string id);
+ ChannelFeatures GetChannelFeatures(Guid? id);
/// <summary>
/// Gets all channel features.
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 675cdbd96..23f4c00c1 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Progress;
@@ -328,11 +329,11 @@ namespace MediaBrowser.Controller.Entities
return;
}
- progress.Report(5);
+ progress.Report(ProgressHelpers.RetrievedChildren);
if (recursive)
{
- ProviderManager.OnRefreshProgress(this, 5);
+ ProviderManager.OnRefreshProgress(this, ProgressHelpers.RetrievedChildren);
}
// Build a dictionary of the current children we have now by Id so we can compare quickly and easily
@@ -388,11 +389,11 @@ namespace MediaBrowser.Controller.Entities
validChildrenNeedGeneration = true;
}
- progress.Report(10);
+ progress.Report(ProgressHelpers.UpdatedChildItems);
if (recursive)
{
- ProviderManager.OnRefreshProgress(this, 10);
+ ProviderManager.OnRefreshProgress(this, ProgressHelpers.UpdatedChildItems);
}
cancellationToken.ThrowIfCancellationRequested();
@@ -402,11 +403,13 @@ namespace MediaBrowser.Controller.Entities
var innerProgress = new ActionableProgress<double>();
var folder = this;
- innerProgress.RegisterAction(p =>
+ innerProgress.RegisterAction(innerPercent =>
{
- double newPct = 0.80 * p + 10;
- progress.Report(newPct);
- ProviderManager.OnRefreshProgress(folder, newPct);
+ var percent = ProgressHelpers.GetProgress(ProgressHelpers.UpdatedChildItems, ProgressHelpers.ScannedSubfolders, innerPercent);
+
+ progress.Report(percent);
+
+ ProviderManager.OnRefreshProgress(folder, percent);
});
if (validChildrenNeedGeneration)
@@ -420,11 +423,11 @@ namespace MediaBrowser.Controller.Entities
if (refreshChildMetadata)
{
- progress.Report(90);
+ progress.Report(ProgressHelpers.ScannedSubfolders);
if (recursive)
{
- ProviderManager.OnRefreshProgress(this, 90);
+ ProviderManager.OnRefreshProgress(this, ProgressHelpers.ScannedSubfolders);
}
var container = this as IMetadataContainer;
@@ -432,13 +435,15 @@ namespace MediaBrowser.Controller.Entities
var innerProgress = new ActionableProgress<double>();
var folder = this;
- innerProgress.RegisterAction(p =>
+ innerProgress.RegisterAction(innerPercent =>
{
- double newPct = 0.10 * p + 90;
- progress.Report(newPct);
+ var percent = ProgressHelpers.GetProgress(ProgressHelpers.ScannedSubfolders, ProgressHelpers.RefreshedMetadata, innerPercent);
+
+ progress.Report(percent);
+
if (recursive)
{
- ProviderManager.OnRefreshProgress(folder, newPct);
+ ProviderManager.OnRefreshProgress(folder, percent);
}
});
@@ -453,55 +458,35 @@ namespace MediaBrowser.Controller.Entities
validChildren = Children.ToList();
}
- await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken);
+ await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken).ConfigureAwait(false);
}
}
}
- private async Task RefreshMetadataRecursive(List<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
+ private Task RefreshMetadataRecursive(IList<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
- var numComplete = 0;
- var count = children.Count;
- double currentPercent = 0;
-
- foreach (var child in children)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var innerProgress = new ActionableProgress<double>();
-
- // Avoid implicitly captured closure
- var currentInnerPercent = currentPercent;
-
- innerProgress.RegisterAction(p =>
- {
- double innerPercent = currentInnerPercent;
- innerPercent += p / count;
- progress.Report(innerPercent);
- });
-
- await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken)
- .ConfigureAwait(false);
-
- numComplete++;
- double percent = numComplete;
- percent /= count;
- percent *= 100;
- currentPercent = percent;
-
- progress.Report(percent);
- }
+ return RunTasks(
+ (baseItem, innerProgress) => RefreshChildMetadata(baseItem, refreshOptions, recursive && baseItem.IsFolder, innerProgress, cancellationToken),
+ children,
+ progress,
+ cancellationToken);
}
private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
- var series = container as Series;
- if (series != null)
- {
- await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
- }
+ // limit the amount of concurrent metadata refreshes
+ await ProviderManager.RunMetadataRefresh(
+ async () =>
+ {
+ var series = container as Series;
+ if (series != null)
+ {
+ await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ }
- await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
+ await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
+ },
+ cancellationToken).ConfigureAwait(false);
}
private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
@@ -516,12 +501,15 @@ namespace MediaBrowser.Controller.Entities
{
if (refreshOptions.RefreshItem(child))
{
- await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ // limit the amount of concurrent metadata refreshes
+ await ProviderManager.RunMetadataRefresh(
+ async () => await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false),
+ cancellationToken).ConfigureAwait(false);
}
if (recursive && child is Folder folder)
{
- await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken);
+ await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -534,39 +522,72 @@ namespace MediaBrowser.Controller.Entities
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
+ private Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
{
- var numComplete = 0;
- var count = children.Count;
- double currentPercent = 0;
+ return RunTasks(
+ (folder, innerProgress) => folder.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService),
+ children,
+ progress,
+ cancellationToken);
+ }
- foreach (var child in children)
+ /// <summary>
+ /// Runs an action block on a list of children.
+ /// </summary>
+ /// <param name="task">The task to run for each child.</param>
+ /// <param name="children">The list of children.</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ private async Task RunTasks<T>(Func<T, IProgress<double>, Task> task, IList<T> children, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var childrenCount = children.Count;
+ var childrenProgress = new double[childrenCount];
+
+ void UpdateProgress()
{
- cancellationToken.ThrowIfCancellationRequested();
+ progress.Report(childrenProgress.Average());
+ }
- var innerProgress = new ActionableProgress<double>();
+ var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
+ var parallelism = fanoutConcurrency == 0 ? Environment.ProcessorCount : fanoutConcurrency;
+
+ var actionBlock = new ActionBlock<int>(
+ async i =>
+ {
+ var innerProgress = new ActionableProgress<double>();
+
+ innerProgress.RegisterAction(innerPercent =>
+ {
+ // round the percent and only update progress if it changed to prevent excessive UpdateProgress calls
+ var innerPercentRounded = Math.Round(innerPercent);
+ if (childrenProgress[i] != innerPercentRounded)
+ {
+ childrenProgress[i] = innerPercentRounded;
+ UpdateProgress();
+ }
+ });
+
+ await task(children[i], innerProgress).ConfigureAwait(false);
- // Avoid implicitly captured closure
- var currentInnerPercent = currentPercent;
+ childrenProgress[i] = 100;
- innerProgress.RegisterAction(p =>
+ UpdateProgress();
+ },
+ new ExecutionDataflowBlockOptions
{
- double innerPercent = currentInnerPercent;
- innerPercent += p / count;
- progress.Report(innerPercent);
+ MaxDegreeOfParallelism = parallelism,
+ CancellationToken = cancellationToken,
});
- await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)
- .ConfigureAwait(false);
+ for (var i = 0; i < childrenCount; i++)
+ {
+ actionBlock.Post(i);
+ }
- numComplete++;
- double percent = numComplete;
- percent /= count;
- percent *= 100;
- currentPercent = percent;
+ actionBlock.Complete();
- progress.Report(percent);
- }
+ await actionBlock.Completion.ConfigureAwait(false);
}
/// <summary>
@@ -1763,5 +1784,45 @@ namespace MediaBrowser.Controller.Entities
}
}
}
+
+ /// <summary>
+ /// Contains constants used when reporting scan progress.
+ /// </summary>
+ private static class ProgressHelpers
+ {
+ /// <summary>
+ /// Reported after the folders immediate children are retrieved.
+ /// </summary>
+ public const int RetrievedChildren = 5;
+
+ /// <summary>
+ /// Reported after add, updating, or deleting child items from the LibraryManager.
+ /// </summary>
+ public const int UpdatedChildItems = 10;
+
+ /// <summary>
+ /// Reported once subfolders are scanned.
+ /// When scanning subfolders, the progress will be between [UpdatedItems, ScannedSubfolders].
+ /// </summary>
+ public const int ScannedSubfolders = 50;
+
+ /// <summary>
+ /// Reported once metadata is refreshed.
+ /// When refreshing metadata, the progress will be between [ScannedSubfolders, MetadataRefreshed].
+ /// </summary>
+ public const int RefreshedMetadata = 100;
+
+ /// <summary>
+ /// Gets the current progress given the previous step, next step, and progress in between.
+ /// </summary>
+ /// <param name="previousProgressStep">The previous progress step.</param>
+ /// <param name="nextProgressStep">The next progress step.</param>
+ /// <param name="currentProgress">The current progress step.</param>
+ /// <returns>The progress.</returns>
+ public static double GetProgress(int previousProgressStep, int nextProgressStep, double currentProgress)
+ {
+ return previousProgressStep + ((nextProgressStep - previousProgressStep) * (currentProgress / 100));
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 601ca3536..24b101694 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -574,5 +574,7 @@ namespace MediaBrowser.Controller.Library
void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason);
BaseItem GetParentItem(string parentId, Guid? userId);
+
+ BaseItem GetParentItem(Guid? parentId, Guid? userId);
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 9acc98dce..5f75df54e 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -17,6 +17,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
+ <PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 996ec27c0..0a4967223 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -46,6 +46,14 @@ namespace MediaBrowser.Controller.Providers
Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken);
/// <summary>
+ /// Runs multiple metadata refreshes concurrently.
+ /// </summary>
+ /// <param name="action">The action to run.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
+ Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken);
+
+ /// <summary>
/// Saves the image.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index a5d641747..db6b47583 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -51,7 +51,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
eventsStarted = true;
}
- else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";", StringComparison.Ordinal))
+ else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(';'))
{
// skip comment lines
}
@@ -151,13 +151,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- var p = new SubtitleTrackEvent();
-
- p.StartPositionTicks = GetTimeCodeFromString(start);
- p.EndPositionTicks = GetTimeCodeFromString(end);
- p.Text = GetFormattedText(text);
-
- trackEvents.Add(p);
+ trackEvents.Add(
+ new SubtitleTrackEvent
+ {
+ StartPositionTicks = GetTimeCodeFromString(start),
+ EndPositionTicks = GetTimeCodeFromString(end),
+ Text = GetFormattedText(text)
+ });
}
catch
{
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 830c8bd10..0dbd51bdc 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -439,5 +439,15 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets the number of days we should retain activity logs.
/// </summary>
public int? ActivityLogRetentionDays { get; set; } = 30;
+
+ /// <summary>
+ /// Gets or sets the how the library scan fans out.
+ /// </summary>
+ public int LibraryScanFanoutConcurrency { get; set; }
+
+ /// <summary>
+ /// Gets or sets the how many metadata refreshes can run concurrently.
+ /// </summary>
+ public int LibraryMetadataRefreshConcurrency { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs
index 09afa64bb..56c89d854 100644
--- a/MediaBrowser.Model/Dlna/ContainerProfile.cs
+++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Model.Dlna
public static bool ContainsContainer(string profileContainers, string inputContainer)
{
var isNegativeList = false;
- if (profileContainers != null && profileContainers.StartsWith("-", StringComparison.Ordinal))
+ if (profileContainers != null && profileContainers.StartsWith('-'))
{
isNegativeList = true;
profileContainers = profileContainers.Substring(1);
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index ee13ffc16..4ad336d33 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Querying
/// Gets or sets the parent identifier.
/// </summary>
/// <value>The parent identifier.</value>
- public string ParentId { get; set; }
+ public Guid? ParentId { get; set; }
/// <summary>
/// Gets or sets the series id.
diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs
index 01ad319a4..ce60062cd 100644
--- a/MediaBrowser.Model/Search/SearchQuery.cs
+++ b/MediaBrowser.Model/Search/SearchQuery.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Model.Search
public string[] ExcludeItemTypes { get; set; }
- public string ParentId { get; set; }
+ public Guid? ParentId { get; set; }
public bool? IsMovie { get; set; }
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index e7e44876d..a20c47cf2 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1167,6 +1167,29 @@ namespace MediaBrowser.Providers.Manager
return RefreshItem(item, options, cancellationToken);
}
+ /// <summary>
+ /// Runs multiple metadata refreshes concurrently.
+ /// </summary>
+ /// <param name="action">The action to run.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
+ public async Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken)
+ {
+ // create a variable for this since it is possible MetadataRefreshThrottler could change due to a config update during a scan
+ var metadataRefreshThrottler = _baseItemManager.MetadataRefreshThrottler;
+
+ await metadataRefreshThrottler.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ await action().ConfigureAwait(false);
+ }
+ finally
+ {
+ metadataRefreshThrottler.Release();
+ }
+ }
+
/// <inheritdoc/>
public void Dispose()
{
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index 648329757..4fff57273 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -203,7 +203,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
item.ShortcutPath = File.ReadAllLines(item.Path)
.Select(NormalizeStrmLine)
- .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith("#", StringComparison.OrdinalIgnoreCase));
+ .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith('#'));
}
public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)