aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml4
-rw-r--r--Emby.Dlna/DlnaManager.cs9
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs3
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs2
-rw-r--r--Emby.Drawing/ImageProcessor.cs7
-rw-r--r--Emby.Naming/Video/CleanStringParser.cs15
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs15
-rw-r--r--Emby.Naming/Video/VideoResolver.cs3
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs5
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs2
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs15
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs2
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj1
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs2
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs2
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs11
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs208
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs30
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs256
-rw-r--r--Emby.Server.Implementations/Localization/Core/eo.json23
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-BR.json2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs2
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs2
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs2
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs2
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs23
-rw-r--r--Jellyfin.Api/Controllers/ImageByNameController.cs23
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs12
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs90
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs8
-rw-r--r--Jellyfin.Api/Controllers/NotificationsController.cs21
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs5
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs15
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs19
-rw-r--r--Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs30
-rw-r--r--Jellyfin.Data/DayOfWeekHelper.cs60
-rw-r--r--Jellyfin.Data/Entities/AccessSchedule.cs29
-rw-r--r--Jellyfin.Data/Entities/ActivityLog.cs21
-rw-r--r--Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs20
-rw-r--r--Jellyfin.Data/Entities/DisplayPreferences.cs14
-rw-r--r--Jellyfin.Data/Entities/Group.cs11
-rw-r--r--Jellyfin.Data/Entities/HomeSection.cs1
-rw-r--r--Jellyfin.Data/Entities/ImageInfo.cs11
-rw-r--r--Jellyfin.Data/Entities/ItemDisplayPreferences.cs9
-rw-r--r--Jellyfin.Data/Entities/Libraries/Artwork.cs16
-rw-r--r--Jellyfin.Data/Entities/Libraries/Book.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/BookMetadata.cs23
-rw-r--r--Jellyfin.Data/Entities/Libraries/Chapter.cs23
-rw-r--r--Jellyfin.Data/Entities/Libraries/Collection.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/CollectionItem.cs40
-rw-r--r--Jellyfin.Data/Entities/Libraries/Company.cs17
-rw-r--r--Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs25
-rw-r--r--Jellyfin.Data/Entities/Libraries/CustomItem.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs21
-rw-r--r--Jellyfin.Data/Entities/Libraries/Episode.cs22
-rw-r--r--Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs26
-rw-r--r--Jellyfin.Data/Entities/Libraries/Genre.cs27
-rw-r--r--Jellyfin.Data/Entities/Libraries/ItemMetadata.cs16
-rw-r--r--Jellyfin.Data/Entities/Libraries/Library.cs22
-rw-r--r--Jellyfin.Data/Entities/Libraries/LibraryItem.cs8
-rw-r--r--Jellyfin.Data/Entities/Libraries/MediaFile.cs21
-rw-r--r--Jellyfin.Data/Entities/Libraries/MediaFileStream.cs23
-rw-r--r--Jellyfin.Data/Entities/Libraries/MetadataProvider.cs11
-rw-r--r--Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs23
-rw-r--r--Jellyfin.Data/Entities/Libraries/Movie.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/MovieMetadata.cs25
-rw-r--r--Jellyfin.Data/Entities/Libraries/MusicAlbum.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs21
-rw-r--r--Jellyfin.Data/Entities/Libraries/Person.cs13
-rw-r--r--Jellyfin.Data/Entities/Libraries/PersonRole.cs28
-rw-r--r--Jellyfin.Data/Entities/Libraries/Photo.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs21
-rw-r--r--Jellyfin.Data/Entities/Libraries/Rating.cs23
-rw-r--r--Jellyfin.Data/Entities/Libraries/RatingSource.cs25
-rw-r--r--Jellyfin.Data/Entities/Libraries/Release.cs16
-rw-r--r--Jellyfin.Data/Entities/Libraries/Season.cs22
-rw-r--r--Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs22
-rw-r--r--Jellyfin.Data/Entities/Libraries/Series.cs3
-rw-r--r--Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs30
-rw-r--r--Jellyfin.Data/Entities/Libraries/Track.cs22
-rw-r--r--Jellyfin.Data/Entities/Libraries/TrackMetadata.cs21
-rw-r--r--Jellyfin.Data/Entities/Permission.cs10
-rw-r--r--Jellyfin.Data/Entities/Preference.cs9
-rw-r--r--Jellyfin.Data/Entities/User.cs58
-rw-r--r--Jellyfin.Data/Interfaces/IHasPermissions.cs2
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj10
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj11
-rw-r--r--Jellyfin.Drawing.Skia/SkiaEncoder.cs13
-rw-r--r--Jellyfin.Networking/Jellyfin.Networking.csproj6
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs8
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs4
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs14
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs4
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs6
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs3
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs9
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs7
-rw-r--r--Jellyfin.Server/Filters/WebsocketModelFilter.cs2
-rw-r--r--Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs2
-rw-r--r--Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs2
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj8
-rw-r--r--Jellyfin.Server/Migrations/MigrationOptions.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs6
-rw-r--r--Jellyfin.Server/Program.cs4
-rw-r--r--Jellyfin.sln9
-rw-r--r--MediaBrowser.Common/Crc32.cs89
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs4
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonStringConverter.cs39
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs9
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj6
-rw-r--r--MediaBrowser.Common/Net/IPHost.cs2
-rw-r--r--MediaBrowser.Common/Net/IPNetAddress.cs6
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs2
-rw-r--r--MediaBrowser.Common/Plugins/BasePluginOfT.cs36
-rw-r--r--MediaBrowser.Common/Plugins/LocalPlugin.cs6
-rw-r--r--MediaBrowser.Common/Progress/ActionableProgress.cs1
-rw-r--r--MediaBrowser.Common/Progress/SimpleProgress.cs1
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs12
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs2
-rw-r--r--MediaBrowser.Controller/Library/NameExtensions.cs8
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs6
-rw-r--r--MediaBrowser.Controller/Providers/ILocalImageProvider.cs2
-rw-r--r--MediaBrowser.Controller/Resolvers/BaseItemResolver.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs7
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs14
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj6
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs17
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs14
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs24
-rw-r--r--README.md3
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs2
-rw-r--r--tests/Jellyfin.Api.Tests/TestHelpers.cs7
-rw-r--r--tests/Jellyfin.Common.Tests/Crc32Tests.cs33
-rw-r--r--tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs39
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs2
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs36
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs45
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs65
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs6
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs58
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs119
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj7
174 files changed, 1357 insertions, 1410 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 20f4dfe33..543fd7fc6 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -160,7 +160,6 @@ jobs:
dependsOn:
- BuildPackage
- BuildDocker
- condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool:
vmImage: 'ubuntu-latest'
@@ -186,9 +185,6 @@ jobs:
- job: PublishNuget
displayName: 'Publish NuGet packages'
- dependsOn:
- - BuildPackage
- condition: succeeded('BuildPackage')
pool:
vmImage: 'ubuntu-latest'
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index d7b75f979..c94d803e1 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -36,7 +36,7 @@ namespace Emby.Dlna
private readonly ILogger<DlnaManager> _logger;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@@ -333,7 +333,12 @@ namespace Emby.Dlna
throw new ArgumentNullException(nameof(id));
}
- var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
+ var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
+
+ if (info == null)
+ {
+ return null;
+ }
return ParseProfileFile(info.Path, info.Info.Type);
}
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 9a2d524d1..d3e9a41ec 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -128,7 +128,8 @@ namespace Emby.Dlna.Main
_netConfig = config.GetConfiguration<NetworkConfiguration>("network");
_disabled = appHost.ListenWithHttps && _netConfig.RequireHttps;
- if (_disabled)
+
+ if (_disabled && _config.GetDlnaConfiguration().EnableServer)
{
_logger.LogError("The DLNA specification does not support HTTPS.");
}
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index 8c7d961f3..d13871add 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -69,7 +69,7 @@ namespace Emby.Dlna.Ssdp
{
lock (_syncLock)
{
- if (_listenerCount > 0 && _deviceLocator == null)
+ if (_listenerCount > 0 && _deviceLocator == null && _commsServer != null)
{
_deviceLocator = new SsdpDeviceLocator(_commsServer);
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 8a2301d2d..aa8a3d212 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -352,8 +352,13 @@ namespace Emby.Drawing
}
/// <inheritdoc />
- public string GetImageCacheTag(User user)
+ public string? GetImageCacheTag(User user)
{
+ if (user.ProfileImage == null)
+ {
+ return null;
+ }
+
return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5()
.ToString("N", CultureInfo.InvariantCulture);
}
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index bd7553a91..4eef3ebc5 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Emby.Naming.Video
@@ -16,8 +17,14 @@ namespace Emby.Naming.Video
/// <param name="expressions">List of regex to parse name and year from.</param>
/// <param name="newName">Parsing result string.</param>
/// <returns>True if parsing was successful.</returns>
- public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
+ public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
{
+ if (string.IsNullOrEmpty(name))
+ {
+ newName = ReadOnlySpan<char>.Empty;
+ return false;
+ }
+
var len = expressions.Count;
for (int i = 0; i < len; i++)
{
@@ -33,12 +40,6 @@ namespace Emby.Naming.Video
private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
{
- if (string.IsNullOrEmpty(name))
- {
- newName = ReadOnlySpan<char>.Empty;
- return false;
- }
-
var match = expression.Match(name);
int index = match.Index;
if (match.Success && index != 0)
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 09a030d2d..7b6a1705b 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -221,20 +221,21 @@ namespace Emby.Naming.Video
string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{
- if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
+ // Remove the folder name before cleaning as we don't care about cleaning that part
+ if (folderName.Length <= testFilename.Length)
{
- testFilename = cleanName.ToString();
+ testFilename = testFilename.Substring(folderName.Length).Trim();
}
- if (folderName.Length <= testFilename.Length)
+ if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
{
- testFilename = testFilename.Substring(folderName.Length).Trim();
+ testFilename = cleanName.Trim().ToString();
}
+ // The CleanStringParser should have removed common keywords etc.
return string.IsNullOrEmpty(testFilename)
- || testFilename[0] == '-'
- || testFilename[0] == '_'
- || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
+ || testFilename[0] == '-'
+ || Regex.IsMatch(testFilename, @"^\[([^]]*)\]");
}
return false;
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 619d1520e..79a6da8f7 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
@@ -146,7 +147,7 @@ namespace Emby.Naming.Video
/// <param name="name">Raw name.</param>
/// <param name="newName">Clean name.</param>
/// <returns>True if cleaning of name was successful.</returns>
- public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
+ public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName)
{
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 835dc33b0..164e6d49d 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -10,8 +10,6 @@ using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna;
@@ -51,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;
@@ -470,7 +467,7 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc />
- public IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true)
+ public IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true)
{
// Convert to list so this isn't executed for each iteration
var parts = GetExportTypes<T>()
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 8c5fa09f6..87ebe960a 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Channels
private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache;
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary>
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 1ab2bdfbe..db532ce5b 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -344,7 +344,20 @@ namespace Emby.Server.Implementations.Collections
}
else
{
- results[item.Id] = item;
+ var alreadyInResults = false;
+ foreach (var child in item.GetMediaSources(true))
+ {
+ if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
+ {
+ alreadyInResults = true;
+ break;
+ }
+ }
+
+ if (!alreadyInResults)
+ {
+ results[item.Id] = item;
+ }
}
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index d78b93bd7..2ae805447 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Data
_imageProcessor = imageProcessor;
_typeMapper = new TypeMapper();
- _jsonOptions = JsonDefaults.GetOptions();
+ _jsonOptions = JsonDefaults.Options;
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 5a9792b51..be552ef93 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -27,6 +27,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
<PackageReference Include="sharpcompress" Version="0.28.1" />
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 7e0c2c1da..06acb5606 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
RemoteEndPoint = remoteEndPoint;
QueryString = query;
- _jsonOptions = JsonDefaults.GetOptions();
+ _jsonOptions = JsonDefaults.Options;
LastActivityDate = DateTime.Now;
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 2070df31e..c2951dd15 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
{
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c63eb7017..b2943020c 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private IMediaSourceProvider[] _providers;
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 7dcc925c2..57d0c26b9 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -59,11 +59,18 @@ namespace Emby.Server.Implementations.Library
/// <param name="newPath">The result of the sub path replacement</param>
/// <returns>The path after replacing the sub path.</returns>
/// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception>
- public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath)
+ public static bool TryReplaceSubPath(
+ [NotNullWhen(true)] this string? path,
+ [NotNullWhen(true)] string? subPath,
+ [NotNullWhen(true)] string? newSubPath,
+ [NotNullWhen(true)] out string? newPath)
{
newPath = null;
- if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length)
+ if (string.IsNullOrEmpty(path)
+ || string.IsNullOrEmpty(subPath)
+ || string.IsNullOrEmpty(newSubPath)
+ || subPath.Length > path.Length)
{
return false;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 2f5e46038..6e688693b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
- protected override T Resolve(ItemResolveArgs args)
+ public override T Resolve(ItemResolveArgs args)
{
return ResolveVideo<T>(args, false);
}
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <param name="args">The args.</param>
/// <param name="parseName">if set to <c>true</c> [parse name].</param>
/// <returns>``0.</returns>
- protected TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
+ protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 86242d137..0525c7e30 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
{
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
- protected override Book Resolve(ItemResolveArgs args)
+ public override Book Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 8ef7172de..714bc3a84 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -69,6 +69,110 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
+ /// <summary>
+ /// Resolves the specified args.
+ /// </summary>
+ /// <param name="args">The args.</param>
+ /// <returns>Video.</returns>
+ public override Video Resolve(ItemResolveArgs args)
+ {
+ var collectionType = args.GetCollectionType();
+
+ // Find movies with their own folders
+ if (args.IsDirectory)
+ {
+ if (IsInvalid(args.Parent, collectionType))
+ {
+ return null;
+ }
+
+ var files = args.FileSystemChildren
+ .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+ .ToList();
+
+ if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ }
+
+ if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+ }
+
+ if (string.IsNullOrEmpty(collectionType))
+ {
+ // Owned items will be caught by the plain video resolver
+ if (args.Parent == null)
+ {
+ // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ return null;
+ }
+
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
+ {
+ return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
+ }
+ }
+
+ if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
+ }
+
+ return null;
+ }
+
+ // Handle owned items
+ if (args.Parent == null)
+ {
+ return base.Resolve(args);
+ }
+
+ if (IsInvalid(args.Parent, collectionType))
+ {
+ return null;
+ }
+
+ Video item = null;
+
+ if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<MusicVideo>(args, false);
+ }
+
+ // To find a movie file, the collection type must be movies or boxsets
+ else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<Movie>(args, true);
+ }
+ else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
+ {
+ item = ResolveVideo<Video>(args, false);
+ }
+ else if (string.IsNullOrEmpty(collectionType))
+ {
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
+ item = ResolveVideo<Video>(args, false);
+ }
+
+ if (item != null)
+ {
+ item.IsInMixedFolder = true;
+ }
+
+ return item;
+ }
+
private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files,
@@ -217,110 +321,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
/// <summary>
- /// Resolves the specified args.
- /// </summary>
- /// <param name="args">The args.</param>
- /// <returns>Video.</returns>
- protected override Video Resolve(ItemResolveArgs args)
- {
- var collectionType = args.GetCollectionType();
-
- // Find movies with their own folders
- if (args.IsDirectory)
- {
- if (IsInvalid(args.Parent, collectionType))
- {
- return null;
- }
-
- var files = args.FileSystemChildren
- .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
- .ToList();
-
- if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
- }
-
- if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
- }
-
- if (string.IsNullOrEmpty(collectionType))
- {
- // Owned items will be caught by the plain video resolver
- if (args.Parent == null)
- {
- // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
- return null;
- }
-
- if (args.HasParent<Series>())
- {
- return null;
- }
-
- {
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
- }
- }
-
- if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
- {
- return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
- }
-
- return null;
- }
-
- // Handle owned items
- if (args.Parent == null)
- {
- return base.Resolve(args);
- }
-
- if (IsInvalid(args.Parent, collectionType))
- {
- return null;
- }
-
- Video item = null;
-
- if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<MusicVideo>(args, false);
- }
-
- // To find a movie file, the collection type must be movies or boxsets
- else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<Movie>(args, true);
- }
- else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
- {
- item = ResolveVideo<Video>(args, false);
- }
- else if (string.IsNullOrEmpty(collectionType))
- {
- if (args.HasParent<Series>())
- {
- return null;
- }
-
- item = ResolveVideo<Video>(args, false);
- }
-
- if (item != null)
- {
- item.IsInMixedFolder = true;
- }
-
- return item;
- }
-
- /// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 2f7af60c0..9b4cd7a3d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
@@ -12,11 +13,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public class EpisodeResolver : BaseVideoResolver<Episode>
{
/// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ public EpisodeResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
+ {
+ }
+
+ /// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Episode.</returns>
- protected override Episode Resolve(ItemResolveArgs args)
+ public override Episode Resolve(ItemResolveArgs args)
{
var parent = args.Parent;
@@ -34,11 +44,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
season = parent.GetParents().OfType<Season>().FirstOrDefault();
}
- // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
+ // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
- if (season != null ||
- string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
- args.HasParent<Series>())
+ if ((season != null ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent<Series>())
+ && (parent is Series || !BaseItem.AllExtrasTypesFolderNames.Contains(parent.Name, StringComparer.OrdinalIgnoreCase)))
{
var episode = ResolveVideo<Episode>(args, false);
@@ -74,14 +85,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
- /// </summary>
- /// <param name="libraryManager">The library manager.</param>
- public EpisodeResolver(ILibraryManager libraryManager)
- : base(libraryManager)
- {
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 78a82118e..44a8cdee4 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationPaths _appPaths;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 57424f043..c20b08088 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly string _dataPath;
private readonly object _fileDataLock = new object();
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private T[] _items;
public ItemDataProvider(
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 6d7c5ac6e..1926e738f 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private DateTime _lastErrorResponse;
public SchedulesDirect(
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 0760e8127..68173a0ef 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_networkManager = networkManager;
_streamHelper = streamHelper;
- _jsonOptions = JsonDefaults.GetOptions();
+ _jsonOptions = JsonDefaults.Options;
}
public string Name => "HD Homerun";
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index f09338330..20a4d87fb 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -2,6 +2,7 @@
using System;
using System.Buffers;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
@@ -10,6 +11,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common;
using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -120,13 +122,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
{
- var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
- await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
-
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var msgLen = WriteGetMessage(buffer, tuner, "lockkey");
+ await stream.WriteAsync(buffer.AsMemory(0, msgLen), cancellationToken).ConfigureAwait(false);
+
+ int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
ParseReturnMessage(buffer, receivedBytes, out string returnVal);
@@ -166,9 +168,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_activeTuner = i;
var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
- var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
- await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
- int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var lockkeyMsgLen = WriteSetMessage(buffer, i, "lockkey", lockKeyString, null);
+ await stream.WriteAsync(buffer.AsMemory(0, lockkeyMsgLen), cancellationToken).ConfigureAwait(false);
+ int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@@ -178,9 +180,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands())
{
- var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
- await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
- receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
+ await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
+ receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@@ -191,10 +193,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
- var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
+ var targetMsgLen = WriteSetMessage(buffer, i, "target", targetValue, lockKeyValue);
- await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
- receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ await stream.WriteAsync(buffer.AsMemory(0, targetMsgLen), cancellationToken).ConfigureAwait(false);
+ receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@@ -232,9 +234,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
foreach (var command in commandList)
{
- var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
- await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
- int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
+ await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
+ int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
@@ -265,17 +267,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var stream = client.GetStream();
- var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
- await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
-
var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
- await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
- var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
+ var releaseTargetLen = WriteSetMessage(buffer, _activeTuner, "target", "none", lockKeyValue);
+ await stream.WriteAsync(buffer.AsMemory(0, releaseTargetLen)).ConfigureAwait(false);
+
+ await stream.ReadAsync(buffer).ConfigureAwait(false);
+ var releaseKeyMsgLen = WriteSetMessage(buffer, _activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null;
- await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
- await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ await stream.WriteAsync(buffer.AsMemory(0, releaseKeyMsgLen)).ConfigureAwait(false);
+ await stream.ReadAsync(buffer).ConfigureAwait(false);
}
finally
{
@@ -283,107 +285,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- private static byte[] CreateGetMessage(int tuner, string name)
+ internal static int WriteGetMessage(Span<byte> buffer, int tuner, string name)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
- int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
-
- var message = new byte[messageLength];
+ var byteName = string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}", tuner, name);
+ int offset = WriteHeaderAndPayload(buffer, byteName);
+ return FinishPacket(buffer, offset);
+ }
- int offset = InsertHeaderAndName(byteName, messageLength, message);
+ private 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);
- bool flipEndian = BitConverter.IsLittleEndian;
+ buffer[offset++] = GetSetValue;
+ offset += WriteNullTerminatedString(buffer.Slice(offset), value);
- // calculate crc and insert at the end of the message
- var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
- if (flipEndian)
+ if (lockkey.HasValue)
{
- Array.Reverse(crcBytes);
+ buffer[offset++] = GetSetLockkey;
+ buffer[offset++] = 4;
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), lockkey.Value);
+ offset += 4;
}
- Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
-
- return message;
+ return FinishPacket(buffer, offset);
}
- private static byte[] CreateSetMessage(int tuner, string name, string value, uint? lockkey)
+ internal static int WriteNullTerminatedString(Span<byte> buffer, ReadOnlySpan<char> payload)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
- var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
+ int len = Encoding.UTF8.GetBytes(payload, buffer.Slice(1)) + 1;
- int messageLength = byteName.Length + byteValue.Length + 12;
- if (lockkey.HasValue)
- {
- messageLength += 6;
- }
+ // TODO: variable length: this can be 2 bytes if len > 127
+ // Write length in front of value
+ buffer[0] = Convert.ToByte(len);
- var message = new byte[messageLength];
+ // null-terminate
+ buffer[len++] = 0;
- int offset = InsertHeaderAndName(byteName, messageLength, message);
+ return len;
+ }
- bool flipEndian = BitConverter.IsLittleEndian;
+ private static int WriteHeaderAndPayload(Span<byte> buffer, ReadOnlySpan<char> payload)
+ {
+ // Packet type
+ BinaryPrimitives.WriteUInt16BigEndian(buffer, GetSetRequest);
- message[offset++] = GetSetValue;
- message[offset++] = Convert.ToByte(byteValue.Length);
- Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
- offset += byteValue.Length;
- if (lockkey.HasValue)
- {
- message[offset++] = GetSetLockkey;
- message[offset++] = 4;
- var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
- if (flipEndian)
- {
- Array.Reverse(lockKeyBytes);
- }
+ // We write the payload length at the end
+ int offset = 4;
- Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
- offset += 4;
- }
+ // Tag
+ buffer[offset++] = GetSetName;
- // calculate crc and insert at the end of the message
- var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
- if (flipEndian)
- {
- Array.Reverse(crcBytes);
- }
-
- Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
+ // Payload length + data
+ int strLen = WriteNullTerminatedString(buffer.Slice(offset), payload);
+ offset += strLen;
- return message;
+ return offset;
}
- private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message)
+ private static int FinishPacket(Span<byte> buffer, int offset)
{
- // check to see if we need to flip endiannes
- bool flipEndian = BitConverter.IsLittleEndian;
- int offset = 0;
-
- // create header bytes
- var getSetBytes = BitConverter.GetBytes(GetSetRequest);
- var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc
-
- if (flipEndian)
- {
- Array.Reverse(getSetBytes);
- Array.Reverse(msgLenBytes);
- }
-
- // insert header bytes into message
- Buffer.BlockCopy(getSetBytes, 0, message, offset, 2);
- offset += 2;
- Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
- offset += 2;
-
- // insert tag name and length
- message[offset++] = GetSetName;
- message[offset++] = Convert.ToByte(byteName.Length);
+ // Payload length
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(2), (ushort)(offset - 4));
- // insert name string
- Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
- offset += byteName.Length;
+ // calculate crc and insert at the end of the message
+ var crc = Crc32.Compute(buffer.Slice(0, offset));
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), crc);
- return offset;
+ return offset + 4;
}
private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
@@ -442,90 +411,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true;
}
-
- private static class HdHomerunCrc
- {
- private static uint[] crc_table = {
- 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
- 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
- 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
- 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
- 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
- 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
- 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
- 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
- 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
- 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
- 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
- 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
- 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
- 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
- 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
- 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
- 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
- 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
- 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
- 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
- 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
- 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
- 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
- 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
- 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
- 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
- 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
- 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
- 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
- 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
- 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
- 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
- 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
- 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
- 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
- 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
- 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
- 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
- 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
- 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
- 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
- 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
- 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
- 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
- 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
- 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
- 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
- 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
- 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
- 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
- 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
- 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
- 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
- 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
- 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
- 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
- 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
- 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
- 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
- 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
- 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
- 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
- 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
- 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
-
- public static uint GetCrc32(byte[] bytes, int numBytes)
- {
- var hash = 0xffffffff;
- for (var i = 0; i < numBytes; i++)
- {
- hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
- }
-
- var tmp = ~hash & 0xffffffff;
- var b0 = tmp & 0xff;
- var b1 = (tmp >> 8) & 0xff;
- var b2 = (tmp >> 16) & 0xff;
- var b3 = (tmp >> 24) & 0xff;
- return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
- }
- }
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
index 3ff7eddae..ca615cc8c 100644
--- a/Emby.Server.Implementations/Localization/Core/eo.json
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -22,5 +22,26 @@
"Artists": "Artistoj",
"Application": "Aplikaĵo",
"AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
- "Albums": "Albumoj"
+ "Albums": "Albumoj",
+ "TasksLibraryCategory": "Libraro",
+ "VersionNumber": "Versio {0}",
+ "UserDownloadingItemWithValues": "{0} elŝutas {1}",
+ "UserCreatedWithName": "Uzanto {0} kreiĝis",
+ "User": "Uzanto",
+ "System": "Sistemo",
+ "Songs": "Kantoj",
+ "ScheduledTaskStartedWithName": "{0} komencis",
+ "ScheduledTaskFailedWithName": "{0} malsukcesis",
+ "PluginUninstalledWithName": "{0} malinstaliĝis",
+ "PluginInstalledWithName": "{0} instaliĝis",
+ "Plugin": "Kromprogramo",
+ "Playlists": "Ludlistoj",
+ "Photos": "Fotoj",
+ "NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis",
+ "NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis",
+ "NotificationOptionPluginInstalled": "Kromprogramo instaliĝis",
+ "MusicVideos": "Muzikvideoj",
+ "LabelIpAddressValue": "IP-adreso: {0}",
+ "Genres": "Ĝenroj",
+ "DeviceOfflineWithName": "{0} malkonektis"
}
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index bdfb786c9..829a29ad4 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -116,7 +116,7 @@
"TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
"TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
"TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
- "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.",
+ "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşın nobailar jasaidy.",
"TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
"TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
}
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index d4cb592ef..9920ef4d5 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -113,5 +113,9 @@
"TasksChannelsCategory": "Internetiniai Kanalai",
"TasksApplicationCategory": "Programa",
"TasksLibraryCategory": "Mediateka",
- "TasksMaintenanceCategory": "Priežiūra"
+ "TasksMaintenanceCategory": "Priežiūra",
+ "TaskCleanActivityLog": "Švarus veiklos žurnalas",
+ "Undefined": "Neapibrėžtas",
+ "Forced": "Priverstas",
+ "Default": "Numatytas"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 5ec8f1e88..323dcced0 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -16,7 +16,7 @@
"Folders": "Pastas",
"Genres": "Gêneros",
"HeaderAlbumArtists": "Artistas do Álbum",
- "HeaderContinueWatching": "Continuar Assistindo",
+ "HeaderContinueWatching": "Continuar assistindo",
"HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episódios favoritos",
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 3f9e22106..98de848bc 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Localization
private List<CultureDto> _cultures;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index c579fc8cb..700396c4c 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Plugins
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_pluginsPath = pluginsPath;
_appVersion = appVersion ?? throw new ArgumentNullException(nameof(appVersion));
- _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions())
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options)
{
WriteIndented = true
};
@@ -678,7 +678,7 @@ namespace Emby.Server.Implementations.Plugins
var entry = versions[x];
if (!string.Equals(lastName, entry.Name, StringComparison.OrdinalIgnoreCase))
{
- entry.DllFiles.AddRange(Directory.EnumerateFiles(entry.Path, "*.dll", SearchOption.AllDirectories));
+ entry.DllFiles = Directory.GetFiles(entry.Path, "*.dll", SearchOption.AllDirectories);
if (entry.IsEnabledAndSupported)
{
lastName = entry.Name;
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index b302303f8..a145a8423 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <summary>
/// The options for the json Serializer.
/// </summary>
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 7af52ea65..fc34f93cd 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Updates
_httpClientFactory = httpClientFactory;
_config = config;
_zipClient = zipClient;
- _jsonSerializerOptions = JsonDefaults.GetOptions();
+ _jsonSerializerOptions = JsonDefaults.Options;
_pluginManager = pluginManager;
}
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index e1c9f69f6..049a4bed7 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers
private readonly IServerConfigurationManager _configurationManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _serializerOptions = JsonDefaults.Options;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationController"/> class.
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index d0ed45acb..473bdc523 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -61,7 +61,13 @@ namespace Jellyfin.Api.Controllers
{
// TODO: Deprecate with new iOS app
var file = segmentId + Path.GetExtension(Request.Path);
- file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
+ var transcodePath = _serverConfigurationManager.GetTranscodePath();
+ file = Path.GetFullPath(Path.Combine(transcodePath, file));
+ var fileDir = Path.GetDirectoryName(file);
+ if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath))
+ {
+ return BadRequest("Invalid segment.");
+ }
return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext);
}
@@ -81,7 +87,13 @@ namespace Jellyfin.Api.Controllers
public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{
var file = playlistId + Path.GetExtension(Request.Path);
- file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
+ var transcodePath = _serverConfigurationManager.GetTranscodePath();
+ file = Path.GetFullPath(Path.Combine(transcodePath, file));
+ var fileDir = Path.GetDirectoryName(file);
+ if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8")
+ {
+ return BadRequest("Invalid segment.");
+ }
return GetFileResult(file, file);
}
@@ -130,7 +142,12 @@ namespace Jellyfin.Api.Controllers
var file = segmentId + Path.GetExtension(Request.Path);
var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
- file = Path.Combine(transcodeFolderPath, file);
+ file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
+ var fileDir = Path.GetDirectoryName(file);
+ if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath))
+ {
+ return BadRequest("Invalid segment.");
+ }
var normalizedPlaylistId = playlistId;
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 198dbc51f..e1b808098 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
: type;
var path = BaseItem.SupportedImageExtensions
- .Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i))
+ .Select(i => Path.GetFullPath(Path.Combine(_applicationPaths.GeneralPath, name, filename + i)))
.FirstOrDefault(System.IO.File.Exists);
if (path == null)
@@ -82,6 +82,11 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
+ if (!path.StartsWith(_applicationPaths.GeneralPath))
+ {
+ return BadRequest("Invalid image path.");
+ }
+
var contentType = MimeTypes.GetMimeType(path);
return File(System.IO.File.OpenRead(path), contentType);
}
@@ -163,7 +168,8 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="FileStreamResult"/> containing the image contents on success, or a <see cref="NotFoundResult"/> if the image could not be found.</returns>
private ActionResult GetImageFile(string basePath, string theme, string? name)
{
- var themeFolder = Path.Combine(basePath, theme);
+ var themeFolder = Path.GetFullPath(Path.Combine(basePath, theme));
+
if (Directory.Exists(themeFolder))
{
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i))
@@ -171,12 +177,18 @@ namespace Jellyfin.Api.Controllers
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
{
+ if (!path.StartsWith(basePath))
+ {
+ return BadRequest("Invalid image path.");
+ }
+
var contentType = MimeTypes.GetMimeType(path);
+
return PhysicalFile(path, contentType);
}
}
- var allFolder = Path.Combine(basePath, "all");
+ var allFolder = Path.GetFullPath(Path.Combine(basePath, "all"));
if (Directory.Exists(allFolder))
{
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, name + i))
@@ -184,6 +196,11 @@ namespace Jellyfin.Api.Controllers
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
{
+ if (!path.StartsWith(basePath))
+ {
+ return BadRequest("Invalid image path.");
+ }
+
var contentType = MimeTypes.GetMimeType(path);
return PhysicalFile(path, contentType);
}
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index cfc038f23..89037749a 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -196,6 +196,11 @@ namespace Jellyfin.Api.Controllers
}
var user = _userManager.GetUserById(userId);
+ if (user?.ProfileImage == null)
+ {
+ return NoContent();
+ }
+
try
{
System.IO.File.Delete(user.ProfileImage.Path);
@@ -235,6 +240,11 @@ namespace Jellyfin.Api.Controllers
}
var user = _userManager.GetUserById(userId);
+ if (user?.ProfileImage == null)
+ {
+ return NoContent();
+ }
+
try
{
System.IO.File.Delete(user.ProfileImage.Path);
@@ -1469,7 +1479,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? imageIndex)
{
var user = _userManager.GetUserById(userId);
- if (user == null)
+ if (user?.ProfileImage == null)
{
return NotFound();
}
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index f061755c3..f232dffaa 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given album.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given playlist.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given genre.
/// </summary>
/// <param name="name">The genre name.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{name}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreByName(
[FromRoute, Required] string name,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given artist.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -229,7 +229,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given genre.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -243,7 +243,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
@@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
- /// Creates an instant playlist based on a given song.
+ /// Creates an instant playlist based on a given item.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">Optional. Filter by user id, and attach user data.</param>
@@ -300,6 +300,80 @@ namespace Jellyfin.Api.Controllers
return GetResult(items, user, limit, dtoOptions);
}
+ /// <summary>
+ /// Creates an instant playlist based on a given artist.
+ /// </summary>
+ /// <param name="id">The item id.</param>
+ /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
+ /// <param name="limit">Optional. The maximum number of records to return.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
+ /// <param name="enableImages">Optional. Include image information in output.</param>
+ /// <param name="enableUserData">Optional. Include user data.</param>
+ /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
+ /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
+ /// <response code="200">Instant playlist returned.</response>
+ /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
+ [HttpGet("Artists/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Obsolete("Use GetInstantMixFromArtists")]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
+ [FromQuery, Required] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ {
+ return GetInstantMixFromArtists(
+ id,
+ userId,
+ limit,
+ fields,
+ enableImages,
+ enableUserData,
+ imageTypeLimit,
+ enableImageTypes);
+ }
+
+ /// <summary>
+ /// Creates an instant playlist based on a given genre.
+ /// </summary>
+ /// <param name="id">The item id.</param>
+ /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
+ /// <param name="limit">Optional. The maximum number of records to return.</param>
+ /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
+ /// <param name="enableImages">Optional. Include image information in output.</param>
+ /// <param name="enableUserData">Optional. Include user data.</param>
+ /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
+ /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
+ /// <response code="200">Instant playlist returned.</response>
+ /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
+ [HttpGet("MusicGenres/InstantMix")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [Obsolete("Use GetInstantMixFromMusicGenres instead")]
+ public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById2(
+ [FromQuery, Required] Guid id,
+ [FromQuery] Guid? userId,
+ [FromQuery] int? limit,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
+ [FromQuery] bool? enableImages,
+ [FromQuery] bool? enableUserData,
+ [FromQuery] int? imageTypeLimit,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
+ {
+ return GetInstantMixFromMusicGenreById(
+ id,
+ userId,
+ limit,
+ fields,
+ enableImages,
+ enableUserData,
+ imageTypeLimit,
+ enableImageTypes);
+ }
+
private QueryResult<BaseItemDto> GetResult(List<BaseItem> items, User? user, int? limit, DtoOptions dtoOptions)
{
var list = items;
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 3443ebd72..f8e8825ef 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -303,7 +303,7 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <response code="204">Library scan started.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
- [HttpGet("Library/Refresh")]
+ [HttpPost("Library/Refresh")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RefreshLibrary()
@@ -590,15 +590,15 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Reports that new movies have been added by an external source.
/// </summary>
- /// <param name="updates">A list of updated media paths.</param>
+ /// <param name="dto">The update paths.</param>
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Media/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto[] updates)
+ public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
{
- foreach (var item in updates)
+ foreach (var item in dto.Updates)
{
_libraryMonitor.ReportFileSystemChanged(item.Path);
}
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
index 0ceda6815..420630cdf 100644
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ b/Jellyfin.Api/Controllers/NotificationsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using Jellyfin.Api.Constants;
@@ -86,26 +87,19 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Sends a notification to all admins.
/// </summary>
- /// <param name="url">The URL of the notification.</param>
- /// <param name="level">The level of the notification.</param>
- /// <param name="name">The name of the notification.</param>
- /// <param name="description">The description of the notification.</param>
+ /// <param name="notificationDto">The notification request.</param>
/// <response code="204">Notification sent.</response>
/// <returns>A <cref see="NoContentResult"/>.</returns>
[HttpPost("Admin")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult CreateAdminNotification(
- [FromQuery] string? url,
- [FromQuery] NotificationLevel? level,
- [FromQuery] string name = "",
- [FromQuery] string description = "")
+ public ActionResult CreateAdminNotification([FromBody, Required] AdminNotificationDto notificationDto)
{
var notification = new NotificationRequest
{
- Name = name,
- Description = description,
- Url = url,
- Level = level ?? NotificationLevel.Normal,
+ Name = notificationDto.Name,
+ Description = notificationDto.Description,
+ Url = notificationDto.Url,
+ Level = notificationDto.NotificationLevel ?? NotificationLevel.Normal,
UserIds = _userManager.Users
.Where(user => user.HasPermission(PermissionKind.IsAdministrator))
.Select(user => user.Id)
@@ -114,7 +108,6 @@ namespace Jellyfin.Api.Controllers
};
_notificationManager.SendNotification(notification, CancellationToken.None);
-
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index a5aa9bfca..24285bfb9 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -45,7 +45,7 @@ namespace Jellyfin.Api.Controllers
{
_installationManager = installationManager;
_pluginManager = pluginManager;
- _serializerOptions = JsonDefaults.GetOptions();
+ _serializerOptions = JsonDefaults.Options;
_config = config;
}
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index d9cb34557..a01a617fc 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -132,7 +132,10 @@ namespace Jellyfin.Api.Controllers
{
var user = _userManager.Users.First();
- user.Username = startupUserDto.Name;
+ if (startupUserDto.Name != null)
+ {
+ user.Username = startupUserDto.Name;
+ }
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
index 991dbfc50..f93638898 100644
--- a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoDto.cs
@@ -1,4 +1,7 @@
-namespace Jellyfin.Api.Models.LibraryDtos
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Api.Models.LibraryDtos
{
/// <summary>
/// Media Update Info Dto.
@@ -6,14 +9,8 @@
public class MediaUpdateInfoDto
{
/// <summary>
- /// Gets or sets media path.
- /// </summary>
- public string? Path { get; set; }
-
- /// <summary>
- /// Gets or sets media update type.
- /// Created, Modified, Deleted.
+ /// Gets or sets the list of updates.
/// </summary>
- public string? UpdateType { get; set; }
+ public IReadOnlyList<MediaUpdateInfoPathDto> Updates { get; set; } = Array.Empty<MediaUpdateInfoPathDto>();
}
}
diff --git a/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
new file mode 100644
index 000000000..852315b92
--- /dev/null
+++ b/Jellyfin.Api/Models/LibraryDtos/MediaUpdateInfoPathDto.cs
@@ -0,0 +1,19 @@
+namespace Jellyfin.Api.Models.LibraryDtos
+{
+ /// <summary>
+ /// The media update info path.
+ /// </summary>
+ public class MediaUpdateInfoPathDto
+ {
+ /// <summary>
+ /// Gets or sets media path.
+ /// </summary>
+ public string? Path { get; set; }
+
+ /// <summary>
+ /// Gets or sets media update type.
+ /// Created, Modified, Deleted.
+ /// </summary>
+ public string? UpdateType { get; set; }
+ }
+}
diff --git a/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
new file mode 100644
index 000000000..2c3a6282f
--- /dev/null
+++ b/Jellyfin.Api/Models/NotificationDtos/AdminNotificationDto.cs
@@ -0,0 +1,30 @@
+using MediaBrowser.Model.Notifications;
+
+namespace Jellyfin.Api.Models.NotificationDtos
+{
+ /// <summary>
+ /// The admin notification dto.
+ /// </summary>
+ public class AdminNotificationDto
+ {
+ /// <summary>
+ /// Gets or sets the notification name.
+ /// </summary>
+ public string? Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the notification description.
+ /// </summary>
+ public string? Description { get; set; }
+
+ /// <summary>
+ /// Gets or sets the notification level.
+ /// </summary>
+ public NotificationLevel? NotificationLevel { get; set; }
+
+ /// <summary>
+ /// Gets or sets the notification url.
+ /// </summary>
+ public string? Url { get; set; }
+ }
+}
diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs
index 4e75f4cfd..b7ba30180 100644
--- a/Jellyfin.Data/DayOfWeekHelper.cs
+++ b/Jellyfin.Data/DayOfWeekHelper.cs
@@ -1,67 +1,21 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
using Jellyfin.Data.Enums;
namespace Jellyfin.Data
{
public static class DayOfWeekHelper
{
- public static List<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day)
+ public static DayOfWeek[] GetDaysOfWeek(DynamicDayOfWeek day)
{
- var days = new List<DayOfWeek>(7);
-
- if (day == DynamicDayOfWeek.Sunday
- || day == DynamicDayOfWeek.Weekend
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Sunday);
- }
-
- if (day == DynamicDayOfWeek.Monday
- || day == DynamicDayOfWeek.Weekday
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Monday);
- }
-
- if (day == DynamicDayOfWeek.Tuesday
- || day == DynamicDayOfWeek.Weekday
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Tuesday);
- }
-
- if (day == DynamicDayOfWeek.Wednesday
- || day == DynamicDayOfWeek.Weekday
- || day == DynamicDayOfWeek.Everyday)
+ return day switch
{
- days.Add(DayOfWeek.Wednesday);
- }
-
- if (day == DynamicDayOfWeek.Thursday
- || day == DynamicDayOfWeek.Weekday
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Thursday);
- }
-
- if (day == DynamicDayOfWeek.Friday
- || day == DynamicDayOfWeek.Weekday
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Friday);
- }
-
- if (day == DynamicDayOfWeek.Saturday
- || day == DynamicDayOfWeek.Weekend
- || day == DynamicDayOfWeek.Everyday)
- {
- days.Add(DayOfWeek.Saturday);
- }
-
- return days;
+ DynamicDayOfWeek.Everyday => new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday },
+ DynamicDayOfWeek.Weekday => new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday },
+ DynamicDayOfWeek.Weekend => new[] { DayOfWeek.Sunday, DayOfWeek.Saturday },
+ _ => new[] { (DayOfWeek)day }
+ };
}
}
}
diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs
index 7d1b76a3f..7974d3add 100644
--- a/Jellyfin.Data/Entities/AccessSchedule.cs
+++ b/Jellyfin.Data/Entities/AccessSchedule.cs
@@ -1,7 +1,5 @@
using System;
-using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using System.Text.Json.Serialization;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
@@ -28,22 +26,12 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="AccessSchedule"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected AccessSchedule()
- {
- }
-
- /// <summary>
/// Gets or sets the id of this instance.
/// </summary>
/// <remarks>
/// Identity, Indexed, Required.
/// </remarks>
[XmlIgnore]
- [Key]
- [Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
@@ -51,41 +39,24 @@ namespace Jellyfin.Data.Entities
/// Gets or sets the id of the associated user.
/// </summary>
[XmlIgnore]
- [Required]
public Guid UserId { get; protected set; }
/// <summary>
/// Gets or sets the day of week.
/// </summary>
/// <value>The day of week.</value>
- [Required]
public DynamicDayOfWeek DayOfWeek { get; set; }
/// <summary>
/// Gets or sets the start hour.
/// </summary>
/// <value>The start hour.</value>
- [Required]
public double StartHour { get; set; }
/// <summary>
/// Gets or sets the end hour.
/// </summary>
/// <value>The end hour.</value>
- [Required]
public double EndHour { get; set; }
-
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="dayOfWeek">The day of the week.</param>
- /// <param name="startHour">The start hour.</param>
- /// <param name="endHour">The end hour.</param>
- /// <param name="userId">The associated user's id.</param>
- /// <returns>The newly created instance.</returns>
- public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
- {
- return new AccessSchedule(dayOfWeek, startHour, endHour, userId);
- }
}
}
diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs
index e2d5c7187..e4534e8b5 100644
--- a/Jellyfin.Data/Entities/ActivityLog.cs
+++ b/Jellyfin.Data/Entities/ActivityLog.cs
@@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
/// <param name="userId">The user id.</param>
- /// <param name="logLevel">The log level.</param>
- public ActivityLog(string name, string type, Guid userId, LogLevel logLevel = LogLevel.Information)
+ public ActivityLog(string name, string type, Guid userId)
{
if (string.IsNullOrEmpty(name))
{
@@ -35,15 +34,7 @@ namespace Jellyfin.Data.Entities
Type = type;
UserId = userId;
DateCreated = DateTime.UtcNow;
- LogSeverity = logLevel;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ActivityLog"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected ActivityLog()
- {
+ LogSeverity = LogLevel.Information;
}
/// <summary>
@@ -59,7 +50,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 512.
/// </remarks>
- [Required]
[MaxLength(512)]
[StringLength(512)]
public string Name { get; set; }
@@ -72,7 +62,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(512)]
[StringLength(512)]
- public string Overview { get; set; }
+ public string? Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
@@ -82,7 +72,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(512)]
[StringLength(512)]
- public string ShortOverview { get; set; }
+ public string? ShortOverview { get; set; }
/// <summary>
/// Gets or sets the type.
@@ -90,7 +80,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 256.
/// </remarks>
- [Required]
[MaxLength(256)]
[StringLength(256)]
public string Type { get; set; }
@@ -111,7 +100,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(256)]
[StringLength(256)]
- public string ItemId { get; set; }
+ public string? ItemId { get; set; }
/// <summary>
/// Gets or sets the date created. This should be in UTC.
diff --git a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
index 511e3b281..cc46248c7 100644
--- a/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/CustomItemDisplayPreferences.cs
@@ -15,22 +15,15 @@ namespace Jellyfin.Data.Entities
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="client">The client.</param>
- /// <param name="preferenceKey">The preference key.</param>
- /// <param name="preferenceValue">The preference value.</param>
- public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string preferenceKey, string preferenceValue)
+ /// <param name="key">The preference key.</param>
+ /// <param name="value">The preference value.</param>
+ public CustomItemDisplayPreferences(Guid userId, Guid itemId, string client, string key, string value)
{
UserId = userId;
ItemId = itemId;
Client = client;
- Key = preferenceKey;
- Value = preferenceValue;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CustomItemDisplayPreferences"/> class.
- /// </summary>
- protected CustomItemDisplayPreferences()
- {
+ Key = key;
+ Value = value;
}
/// <summary>
@@ -64,7 +57,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required. Max Length = 32.
/// </remarks>
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -75,7 +67,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public string Key { get; set; }
/// <summary>
@@ -84,7 +75,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public string Value { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/DisplayPreferences.cs b/Jellyfin.Data/Entities/DisplayPreferences.cs
index 1a8ca1da3..64cd6812a 100644
--- a/Jellyfin.Data/Entities/DisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/DisplayPreferences.cs
@@ -30,20 +30,11 @@ namespace Jellyfin.Data.Entities
SkipBackwardLength = 10000;
ScrollDirection = ScrollDirection.Horizontal;
ChromecastVersion = ChromecastVersion.Stable;
- DashboardTheme = string.Empty;
- TvHome = string.Empty;
HomeSections = new HashSet<HomeSection>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="DisplayPreferences"/> class.
- /// </summary>
- protected DisplayPreferences()
- {
- }
-
- /// <summary>
/// Gets or sets the Id.
/// </summary>
/// <remarks>
@@ -74,7 +65,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required. Max Length = 32.
/// </remarks>
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -145,14 +135,14 @@ namespace Jellyfin.Data.Entities
/// </summary>
[MaxLength(32)]
[StringLength(32)]
- public string DashboardTheme { get; set; }
+ public string? DashboardTheme { get; set; }
/// <summary>
/// Gets or sets the tv home screen.
/// </summary>
[MaxLength(32)]
[StringLength(32)]
- public string TvHome { get; set; }
+ public string? TvHome { get; set; }
/// <summary>
/// Gets or sets the home sections.
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
index 878811e59..b14e22b7b 100644
--- a/Jellyfin.Data/Entities/Group.cs
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -33,16 +33,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="Group"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Group()
- {
- }
-
- /// <summary>
/// Gets or sets the id of this group.
/// </summary>
/// <remarks>
@@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/HomeSection.cs b/Jellyfin.Data/Entities/HomeSection.cs
index 062046260..5adc52491 100644
--- a/Jellyfin.Data/Entities/HomeSection.cs
+++ b/Jellyfin.Data/Entities/HomeSection.cs
@@ -15,7 +15,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Identity. Required.
/// </remarks>
- [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs
index ab8452e62..e0c37047d 100644
--- a/Jellyfin.Data/Entities/ImageInfo.cs
+++ b/Jellyfin.Data/Entities/ImageInfo.cs
@@ -20,16 +20,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="ImageInfo"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected ImageInfo()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
[MaxLength(512)]
[StringLength(512)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
index 2b25bb25f..4bfeb2fa3 100644
--- a/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
+++ b/Jellyfin.Data/Entities/ItemDisplayPreferences.cs
@@ -29,13 +29,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="ItemDisplayPreferences"/> class.
- /// </summary>
- protected ItemDisplayPreferences()
- {
- }
-
- /// <summary>
/// Gets or sets the Id.
/// </summary>
/// <remarks>
@@ -66,7 +59,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required. Max Length = 32.
/// </remarks>
- [Required]
[MaxLength(32)]
[StringLength(32)]
public string Client { get; set; }
@@ -106,7 +98,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
[MaxLength(64)]
[StringLength(64)]
public string SortBy { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Artwork.cs b/Jellyfin.Data/Entities/Libraries/Artwork.cs
index 06cd33330..84a524de2 100644
--- a/Jellyfin.Data/Entities/Libraries/Artwork.cs
+++ b/Jellyfin.Data/Entities/Libraries/Artwork.cs
@@ -18,8 +18,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="path">The path.</param>
/// <param name="kind">The kind of art.</param>
- /// <param name="owner">The owner.</param>
- public Artwork(string path, ArtKind kind, IHasArtwork owner)
+ public Artwork(string path, ArtKind kind)
{
if (string.IsNullOrEmpty(path))
{
@@ -28,18 +27,6 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path;
Kind = kind;
-
- owner?.Artwork.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Artwork"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Artwork()
- {
}
/// <summary>
@@ -57,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Book.cs b/Jellyfin.Data/Entities/Libraries/Book.cs
index 2e63f75bd..aea3d58d5 100644
--- a/Jellyfin.Data/Entities/Libraries/Book.cs
+++ b/Jellyfin.Data/Entities/Libraries/Book.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Book"/> class.
/// </summary>
- public Book()
+ /// <param name="library">The library.</param>
+ public Book(Library library) : base(library)
{
BookMetadata = new HashSet<BookMetadata>();
Releases = new HashSet<Release>();
diff --git a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
index 4a3d290f0..1ff4327b0 100644
--- a/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/BookMetadata.cs
@@ -1,8 +1,6 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries
@@ -17,30 +15,12 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="book">The book.</param>
- public BookMetadata(string title, string language, Book book) : base(title, language)
+ public BookMetadata(string title, string language) : base(title, language)
{
- if (book == null)
- {
- throw new ArgumentNullException(nameof(book));
- }
-
- book.BookMetadata.Add(this);
-
Publishers = new HashSet<Company>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="BookMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected BookMetadata()
- {
- }
-
- /// <summary>
/// Gets or sets the ISBN.
/// </summary>
public long? Isbn { get; set; }
@@ -51,7 +31,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Publishers { get; protected set; }
/// <inheritdoc />
- [NotMapped]
public ICollection<Company> Companies => Publishers;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Chapter.cs b/Jellyfin.Data/Entities/Libraries/Chapter.cs
index f503de379..11f53ae20 100644
--- a/Jellyfin.Data/Entities/Libraries/Chapter.cs
+++ b/Jellyfin.Data/Entities/Libraries/Chapter.cs
@@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="language">ISO-639-3 3-character language codes.</param>
/// <param name="startTime">The start time for this chapter.</param>
- /// <param name="release">The release.</param>
- public Chapter(string language, long startTime, Release release)
+ public Chapter(string language, long startTime)
{
if (string.IsNullOrEmpty(language))
{
@@ -27,23 +26,6 @@ namespace Jellyfin.Data.Entities.Libraries
Language = language;
StartTime = startTime;
-
- if (release == null)
- {
- throw new ArgumentNullException(nameof(release));
- }
-
- release.Chapters.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Chapter"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Chapter()
- {
}
/// <summary>
@@ -63,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the language.
@@ -72,7 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3
/// ISO-639-3 3-character language codes.
/// </remarks>
- [Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]
diff --git a/Jellyfin.Data/Entities/Libraries/Collection.cs b/Jellyfin.Data/Entities/Libraries/Collection.cs
index 39eded752..4253b7ecc 100644
--- a/Jellyfin.Data/Entities/Libraries/Collection.cs
+++ b/Jellyfin.Data/Entities/Libraries/Collection.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
#pragma warning disable CA2227
using System.Collections.Generic;
@@ -37,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <inheritdoc />
[ConcurrencyCheck]
diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
index f9539964d..e19362bdf 100644
--- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -13,39 +12,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="CollectionItem"/> class.
/// </summary>
- /// <param name="collection">The collection.</param>
- /// <param name="previous">The previous item.</param>
- /// <param name="next">The next item.</param>
- public CollectionItem(Collection collection, CollectionItem previous, CollectionItem next)
- {
- if (collection == null)
- {
- throw new ArgumentNullException(nameof(collection));
- }
-
- collection.Items.Add(this);
-
- if (next != null)
- {
- Next = next;
- next.Previous = this;
- }
-
- if (previous != null)
- {
- Previous = previous;
- previous.Next = this;
- }
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CollectionItem"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected CollectionItem()
+ /// <param name="libraryItem">The library item.</param>
+ public CollectionItem(LibraryItem libraryItem)
{
+ LibraryItem = libraryItem;
}
/// <summary>
@@ -75,7 +45,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks>
- public virtual CollectionItem Next { get; set; }
+ public virtual CollectionItem? Next { get; set; }
/// <summary>
/// Gets or sets the previous item in the collection.
@@ -83,7 +53,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// TODO check if this properly updated Dependant and has the proper principal relationship.
/// </remarks>
- public virtual CollectionItem Previous { get; set; }
+ public virtual CollectionItem? Previous { get; set; }
/// <inheritdoc />
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/Company.cs b/Jellyfin.Data/Entities/Libraries/Company.cs
index 3b6ed3309..09050bb52 100644
--- a/Jellyfin.Data/Entities/Libraries/Company.cs
+++ b/Jellyfin.Data/Entities/Libraries/Company.cs
@@ -15,22 +15,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Company"/> class.
/// </summary>
- /// <param name="owner">The owner of this company.</param>
- public Company(IHasCompanies owner)
+ public Company()
{
- owner?.Companies.Add(this);
-
CompanyMetadata = new HashSet<CompanyMetadata>();
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Company"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Company()
- {
+ ChildCompanies = new HashSet<Company>();
}
/// <summary>
@@ -57,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> ChildCompanies { get; protected set; }
/// <inheritdoc />
- [NotMapped]
public ICollection<Company> Companies => ChildCompanies;
/// <inheritdoc />
diff --git a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
index 8aa0486af..a29f08c7f 100644
--- a/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,21 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="company">The company.</param>
- public CompanyMetadata(string title, string language, Company company) : base(title, language)
- {
- if (company == null)
- {
- throw new ArgumentNullException(nameof(company));
- }
-
- company.CompanyMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
- /// </summary>
- protected CompanyMetadata()
+ public CompanyMetadata(string title, string language) : base(title, language)
{
}
@@ -39,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string Description { get; set; }
+ public string? Description { get; set; }
/// <summary>
/// Gets or sets the headquarters.
@@ -49,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string Headquarters { get; set; }
+ public string? Headquarters { get; set; }
/// <summary>
/// Gets or sets the country code.
@@ -59,7 +44,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
/// <summary>
/// Gets or sets the homepage.
@@ -69,6 +54,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Homepage { get; set; }
+ public string? Homepage { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItem.cs b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
index 115489c78..88d1a0c25 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItem.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="CustomItem"/> class.
/// </summary>
- public CustomItem()
+ /// <param name="library">The library.</param>
+ public CustomItem(Library library) : base(library)
{
CustomItemMetadata = new HashSet<CustomItemMetadata>();
Releases = new HashSet<Release>();
diff --git a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
index f0daedfbe..af2393870 100644
--- a/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="item">The item.</param>
- public CustomItemMetadata(string title, string language, CustomItem item) : base(title, language)
- {
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
-
- item.CustomItemMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected CustomItemMetadata()
+ public CustomItemMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Episode.cs b/Jellyfin.Data/Entities/Libraries/Episode.cs
index 0bdc2d764..458c7d9f5 100644
--- a/Jellyfin.Data/Entities/Libraries/Episode.cs
+++ b/Jellyfin.Data/Entities/Libraries/Episode.cs
@@ -1,6 +1,5 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
@@ -14,31 +13,14 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Episode"/> class.
/// </summary>
- /// <param name="season">The season.</param>
- public Episode(Season season)
+ /// <param name="library">The library.</param>
+ public Episode(Library library) : base(library)
{
- if (season == null)
- {
- throw new ArgumentNullException(nameof(season));
- }
-
- season.Episodes.Add(this);
-
Releases = new HashSet<Release>();
EpisodeMetadata = new HashSet<EpisodeMetadata>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="Episode"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Episode()
- {
- }
-
- /// <summary>
/// Gets or sets the episode number.
/// </summary>
public int? EpisodeNumber { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
index 7efb840f0..b0ef11e0f 100644
--- a/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="episode">The episode.</param>
- public EpisodeMetadata(string title, string language, Episode episode) : base(title, language)
- {
- if (episode == null)
- {
- throw new ArgumentNullException(nameof(episode));
- }
-
- episode.EpisodeMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected EpisodeMetadata()
+ public EpisodeMetadata(string title, string language) : base(title, language)
{
}
@@ -42,7 +24,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
/// <summary>
/// Gets or sets the plot.
@@ -52,7 +34,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
/// <summary>
/// Gets or sets the tagline.
@@ -62,6 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Genre.cs b/Jellyfin.Data/Entities/Libraries/Genre.cs
index 2a2dbd1a5..9f3d65028 100644
--- a/Jellyfin.Data/Entities/Libraries/Genre.cs
+++ b/Jellyfin.Data/Entities/Libraries/Genre.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -14,32 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Genre"/> class.
/// </summary>
/// <param name="name">The name.</param>
- /// <param name="itemMetadata">The metadata.</param>
- public Genre(string name, ItemMetadata itemMetadata)
+ public Genre(string name)
{
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
-
Name = name;
-
- if (itemMetadata == null)
- {
- throw new ArgumentNullException(nameof(itemMetadata));
- }
-
- itemMetadata.Genres.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Genre"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Genre()
- {
}
/// <summary>
@@ -57,7 +33,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Indexed, Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
index d74330c05..d12e011a8 100644
--- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
@@ -43,16 +43,6 @@ namespace Jellyfin.Data.Entities.Libraries
}
/// <summary>
- /// Initializes a new instance of the <see cref="ItemMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to being abstract.
- /// </remarks>
- protected ItemMetadata()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -67,7 +57,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Title { get; set; }
@@ -80,7 +69,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string OriginalTitle { get; set; }
+ public string? OriginalTitle { get; set; }
/// <summary>
/// Gets or sets the sort title.
@@ -90,7 +79,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string SortTitle { get; set; }
+ public string? SortTitle { get; set; }
/// <summary>
/// Gets or sets the language.
@@ -99,7 +88,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// Required, Min length = 3, Max length = 3.
/// ISO-639-3 3-character language codes.
/// </remarks>
- [Required]
[MinLength(3)]
[MaxLength(3)]
[StringLength(3)]
diff --git a/Jellyfin.Data/Entities/Libraries/Library.cs b/Jellyfin.Data/Entities/Libraries/Library.cs
index 4f82a2e2a..e45384902 100644
--- a/Jellyfin.Data/Entities/Libraries/Library.cs
+++ b/Jellyfin.Data/Entities/Libraries/Library.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -14,24 +13,11 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Library"/> class.
/// </summary>
/// <param name="name">The name of the library.</param>
- public Library(string name)
+ /// <param name="path">The path of the library.</param>
+ public Library(string name, string path)
{
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
-
Name = name;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Library"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Library()
- {
+ Path = path;
}
/// <summary>
@@ -49,7 +35,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 128.
/// </remarks>
- [Required]
[MaxLength(128)]
[StringLength(128)]
public string Name { get; set; }
@@ -60,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public string Path { get; set; }
/// <inheritdoc />
diff --git a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
index a9167aa7f..67ffad944 100644
--- a/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
+++ b/Jellyfin.Data/Entities/Libraries/LibraryItem.cs
@@ -21,13 +21,6 @@ namespace Jellyfin.Data.Entities.Libraries
}
/// <summary>
- /// Initializes a new instance of the <see cref="LibraryItem"/> class.
- /// </summary>
- protected LibraryItem()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -51,7 +44,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public virtual Library Library { get; set; }
/// <inheritdoc />
diff --git a/Jellyfin.Data/Entities/Libraries/MediaFile.cs b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
index 9924d5728..f3e2fe653 100644
--- a/Jellyfin.Data/Entities/Libraries/MediaFile.cs
+++ b/Jellyfin.Data/Entities/Libraries/MediaFile.cs
@@ -19,8 +19,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="path">The path relative to the LibraryRoot.</param>
/// <param name="kind">The file kind.</param>
- /// <param name="release">The release.</param>
- public MediaFile(string path, MediaFileKind kind, Release release)
+ public MediaFile(string path, MediaFileKind kind)
{
if (string.IsNullOrEmpty(path))
{
@@ -30,27 +29,10 @@ namespace Jellyfin.Data.Entities.Libraries
Path = path;
Kind = kind;
- if (release == null)
- {
- throw new ArgumentNullException(nameof(release));
- }
-
- release.MediaFiles.Add(this);
-
MediaFileStreams = new HashSet<MediaFileStream>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="MediaFile"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MediaFile()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -65,7 +47,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Path { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
index 5b03e260e..ba21294fc 100644
--- a/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
+++ b/Jellyfin.Data/Entities/Libraries/MediaFileStream.cs
@@ -1,4 +1,5 @@
-using System;
+#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
+
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -14,27 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MediaFileStream"/> class.
/// </summary>
/// <param name="streamNumber">The number of this stream.</param>
- /// <param name="mediaFile">The media file.</param>
- public MediaFileStream(int streamNumber, MediaFile mediaFile)
+ public MediaFileStream(int streamNumber)
{
StreamNumber = streamNumber;
-
- if (mediaFile == null)
- {
- throw new ArgumentNullException(nameof(mediaFile));
- }
-
- mediaFile.MediaFileStreams.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MediaFileStream"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MediaFileStream()
- {
}
/// <summary>
diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
index a18a612bc..fb2587882 100644
--- a/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
+++ b/Jellyfin.Data/Entities/Libraries/MetadataProvider.cs
@@ -25,16 +25,6 @@ namespace Jellyfin.Data.Entities.Libraries
}
/// <summary>
- /// Initializes a new instance of the <see cref="MetadataProvider"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MetadataProvider()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -49,7 +39,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
index fcfb35bfa..2a9c904c8 100644
--- a/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
+++ b/Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
/// </summary>
/// <param name="providerId">The provider id.</param>
- /// <param name="itemMetadata">The metadata entity.</param>
- public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
+ /// <param name="metadataProvider">The metadata provider.</param>
+ public MetadataProviderId(string providerId, MetadataProvider metadataProvider)
{
if (string.IsNullOrEmpty(providerId))
{
@@ -23,23 +23,7 @@ namespace Jellyfin.Data.Entities.Libraries
}
ProviderId = providerId;
-
- if (itemMetadata == null)
- {
- throw new ArgumentNullException(nameof(itemMetadata));
- }
-
- itemMetadata.Sources.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MetadataProviderId()
- {
+ MetadataProvider = metadataProvider;
}
/// <summary>
@@ -57,7 +41,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string ProviderId { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Movie.cs b/Jellyfin.Data/Entities/Libraries/Movie.cs
index 08db904fa..f89cacff4 100644
--- a/Jellyfin.Data/Entities/Libraries/Movie.cs
+++ b/Jellyfin.Data/Entities/Libraries/Movie.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Movie"/> class.
/// </summary>
- public Movie()
+ /// <param name="library">The library.</param>
+ public Movie(Library library) : base(library)
{
Releases = new HashSet<Release>();
MovieMetadata = new HashSet<MovieMetadata>();
diff --git a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
index aa1501a5c..fb181dea6 100644
--- a/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
namespace Jellyfin.Data.Entities.Libraries
@@ -17,22 +16,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the movie.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="movie">The movie.</param>
- public MovieMetadata(string title, string language, Movie movie) : base(title, language)
+ public MovieMetadata(string title, string language) : base(title, language)
{
Studios = new HashSet<Company>();
-
- movie.MovieMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MovieMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MovieMetadata()
- {
}
/// <summary>
@@ -43,7 +29,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
/// <summary>
/// Gets or sets the tagline.
@@ -53,7 +39,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
/// <summary>
/// Gets or sets the plot.
@@ -63,7 +49,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
/// <summary>
/// Gets or sets the country code.
@@ -73,7 +59,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
/// <summary>
/// Gets or sets the studios that produced this movie.
@@ -81,7 +67,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Studios { get; protected set; }
/// <inheritdoc />
- [NotMapped]
public ICollection<Company> Companies => Studios;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
index 06aff6f45..4049cdac8 100644
--- a/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
+++ b/Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
@@ -12,7 +12,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbum"/> class.
/// </summary>
- public MusicAlbum()
+ /// <param name="library">The library.</param>
+ public MusicAlbum(Library library) : base(library)
{
MusicAlbumMetadata = new HashSet<MusicAlbumMetadata>();
Tracks = new HashSet<Track>();
diff --git a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
index 05c0b0374..3080bd692 100644
--- a/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
@@ -15,22 +15,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the album.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="album">The music album.</param>
- public MusicAlbumMetadata(string title, string language, MusicAlbum album) : base(title, language)
+ public MusicAlbumMetadata(string title, string language) : base(title, language)
{
Labels = new HashSet<Company>();
-
- album.MusicAlbumMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected MusicAlbumMetadata()
- {
}
/// <summary>
@@ -41,7 +28,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string Barcode { get; set; }
+ public string? Barcode { get; set; }
/// <summary>
/// Gets or sets the label number.
@@ -51,7 +38,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string LabelNumber { get; set; }
+ public string? LabelNumber { get; set; }
/// <summary>
/// Gets or sets the country code.
@@ -61,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
/// <summary>
/// Gets or sets a collection containing the labels.
diff --git a/Jellyfin.Data/Entities/Libraries/Person.cs b/Jellyfin.Data/Entities/Libraries/Person.cs
index af4c87b73..159bd47be 100644
--- a/Jellyfin.Data/Entities/Libraries/Person.cs
+++ b/Jellyfin.Data/Entities/Libraries/Person.cs
@@ -32,16 +32,6 @@ namespace Jellyfin.Data.Entities.Libraries
}
/// <summary>
- /// Initializes a new instance of the <see cref="Person"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Person()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -56,7 +46,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
@@ -69,7 +58,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(256)]
[StringLength(256)]
- public string SourceId { get; set; }
+ public string? SourceId { get; set; }
/// <summary>
/// Gets or sets the date added.
diff --git a/Jellyfin.Data/Entities/Libraries/PersonRole.cs b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
index cd38ee83d..988aa84ba 100644
--- a/Jellyfin.Data/Entities/Libraries/PersonRole.cs
+++ b/Jellyfin.Data/Entities/Libraries/PersonRole.cs
@@ -1,6 +1,5 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -18,32 +17,16 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="PersonRole"/> class.
/// </summary>
/// <param name="type">The role type.</param>
- /// <param name="itemMetadata">The metadata.</param>
- public PersonRole(PersonRoleType type, ItemMetadata itemMetadata)
+ /// <param name="person">The person.</param>
+ public PersonRole(PersonRoleType type, Person person)
{
Type = type;
-
- if (itemMetadata == null)
- {
- throw new ArgumentNullException(nameof(itemMetadata));
- }
-
- itemMetadata.PersonRoles.Add(this);
-
+ Person = person;
+ Artwork = new HashSet<Artwork>();
Sources = new HashSet<MetadataProviderId>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="PersonRole"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected PersonRole()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -60,7 +43,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Role { get; set; }
+ public string? Role { get; set; }
/// <summary>
/// Gets or sets the person's role type.
@@ -80,7 +63,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required.
/// </remarks>
- [Required]
public virtual Person Person { get; set; }
/// <inheritdoc />
diff --git a/Jellyfin.Data/Entities/Libraries/Photo.cs b/Jellyfin.Data/Entities/Libraries/Photo.cs
index 25562ec96..eb5c96267 100644
--- a/Jellyfin.Data/Entities/Libraries/Photo.cs
+++ b/Jellyfin.Data/Entities/Libraries/Photo.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Photo"/> class.
/// </summary>
- public Photo()
+ /// <param name="library">The library.</param>
+ public Photo(Library library) : base(library)
{
PhotoMetadata = new HashSet<PhotoMetadata>();
Releases = new HashSet<Release>();
diff --git a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
index ffc790b57..6c284307d 100644
--- a/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the photo.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="photo">The photo.</param>
- public PhotoMetadata(string title, string language, Photo photo) : base(title, language)
- {
- if (photo == null)
- {
- throw new ArgumentNullException(nameof(photo));
- }
-
- photo.PhotoMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected PhotoMetadata()
+ public PhotoMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Rating.cs b/Jellyfin.Data/Entities/Libraries/Rating.cs
index 98226cd80..6862012a8 100644
--- a/Jellyfin.Data/Entities/Libraries/Rating.cs
+++ b/Jellyfin.Data/Entities/Libraries/Rating.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -14,27 +13,9 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Rating"/> class.
/// </summary>
/// <param name="value">The value.</param>
- /// <param name="itemMetadata">The metadata.</param>
- public Rating(double value, ItemMetadata itemMetadata)
+ public Rating(double value)
{
Value = value;
-
- if (itemMetadata == null)
- {
- throw new ArgumentNullException(nameof(itemMetadata));
- }
-
- itemMetadata.Ratings.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Rating"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Rating()
- {
}
/// <summary>
@@ -67,7 +48,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Gets or sets the rating type.
/// If this is <c>null</c> it's the internal user rating.
/// </summary>
- public virtual RatingSource RatingType { get; set; }
+ public virtual RatingSource? RatingType { get; set; }
/// <inheritdoc />
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/RatingSource.cs b/Jellyfin.Data/Entities/Libraries/RatingSource.cs
index 549f41804..ae0d806ff 100644
--- a/Jellyfin.Data/Entities/Libraries/RatingSource.cs
+++ b/Jellyfin.Data/Entities/Libraries/RatingSource.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Interfaces;
@@ -15,28 +14,10 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="minimumValue">The minimum value.</param>
/// <param name="maximumValue">The maximum value.</param>
- /// <param name="rating">The rating.</param>
- public RatingSource(double minimumValue, double maximumValue, Rating rating)
+ public RatingSource(double minimumValue, double maximumValue)
{
MinimumValue = minimumValue;
MaximumValue = maximumValue;
-
- if (rating == null)
- {
- throw new ArgumentNullException(nameof(rating));
- }
-
- rating.RatingType = this;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="RatingSource"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected RatingSource()
- {
}
/// <summary>
@@ -56,7 +37,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the minimum value.
@@ -81,7 +62,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Gets or sets the metadata source.
/// </summary>
- public virtual MetadataProviderId Source { get; set; }
+ public virtual MetadataProviderId? Source { get; set; }
/// <inheritdoc />
public void OnSavingChanges()
diff --git a/Jellyfin.Data/Entities/Libraries/Release.cs b/Jellyfin.Data/Entities/Libraries/Release.cs
index b633e08fb..21d403979 100644
--- a/Jellyfin.Data/Entities/Libraries/Release.cs
+++ b/Jellyfin.Data/Entities/Libraries/Release.cs
@@ -17,8 +17,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// Initializes a new instance of the <see cref="Release"/> class.
/// </summary>
/// <param name="name">The name of this release.</param>
- /// <param name="owner">The owner of this release.</param>
- public Release(string name, IHasReleases owner)
+ public Release(string name)
{
if (string.IsNullOrEmpty(name))
{
@@ -27,23 +26,11 @@ namespace Jellyfin.Data.Entities.Libraries
Name = name;
- owner?.Releases.Add(this);
-
MediaFiles = new HashSet<MediaFile>();
Chapters = new HashSet<Chapter>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="Release"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Release()
- {
- }
-
- /// <summary>
/// Gets or sets the id.
/// </summary>
/// <remarks>
@@ -58,7 +45,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// <remarks>
/// Required, Max length = 1024.
/// </remarks>
- [Required]
[MaxLength(1024)]
[StringLength(1024)]
public string Name { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/Season.cs b/Jellyfin.Data/Entities/Libraries/Season.cs
index eb6674dbc..04f723a1d 100644
--- a/Jellyfin.Data/Entities/Libraries/Season.cs
+++ b/Jellyfin.Data/Entities/Libraries/Season.cs
@@ -1,6 +1,5 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,31 +12,14 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Season"/> class.
/// </summary>
- /// <param name="series">The series.</param>
- public Season(Series series)
+ /// <param name="library">The library.</param>
+ public Season(Library library) : base(library)
{
- if (series == null)
- {
- throw new ArgumentNullException(nameof(series));
- }
-
- series.Seasons.Add(this);
-
Episodes = new HashSet<Episode>();
SeasonMetadata = new HashSet<SeasonMetadata>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="Season"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Season()
- {
- }
-
- /// <summary>
/// Gets or sets the season number.
/// </summary>
public int? SeasonNumber { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
index 7ce79756b..da40a075f 100644
--- a/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Data.Entities.Libraries
@@ -13,24 +12,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="season">The season.</param>
- public SeasonMetadata(string title, string language, Season season) : base(title, language)
- {
- if (season == null)
- {
- throw new ArgumentNullException(nameof(season));
- }
-
- season.SeasonMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected SeasonMetadata()
+ public SeasonMetadata(string title, string language) : base(title, language)
{
}
@@ -42,6 +24,6 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Series.cs b/Jellyfin.Data/Entities/Libraries/Series.cs
index 8c8317d14..59508831e 100644
--- a/Jellyfin.Data/Entities/Libraries/Series.cs
+++ b/Jellyfin.Data/Entities/Libraries/Series.cs
@@ -13,7 +13,8 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Series"/> class.
/// </summary>
- public Series()
+ /// <param name="library">The library.</param>
+ public Series(Library library) : base(library)
{
DateAdded = DateTime.UtcNow;
Seasons = new HashSet<Season>();
diff --git a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
index 877dbfc69..730deccae 100644
--- a/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
@@ -1,6 +1,5 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -18,30 +17,12 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="series">The series.</param>
- public SeriesMetadata(string title, string language, Series series) : base(title, language)
+ public SeriesMetadata(string title, string language) : base(title, language)
{
- if (series == null)
- {
- throw new ArgumentNullException(nameof(series));
- }
-
- series.SeriesMetadata.Add(this);
-
Networks = new HashSet<Company>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected SeriesMetadata()
- {
- }
-
- /// <summary>
/// Gets or sets the outline.
/// </summary>
/// <remarks>
@@ -49,7 +30,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Outline { get; set; }
+ public string? Outline { get; set; }
/// <summary>
/// Gets or sets the plot.
@@ -59,7 +40,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string Plot { get; set; }
+ public string? Plot { get; set; }
/// <summary>
/// Gets or sets the tagline.
@@ -69,7 +50,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(1024)]
[StringLength(1024)]
- public string Tagline { get; set; }
+ public string? Tagline { get; set; }
/// <summary>
/// Gets or sets the country code.
@@ -79,7 +60,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </remarks>
[MaxLength(2)]
[StringLength(2)]
- public string Country { get; set; }
+ public string? Country { get; set; }
/// <summary>
/// Gets or sets a collection containing the networks.
@@ -87,7 +68,6 @@ namespace Jellyfin.Data.Entities.Libraries
public virtual ICollection<Company> Networks { get; protected set; }
/// <inheritdoc />
- [NotMapped]
public ICollection<Company> Companies => Networks;
}
}
diff --git a/Jellyfin.Data/Entities/Libraries/Track.cs b/Jellyfin.Data/Entities/Libraries/Track.cs
index 782bfb5ce..86a3edff8 100644
--- a/Jellyfin.Data/Entities/Libraries/Track.cs
+++ b/Jellyfin.Data/Entities/Libraries/Track.cs
@@ -1,6 +1,5 @@
#pragma warning disable CA2227
-using System;
using System.Collections.Generic;
using Jellyfin.Data.Interfaces;
@@ -14,31 +13,14 @@ namespace Jellyfin.Data.Entities.Libraries
/// <summary>
/// Initializes a new instance of the <see cref="Track"/> class.
/// </summary>
- /// <param name="album">The album.</param>
- public Track(MusicAlbum album)
+ /// <param name="library">The library.</param>
+ public Track(Library library) : base(library)
{
- if (album == null)
- {
- throw new ArgumentNullException(nameof(album));
- }
-
- album.Tracks.Add(this);
-
Releases = new HashSet<Release>();
TrackMetadata = new HashSet<TrackMetadata>();
}
/// <summary>
- /// Initializes a new instance of the <see cref="Track"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected Track()
- {
- }
-
- /// <summary>
/// Gets or sets the track number.
/// </summary>
public int? TrackNumber { get; set; }
diff --git a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
index 321f93bf2..042d2b90d 100644
--- a/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
+++ b/Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace Jellyfin.Data.Entities.Libraries
{
/// <summary>
@@ -12,24 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// </summary>
/// <param name="title">The title or name of the object.</param>
/// <param name="language">ISO-639-3 3-character language codes.</param>
- /// <param name="track">The track.</param>
- public TrackMetadata(string title, string language, Track track) : base(title, language)
- {
- if (track == null)
- {
- throw new ArgumentNullException(nameof(track));
- }
-
- track.TrackMetadata.Add(this);
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="TrackMetadata"/> class.
- /// </summary>
- /// <remarks>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </remarks>
- protected TrackMetadata()
+ public TrackMetadata(string title, string language) : base(title, language)
{
}
}
diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs
index d92e5d9d2..f059dedfa 100644
--- a/Jellyfin.Data/Entities/Permission.cs
+++ b/Jellyfin.Data/Entities/Permission.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
+
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums;
@@ -23,14 +25,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="Permission"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected Permission()
- {
- }
-
- /// <summary>
/// Gets or sets the id of this permission.
/// </summary>
/// <remarks>
diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs
index 4efddf2a4..a8813ab88 100644
--- a/Jellyfin.Data/Entities/Preference.cs
+++ b/Jellyfin.Data/Entities/Preference.cs
@@ -24,14 +24,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Initializes a new instance of the <see cref="Preference"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected Preference()
- {
- }
-
- /// <summary>
/// Gets or sets the id of this preference.
/// </summary>
/// <remarks>
@@ -54,7 +46,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 65535.
/// </remarks>
- [Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Value { get; set; }
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 362f3b4eb..74331726c 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -51,6 +51,7 @@ namespace Jellyfin.Data.Entities
PasswordResetProviderId = passwordResetProviderId;
AccessSchedules = new HashSet<AccessSchedule>();
+ DisplayPreferences = new HashSet<DisplayPreferences>();
ItemDisplayPreferences = new HashSet<ItemDisplayPreferences>();
// Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>();
@@ -72,17 +73,6 @@ namespace Jellyfin.Data.Entities
PlayDefaultAudioTrack = true;
SubtitleMode = SubtitlePlaybackMode.Default;
SyncPlayAccess = SyncPlayUserAccessType.CreateAndJoinGroups;
-
- AddDefaultPermissions();
- AddDefaultPreferences();
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="User"/> class.
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected User()
- {
}
/// <summary>
@@ -100,7 +90,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string Username { get; set; }
@@ -113,7 +102,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string Password { get; set; }
+ public string? Password { get; set; }
/// <summary>
/// Gets or sets the user's easy password, or <c>null</c> if none is set.
@@ -123,7 +112,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
- public string EasyPassword { get; set; }
+ public string? EasyPassword { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user must update their password.
@@ -141,7 +130,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string AudioLanguagePreference { get; set; }
+ public string? AudioLanguagePreference { get; set; }
/// <summary>
/// Gets or sets the authentication provider id.
@@ -149,7 +138,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string AuthenticationProviderId { get; set; }
@@ -160,7 +148,6 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required, Max length = 255.
/// </remarks>
- [Required]
[MaxLength(255)]
[StringLength(255)]
public string PasswordResetProviderId { get; set; }
@@ -217,7 +204,7 @@ namespace Jellyfin.Data.Entities
/// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string SubtitleLanguagePreference { get; set; }
+ public string? SubtitleLanguagePreference { get; set; }
/// <summary>
/// Gets or sets a value indicating whether missing episodes should be displayed.
@@ -312,7 +299,7 @@ namespace Jellyfin.Data.Entities
/// Gets or sets the user's profile image. Can be <c>null</c>.
/// </summary>
// [ForeignKey("UserId")]
- public virtual ImageInfo ProfileImage { get; set; }
+ public virtual ImageInfo? ProfileImage { get; set; }
/// <summary>
/// Gets or sets the user's display preferences.
@@ -320,8 +307,7 @@ namespace Jellyfin.Data.Entities
/// <remarks>
/// Required.
/// </remarks>
- [Required]
- public virtual DisplayPreferences DisplayPreferences { get; set; }
+ public virtual ICollection<DisplayPreferences> DisplayPreferences { get; set; }
/// <summary>
/// Gets or sets the level of sync play permissions this user has.
@@ -494,18 +480,11 @@ namespace Jellyfin.Data.Entities
return Array.IndexOf(GetPreferenceValues<Guid>(PreferenceKind.GroupedFolders), id) != -1;
}
- private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date)
- {
- var localTime = date.ToLocalTime();
- var hour = localTime.TimeOfDay.TotalHours;
-
- return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek)
- && hour >= schedule.StartHour
- && hour <= schedule.EndHour;
- }
-
+ /// <summary>
+ /// Initializes the default permissions for a user. Should only be called on user creation.
+ /// </summary>
// TODO: make these user configurable?
- private void AddDefaultPermissions()
+ public void AddDefaultPermissions()
{
Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
@@ -530,12 +509,25 @@ namespace Jellyfin.Data.Entities
Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false));
}
- private void AddDefaultPreferences()
+ /// <summary>
+ /// Initializes the default preferences. Should only be called on user creation.
+ /// </summary>
+ public void AddDefaultPreferences()
{
foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>())
{
Preferences.Add(new Preference(val, string.Empty));
}
}
+
+ private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date)
+ {
+ var localTime = date.ToLocalTime();
+ var hour = localTime.TimeOfDay.TotalHours;
+
+ return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek)
+ && hour >= schedule.StartHour
+ && hour <= schedule.EndHour;
+ }
}
}
diff --git a/Jellyfin.Data/Interfaces/IHasPermissions.cs b/Jellyfin.Data/Interfaces/IHasPermissions.cs
index 3be72259a..85ee12ad7 100644
--- a/Jellyfin.Data/Interfaces/IHasPermissions.cs
+++ b/Jellyfin.Data/Interfaces/IHasPermissions.cs
@@ -2,7 +2,7 @@
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-namespace Jellyfin.Data
+namespace Jellyfin.Data.Interfaces
{
/// <summary>
/// An abstraction representing an entity that has permissions.
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 42731bb11..3b14d3312 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -5,6 +5,9 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ <Nullable>enable</Nullable>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
@@ -24,10 +27,6 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
@@ -40,8 +39,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.3" />
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 1a8415ae0..3fc44640b 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -11,6 +11,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -30,6 +32,11 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <!-- Needed for https://github.com/dotnet/roslyn-analyzers/issues/4382 which is in the SDK yet -->
+ <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
+ </ItemGroup>
+
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
@@ -37,8 +44,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index eab5777d5..fd7cb5ec5 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -274,8 +274,8 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap)
{
- using var codec = SKCodec.Create(NormalizePath(path));
- if (codec == null)
+ using SKCodec codec = SKCodec.Create(NormalizePath(path), out SKCodecResult res);
+ if (res != SKCodecResult.Success)
{
origin = GetSKEncodedOrigin(orientation);
return null;
@@ -345,11 +345,6 @@ namespace Jellyfin.Drawing.Skia
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
{
- if (origin == SKEncodedOrigin.Default)
- {
- return bitmap;
- }
-
var needsFlip = origin == SKEncodedOrigin.LeftBottom
|| origin == SKEncodedOrigin.LeftTop
|| origin == SKEncodedOrigin.RightBottom
@@ -447,7 +442,7 @@ namespace Jellyfin.Drawing.Skia
}
/// <inheritdoc/>
- public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
+ public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat)
{
if (inputPath.Length == 0)
{
@@ -459,7 +454,7 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
- var skiaOutputFormat = GetImageFormat(selectedOutputFormat);
+ var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);
diff --git a/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj
index f89a18426..63557e91f 100644
--- a/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -5,6 +5,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -18,10 +20,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
index 2f9f44ed6..8b0bd84c6 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Security/AuthenticationSucceededLogger.cs
@@ -29,20 +29,20 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Security
}
/// <inheritdoc />
- public async Task OnEvent(GenericEventArgs<AuthenticationResult> e)
+ public async Task OnEvent(GenericEventArgs<AuthenticationResult> eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("AuthenticationSucceededWithUserName"),
- e.Argument.User.Name),
+ eventArgs.Argument.User.Name),
"AuthenticationSucceeded",
- e.Argument.User.Id)
+ eventArgs.Argument.User.Id)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("LabelIpAddressValue"),
- e.Argument.SessionInfo.RemoteEndPoint),
+ eventArgs.Argument.SessionInfo.RemoteEndPoint),
}).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
index 0340248bb..aa6015caa 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
@@ -86,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
return name;
}
- private static string? GetPlaybackNotificationType(string mediaType)
+ private static string GetPlaybackNotificationType(string mediaType)
{
if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
@@ -98,7 +98,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
return NotificationType.VideoPlayback.ToString();
}
- return null;
+ return "Playback";
}
}
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
index 05201a346..cbc9f3017 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedLogger.cs
@@ -33,10 +33,10 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
}
/// <inheritdoc />
- public async Task OnEvent(TaskCompletionEventArgs e)
+ public async Task OnEvent(TaskCompletionEventArgs eventArgs)
{
- var result = e.Result;
- var task = e.Task;
+ var result = eventArgs.Result;
+ var task = eventArgs.Task;
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
&& !activityTask.IsLogged)
@@ -54,14 +54,14 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
{
var vals = new List<string>();
- if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
+ if (!string.IsNullOrEmpty(eventArgs.Result.ErrorMessage))
{
- vals.Add(e.Result.ErrorMessage);
+ vals.Add(eventArgs.Result.ErrorMessage);
}
- if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
+ if (!string.IsNullOrEmpty(eventArgs.Result.LongErrorMessage))
{
- vals.Add(e.Result.LongErrorMessage);
+ vals.Add(eventArgs.Result.LongErrorMessage);
}
await _activityManager.CreateAsync(new ActivityLog(
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
index 91a30069e..eb7572ac6 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledLogger.cs
@@ -30,13 +30,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
}
/// <inheritdoc />
- public async Task OnEvent(PluginUninstalledEventArgs e)
+ public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
{
await _activityManager.CreateAsync(new ActivityLog(
string.Format(
CultureInfo.InvariantCulture,
_localizationManager.GetLocalizedString("PluginUninstalledWithName"),
- e.Argument.Name),
+ eventArgs.Argument.Name),
NotificationType.PluginUninstalled.ToString(),
Guid.Empty))
.ConfigureAwait(false);
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
index a14911b94..9beb6f2f2 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs
@@ -30,12 +30,12 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
}
/// <inheritdoc />
- public async Task OnEvent(UserUpdatedEventArgs e)
+ public async Task OnEvent(UserUpdatedEventArgs eventArgs)
{
await _sessionManager.SendMessageToUserSessions(
- new List<Guid> { e.Argument.Id },
+ new List<Guid> { eventArgs.Argument.Id },
SessionMessageType.UserUpdated,
- _userManager.GetUserDto(e.Argument),
+ _userManager.GetUserDto(eventArgs.Argument),
CancellationToken.None).ConfigureAwait(false);
}
}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 19c7ac567..96a4fa2fb 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -6,6 +6,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -25,6 +27,8 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.3" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
index 9cc1c3e5e..c99c5e4ef 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -66,7 +66,7 @@ namespace Jellyfin.Server.Implementations.Users
else if (string.Equals(
spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal),
pin.Replace("-", string.Empty, StringComparison.Ordinal),
- StringComparison.InvariantCultureIgnoreCase))
+ StringComparison.OrdinalIgnoreCase))
{
var resetUser = userManager.GetUserByName(spr.UserName)
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index c8a589cab..a3e9516b9 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -1,4 +1,5 @@
#pragma warning disable CA1307
+#pragma warning disable CA1309
using System;
using System.Collections.Generic;
@@ -35,7 +36,7 @@ namespace Jellyfin.Server.Implementations.Users
if (prefs == null)
{
- prefs = new DisplayPreferences(userId, itemId, client);
+ prefs = new DisplayPreferences(userId, itemId, client);
_dbContext.DisplayPreferences.Add(prefs);
}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 76d1389ca..50d7612f2 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -184,12 +184,15 @@ namespace Jellyfin.Server.Implementations.Users
var user = new User(
name,
- _defaultAuthenticationProvider.GetType().FullName,
- _defaultPasswordResetProvider.GetType().FullName)
+ _defaultAuthenticationProvider.GetType().FullName!,
+ _defaultPasswordResetProvider.GetType().FullName!)
{
InternalId = max + 1
};
+ user.AddDefaultPermissions();
+ user.AddDefaultPreferences();
+
_users.Add(user.Id, user);
return user;
@@ -444,7 +447,7 @@ namespace Jellyfin.Server.Implementations.Users
{
var providerId = authenticationProvider.GetType().FullName;
- if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
+ if (providerId != null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
{
user.AuthenticationProviderId = providerId;
await UpdateUserAsync(user).ConfigureAwait(false);
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 1828f1a7e..d26ac251c 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -225,7 +225,7 @@ namespace Jellyfin.Server.Extensions
.AddJsonOptions(options =>
{
// Update all properties that are set in JsonDefaults
- var jsonOptions = JsonDefaults.GetPascalCaseOptions();
+ var jsonOptions = JsonDefaults.PascalCaseOptions;
// From JsonDefaults
options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
@@ -260,15 +260,16 @@ namespace Jellyfin.Server.Extensions
{
return serviceCollection.AddSwaggerGen(c =>
{
+ var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString() ?? "0.0.0.1";
c.SwaggerDoc("api-docs", new OpenApiInfo
{
Title = "Jellyfin API",
- Version = "v1",
+ Version = version,
Extensions = new Dictionary<string, IOpenApiExtension>
{
{
"x-jellyfin-version",
- new OpenApiString(typeof(ApplicationHost).Assembly.GetName().Version?.ToString())
+ new OpenApiString(version)
}
}
});
diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs
index 248802857..38afb201d 100644
--- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs
+++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs
@@ -25,6 +25,8 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository);
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
+
+ context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
}
}
}
diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
index 8043989b1..c349e3dca 100644
--- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
@@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
/// </summary>
- public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
+ public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCaseOptions)
{
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
index d0110b125..0480f5e0e 100644
--- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
/// </summary>
- public PascalCaseJsonProfileFormatter() : base(JsonDefaults.GetPascalCaseOptions())
+ public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCaseOptions)
{
SupportedMediaTypes.Clear();
// Add application/json for default formatter
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 6bfb5b878..09799307b 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -13,7 +13,9 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ <!-- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> -->
</PropertyGroup>
<ItemGroup>
@@ -31,10 +33,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs
index 816dd9ee7..c9710f1fd 100644
--- a/Jellyfin.Server/Migrations/MigrationOptions.cs
+++ b/Jellyfin.Server/Migrations/MigrationOptions.cs
@@ -16,9 +16,12 @@ namespace Jellyfin.Server.Migrations
Applied = new List<(Guid Id, string Name)>();
}
+// .Net xml serializer can't handle interfaces
+#pragma warning disable CA1002 // Do not expose generic lists
/// <summary>
/// Gets the list of applied migration routine names.
/// </summary>
public List<(Guid Id, string Name)> Applied { get; }
+#pragma warning restore CA1002
}
}
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 33f039c39..a15a38177 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -1,7 +1,5 @@
using System;
-using System.Globalization;
using System.IO;
-using System.Linq;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Serialization;
using Jellyfin.Data.Entities;
@@ -76,7 +74,7 @@ namespace Jellyfin.Server.Migrations.Routines
foreach (var entry in queryResult)
{
- UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.GetOptions());
+ UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob(), JsonDefaults.Options);
if (mockup == null)
{
continue;
@@ -104,7 +102,7 @@ namespace Jellyfin.Server.Migrations.Routines
_ => policy.LoginAttemptsBeforeLockout
};
- var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId)
+ var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
{
Id = entry[1].ReadGuidFromBlob(),
InternalId = entry[0].ToInt64(),
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 6ae0542c0..4f203b7a9 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -222,7 +222,7 @@ namespace Jellyfin.Server
}
finally
{
- appHost?.Dispose();
+ appHost.Dispose();
}
if (_restartOnShutdown)
@@ -623,7 +623,7 @@ namespace Jellyfin.Server
string commandLineArgsString;
if (options.RestartArgs != null)
{
- commandLineArgsString = options.RestartArgs ?? string.Empty;
+ commandLineArgsString = options.RestartArgs;
}
else
{
diff --git a/Jellyfin.sln b/Jellyfin.sln
index 9d5cd98e2..8626a4b1b 100644
--- a/Jellyfin.sln
+++ b/Jellyfin.sln
@@ -77,9 +77,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -219,6 +219,10 @@ Global
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -235,6 +239,7 @@ Global
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
diff --git a/MediaBrowser.Common/Crc32.cs b/MediaBrowser.Common/Crc32.cs
new file mode 100644
index 000000000..599eb4c99
--- /dev/null
+++ b/MediaBrowser.Common/Crc32.cs
@@ -0,0 +1,89 @@
+#pragma warning disable CS1591
+
+using System;
+
+namespace MediaBrowser.Common
+{
+ public static class Crc32
+ {
+ private static readonly uint[] _crcTable =
+ {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+ };
+
+ public static uint Compute(ReadOnlySpan<byte> bytes)
+ {
+ var crc = 0xffffffff;
+ var len = bytes.Length;
+ for (var i = 0; i < len; i++)
+ {
+ crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff];
+ }
+
+ return ~crc;
+ }
+ }
+}
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index ddcf2ac17..c3e4ed6db 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -10,7 +10,7 @@ namespace MediaBrowser.Common
/// </summary>
/// <param name="type">Type to create.</param>
/// <returns>New instance of type <param>type</param>.</returns>
- public delegate object CreationDelegate(Type type);
+ public delegate object CreationDelegateFactory(Type type);
/// <summary>
/// An interface to be implemented by the applications hosting a kernel.
@@ -112,7 +112,7 @@ namespace MediaBrowser.Common
/// <param name="defaultFunc">Delegate function that gets called to create the object.</param>
/// <param name="manageLifetime">If set to <c>true</c> [manage lifetime].</param>
/// <returns><see cref="IReadOnlyCollection{T}" />.</returns>
- IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true);
+ IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true);
/// <summary>
/// Gets the export types.
diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs
new file mode 100644
index 000000000..669b3cd07
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Buffers;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ /// <summary>
+ /// Converter to allow the serializer to read strings.
+ /// </summary>
+ public class JsonStringConverter : JsonConverter<string>
+ {
+ /// <inheritdoc />
+ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.TokenType switch
+ {
+ JsonTokenType.Null => null,
+ JsonTokenType.String => reader.GetString(),
+ _ => GetRawValue(reader)
+ };
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value);
+ }
+
+ private static string GetRawValue(Utf8JsonReader reader)
+ {
+ var utf8Bytes = reader.HasValueSequence
+ ? reader.ValueSequence.ToArray()
+ : reader.ValueSpan;
+ return Encoding.UTF8.GetString(utf8Bytes);
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index 2ef24a884..405d6125f 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -39,7 +39,8 @@ namespace MediaBrowser.Common.Json
new JsonStringEnumConverter(),
new JsonNullableStructConverterFactory(),
new JsonBoolNumberConverter(),
- new JsonDateTimeConverter()
+ new JsonDateTimeConverter(),
+ new JsonStringConverter()
}
};
@@ -61,7 +62,7 @@ namespace MediaBrowser.Common.Json
/// If the defaults must be modified the author must use the copy constructor.
/// </remarks>
/// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns>
- public static JsonSerializerOptions GetOptions()
+ public static JsonSerializerOptions Options
=> _jsonSerializerOptions;
/// <summary>
@@ -72,7 +73,7 @@ namespace MediaBrowser.Common.Json
/// If the defaults must be modified the author must use the copy constructor.
/// </remarks>
/// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns>
- public static JsonSerializerOptions GetCamelCaseOptions()
+ public static JsonSerializerOptions CamelCaseOptions
=> _camelCaseJsonSerializerOptions;
/// <summary>
@@ -83,7 +84,7 @@ namespace MediaBrowser.Common.Json
/// If the defaults must be modified the author must use the copy constructor.
/// </remarks>
/// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns>
- public static JsonSerializerOptions GetPascalCaseOptions()
+ public static JsonSerializerOptions PascalCaseOptions
=> _pascalCaseJsonSerializerOptions;
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 34e1934e2..0d9f78704 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -33,6 +33,8 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
@@ -51,10 +53,6 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Jellyfin.Common.Tests</_Parameter1>
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
index 4a7c70190..d67b6b8e1 100644
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ b/MediaBrowser.Common/Net/IPHost.cs
@@ -406,7 +406,7 @@ namespace MediaBrowser.Common.Net
}
// If we haven't resolved before, or our timer has run out...
- if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved?.AddMinutes(Timeout)))
+ if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout)))
{
_lastResolved = DateTime.UtcNow;
ResolveHostInternal().GetAwaiter().GetResult();
diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs
index 5fab52eac..59e37a5c6 100644
--- a/MediaBrowser.Common/Net/IPNetAddress.cs
+++ b/MediaBrowser.Common/Net/IPNetAddress.cs
@@ -216,11 +216,11 @@ namespace MediaBrowser.Common.Net
}
/// <inheritdoc/>
- public override bool Equals(IPAddress address)
+ public override bool Equals(IPAddress ip)
{
- if (address != null && !address.Equals(IPAddress.None) && !Address.Equals(IPAddress.None))
+ if (ip != null && !ip.Equals(IPAddress.None) && !Address.Equals(IPAddress.None))
{
- return address.Equals(Address);
+ return ip.Equals(Address);
}
return false;
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index 7b162c0e1..ad5a7338d 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -50,7 +50,7 @@ namespace MediaBrowser.Common.Plugins
/// Gets a value indicating whether the plugin can be uninstalled.
/// </summary>
public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath)
- .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.InvariantCulture);
+ .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.Ordinal);
/// <summary>
/// Gets the plugin info.
diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
index d5c780851..99c226f50 100644
--- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs
+++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
@@ -39,29 +39,27 @@ namespace MediaBrowser.Common.Plugins
{
ApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
- if (this is IPluginAssembly assemblyPlugin)
- {
- var assembly = GetType().Assembly;
- var assemblyName = assembly.GetName();
- var assemblyFilePath = assembly.Location;
- var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
- if (!Directory.Exists(dataFolderPath) && Version != null)
- {
- // Try again with the version number appended to the folder name.
- dataFolderPath = dataFolderPath + "_" + Version.ToString();
- }
+ var assembly = GetType().Assembly;
+ var assemblyName = assembly.GetName();
+ var assemblyFilePath = assembly.Location;
- assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
+ var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
+ if (!Directory.Exists(dataFolderPath) && Version != null)
+ {
+ // Try again with the version number appended to the folder name.
+ dataFolderPath = dataFolderPath + "_" + Version.ToString();
+ }
- var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true);
- if (idAttributes.Length > 0)
- {
- var attribute = (GuidAttribute)idAttributes[0];
- var assemblyId = new Guid(attribute.Value);
+ SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
- assemblyPlugin.SetId(assemblyId);
- }
+ var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true);
+ if (idAttributes.Length > 0)
+ {
+ var attribute = (GuidAttribute)idAttributes[0];
+ var assemblyId = new Guid(attribute.Value);
+
+ SetId(assemblyId);
}
}
diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs
index 23b6cfa81..12a1ad1ec 100644
--- a/MediaBrowser.Common/Plugins/LocalPlugin.cs
+++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Common.Plugins
public LocalPlugin(string path, bool isSupported, PluginManifest manifest)
{
Path = path;
- DllFiles = new List<string>();
+ DllFiles = Array.Empty<string>();
_supported = isSupported;
Manifest = manifest;
}
@@ -59,9 +59,9 @@ namespace MediaBrowser.Common.Plugins
public string Path { get; }
/// <summary>
- /// Gets the list of dll files for this plugin.
+ /// Gets or sets the list of dll files for this plugin.
/// </summary>
- public List<string> DllFiles { get; }
+ public IReadOnlyList<string> DllFiles { get; set; }
/// <summary>
/// Gets or sets the instance of this plugin.
diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs
index d5bcd5be9..fe7cb1078 100644
--- a/MediaBrowser.Common/Progress/ActionableProgress.cs
+++ b/MediaBrowser.Common/Progress/ActionableProgress.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable CA1003
using System;
diff --git a/MediaBrowser.Common/Progress/SimpleProgress.cs b/MediaBrowser.Common/Progress/SimpleProgress.cs
index d75675bf1..988d8ad34 100644
--- a/MediaBrowser.Common/Progress/SimpleProgress.cs
+++ b/MediaBrowser.Common/Progress/SimpleProgress.cs
@@ -1,4 +1,5 @@
#pragma warning disable CS1591
+#pragma warning disable CA1003
using System;
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 935a79031..142cebd0c 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Drawing
string GetImageCacheTag(BaseItem item, ChapterInfo info);
- string GetImageCacheTag(User user);
+ string? GetImageCacheTag(User user);
/// <summary>
/// Processes the image.
diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs
index f6d3cd6cc..20fad4cb0 100644
--- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs
@@ -1,6 +1,8 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -23,15 +25,7 @@ namespace MediaBrowser.Controller.Entities.Audio
public static IEnumerable<string> GetAllArtists<T>(this T item)
where T : IHasArtist, IHasAlbumArtist
{
- foreach (var i in item.AlbumArtists)
- {
- yield return i;
- }
-
- foreach (var i in item.Artists)
- {
- yield return i;
- }
+ return item.AlbumArtists.Concat(item.Artists).DistinctNames();
}
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 65fd1654c..76b6d39a9 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class CollectionFolder : Folder, ICollectionFolder
{
- private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public static IXmlSerializer XmlSerializer { get; set; }
public static IServerApplicationHost ApplicationHost { get; set; }
diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs
index 1c90bb4e0..6e79dc8dd 100644
--- a/MediaBrowser.Controller/Library/NameExtensions.cs
+++ b/MediaBrowser.Controller/Library/NameExtensions.cs
@@ -10,6 +10,10 @@ namespace MediaBrowser.Controller.Library
{
public static class NameExtensions
{
+ public static IEnumerable<string> DistinctNames(this IEnumerable<string> names)
+ => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase)
+ .Select(x => x.First());
+
private static string RemoveDiacritics(string? name)
{
if (name == null)
@@ -19,9 +23,5 @@ namespace MediaBrowser.Controller.Library
return name.RemoveDiacritics();
}
-
- public static IEnumerable<string> DistinctNames(this IEnumerable<string> names)
- => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase)
- .Select(x => x.First());
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index d487a324f..8c68b47dd 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -34,6 +34,8 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
+ <AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
@@ -52,8 +54,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index e5877a484..09080e7b2 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -541,6 +541,8 @@ namespace MediaBrowser.Controller.MediaEncoding
.Append(encodingOptions.VaapiDevice)
.Append(' ');
}
+
+ arg.Append("-autorotate 0 ");
}
if (state.IsVideoRequest
@@ -585,6 +587,8 @@ namespace MediaBrowser.Controller.MediaEncoding
.Append("-init_hw_device qsv@va ")
.Append("-hwaccel_output_format vaapi ");
}
+
+ arg.Append("-autorotate 0 ");
}
}
@@ -592,7 +596,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& isNvdecDecoder)
{
- arg.Append("-hwaccel_output_format cuda ");
+ arg.Append("-hwaccel_output_format cuda -autorotate 0 ");
}
if (state.IsVideoRequest
diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
index c129eddb3..f78bd6ddf 100644
--- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
@@ -10,6 +10,6 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
public interface ILocalImageProvider : IImageProvider
{
- List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
+ IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
}
}
diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
index 67acdd9a3..25128a5cd 100644
--- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
- protected virtual T Resolve(ItemResolveArgs args)
+ public virtual T Resolve(ItemResolveArgs args)
{
return null;
}
diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs
index 556bb6a0e..b6189bcdd 100644
--- a/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderLocalImageProvider.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.LocalMetadata.Images
}
/// <inheritdoc />
- public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var collectionFolder = (CollectionFolder)item;
diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
index 393ad2efb..2d3b2d889 100644
--- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Images
}
/// <inheritdoc />
- public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var parentPath = Path.GetDirectoryName(item.Path);
diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
index 509b5d700..10d691b3e 100644
--- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -69,13 +70,13 @@ namespace MediaBrowser.LocalMetadata.Images
}
/// <inheritdoc />
- public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var path = item.GetInternalMetadataPath();
if (!Directory.Exists(path))
{
- return new List<LocalImageInfo>();
+ return Enumerable.Empty<LocalImageInfo>();
}
try
@@ -85,7 +86,7 @@ namespace MediaBrowser.LocalMetadata.Images
catch (IOException ex)
{
_logger.LogError(ex, "Error while getting images for {Library}", item.Name);
- return new List<LocalImageInfo>();
+ return Enumerable.Empty<LocalImageInfo>();
}
}
}
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
index 84c3ed8b0..7ad8c24e8 100644
--- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
@@ -108,7 +108,7 @@ namespace MediaBrowser.LocalMetadata.Images
{
if (!item.IsFileProtocol)
{
- return new List<FileSystemMetadata>();
+ return Enumerable.Empty<FileSystemMetadata>();
}
var path = item.ContainingFolderPath;
@@ -116,7 +116,7 @@ namespace MediaBrowser.LocalMetadata.Images
// Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dirs...
if (!Directory.Exists(path))
{
- return Array.Empty<FileSystemMetadata>();
+ return Enumerable.Empty<FileSystemMetadata>();
}
if (includeDirectories)
@@ -133,7 +133,7 @@ namespace MediaBrowser.LocalMetadata.Images
}
/// <inheritdoc />
- public List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
{
var files = GetFiles(item, true, directoryService).ToList();
@@ -151,7 +151,7 @@ namespace MediaBrowser.LocalMetadata.Images
/// <param name="path">The images path.</param>
/// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
/// <returns>The local image info.</returns>
- public List<LocalImageInfo> GetImages(BaseItem item, string path, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, string path, IDirectoryService directoryService)
{
return GetImages(item, new[] { path }, directoryService);
}
@@ -163,7 +163,7 @@ namespace MediaBrowser.LocalMetadata.Images
/// <param name="paths">The image paths.</param>
/// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
/// <returns>The local image info.</returns>
- public List<LocalImageInfo> GetImages(BaseItem item, IEnumerable<string> paths, IDirectoryService directoryService)
+ public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IEnumerable<string> paths, IDirectoryService directoryService)
{
IEnumerable<FileSystemMetadata> files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImageExtensions, true, false));
@@ -181,9 +181,7 @@ namespace MediaBrowser.LocalMetadata.Images
{
if (supportParentSeriesFiles)
{
- var season = item as Season;
-
- if (season != null)
+ if (item is Season season)
{
PopulateSeasonImagesFromSeriesFolder(season, images, directoryService);
}
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 1792f1d9b..eb2077a5f 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -16,6 +16,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -29,8 +31,4 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index b0afb834b..5f620634f 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -1275,8 +1275,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
// Only split by comma if there is no pipe in the string
// We have to be careful to not split names like Matthew, Jr.
- var separator = value.IndexOf('|', StringComparison.Ordinal) == -1
- && value.IndexOf(';', StringComparison.Ordinal) == -1 ? new[] { ',' } : new[] { '|', ';' };
+ var separator = !value.Contains('|', StringComparison.Ordinal)
+ && !value.Contains(';', StringComparison.Ordinal) ? new[] { ',' } : new[] { '|', ';' };
value = value.Trim().Trim(separator);
diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
index ff846830b..7df800971 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
/// <inheritdoc />
- protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<BoxSet> item)
+ protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<BoxSet> itemResult)
{
switch (reader.Name)
{
@@ -33,7 +33,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
using (var subReader = reader.ReadSubtree())
{
- FetchFromCollectionItemsNode(subReader, item);
+ FetchFromCollectionItemsNode(subReader, itemResult);
}
}
else
@@ -44,7 +44,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
break;
default:
- base.FetchDataFromXmlNode(reader, item);
+ base.FetchDataFromXmlNode(reader, itemResult);
break;
}
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
index 78c0fa8ad..b84307cb2 100644
--- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
@@ -23,9 +23,9 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
/// <inheritdoc />
- protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Playlist> result)
+ protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Playlist> itemResult)
{
- var item = result.Item;
+ var item = itemResult.Item;
switch (reader.Name)
{
@@ -53,7 +53,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
break;
default:
- base.FetchDataFromXmlNode(reader, result);
+ base.FetchDataFromXmlNode(reader, itemResult);
break;
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index e59fcb965..dfbce5f49 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -37,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Savers
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{BaseXmlSaver}"/> interface.</param>
- public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BaseXmlSaver> logger)
+ protected BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BaseXmlSaver> logger)
{
FileSystem = fileSystem;
ConfigurationManager = configurationManager;
@@ -421,20 +420,17 @@ namespace MediaBrowser.LocalMetadata.Savers
writer.WriteEndElement();
}
- var boxset = item as BoxSet;
- if (boxset != null)
+ if (item is BoxSet boxset)
{
AddLinkedChildren(boxset, writer, "CollectionItems", "CollectionItem");
}
- var playlist = item as Playlist;
- if (playlist != null && !Playlist.IsPlaylistFile(playlist.Path))
+ if (item is Playlist playlist && !Playlist.IsPlaylistFile(playlist.Path))
{
AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem");
}
- var hasShares = item as IHasShares;
- if (hasShares != null)
+ if (item is IHasShares hasShares)
{
AddShares(hasShares, writer);
}
@@ -542,10 +538,5 @@ namespace MediaBrowser.LocalMetadata.Savers
writer.WriteEndElement();
}
-
- private bool IsPersonType(PersonInfo person, string type)
- {
- return string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
- }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 8a25a64c7..47cf020b4 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_localization = localization;
_encodingHelperFactory = encodingHelperFactory;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
- _jsonSerializerOptions = JsonDefaults.GetOptions();
+ _jsonSerializerOptions = JsonDefaults.Options;
}
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index cd9e47743..2adb11908 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index 0a79f5bb5..00feeec1f 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public static AudioDbAlbumProvider Current;
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index 36700d191..b8095ff04 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index 4b1d91567..59ecbc017 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 97fcbfb6f..428b0ded1 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
_configurationManager = configurationManager;
_appHost = appHost;
- _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index e3301ff32..d35805a84 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
_configurationManager = configurationManager;
_appHost = appHost;
- _jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index fcd8e614c..ca1af6c49 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -19,11 +20,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ private readonly ILibraryManager _libraryManager;
- public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
+ public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
+ _libraryManager = libraryManager;
}
public string Name => TmdbUtils.ProviderName;
@@ -83,7 +86,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
// We don't already have an Id, need to fetch it
if (tmdbId <= 0)
{
- var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
+ // ParseName is required here.
+ // Caller provides the filename with extension stripped and NOT the parsed filename
+ var parsedName = _libraryManager.ParseName(id.Name);
+ var cleanedName = TmdbUtils.CleanName(parsedName.Name);
+ var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, cancellationToken).ConfigureAwait(false);
if (searchResults != null && searchResults.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 2cd1ee717..4963777bc 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -140,7 +140,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
// ParseName is required here.
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
- var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var cleanedName = TmdbUtils.CleanName(parsedName.Name);
+ var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 74c2acf47..496e1ae25 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -189,7 +189,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// ParseName is required here.
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
- var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false);
+ var cleanedName = TmdbUtils.CleanName(parsedName.Name);
+ var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index 0e8a5baab..15a44c7ed 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using MediaBrowser.Model.Entities;
using TMDbLib.Objects.General;
@@ -12,6 +13,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
public static class TmdbUtils
{
+ private static readonly Regex _nonWords = new (@"[\W_]+", RegexOptions.Compiled);
+
/// <summary>
/// URL of the TMDB instance to use.
/// </summary>
@@ -43,6 +46,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
};
/// <summary>
+ /// Cleans the name according to TMDb requirements.
+ /// </summary>
+ /// <param name="name">The name of the entity.</param>
+ /// <returns>The cleaned name.</returns>
+ public static string CleanName(string name)
+ {
+ // TMDb expects a space separated list of words make sure that is the case
+ return _nonWords.Replace(name, " ");
+ }
+
+ /// <summary>
/// Maps the TMDB provided roles for crew members to Jellyfin roles.
/// </summary>
/// <param name="crew">Crew member to map against the Jellyfin person types.</param>
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index d4d79d27b..1f3d9acff 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -205,12 +205,30 @@ namespace MediaBrowser.Providers.Subtitles
if (saveInMediaFolder)
{
- savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName));
+ var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName));
+ // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
+ if (mediaFolderPath.StartsWith(video.ContainingFolderPath))
+ {
+ savePaths.Add(mediaFolderPath);
+ }
}
- savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
+ var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
+
+ // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path.");
+ if (internalPath.StartsWith(video.GetInternalMetadataPath()))
+ {
+ savePaths.Add(internalPath);
+ }
- await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
+ if (savePaths.Count > 0)
+ {
+ await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
+ }
+ else
+ {
+ _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid.");
+ }
}
}
diff --git a/README.md b/README.md
index cba88c8d2..6859a8a76 100644
--- a/README.md
+++ b/README.md
@@ -29,9 +29,6 @@
<a href="https://features.jellyfin.org">
<img alt="Submit Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/>
</a>
-<a href="https://forum.jellyfin.org">
-<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/>
-</a>
<a href="https://matrix.to/#/+jellyfin:matrix.org">
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
</a>
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
index ee20cc573..de03aa5f5 100644
--- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -128,6 +128,8 @@ namespace Jellyfin.Api.Tests.Auth
{
var authorizationInfo = _fixture.Create<AuthorizationInfo>();
authorizationInfo.User = _fixture.Create<User>();
+ authorizationInfo.User.AddDefaultPermissions();
+ authorizationInfo.User.AddDefaultPreferences();
authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
authorizationInfo.IsApiKey = false;
diff --git a/tests/Jellyfin.Api.Tests/TestHelpers.cs b/tests/Jellyfin.Api.Tests/TestHelpers.cs
index f27cdf7b6..f9bca4146 100644
--- a/tests/Jellyfin.Api.Tests/TestHelpers.cs
+++ b/tests/Jellyfin.Api.Tests/TestHelpers.cs
@@ -26,8 +26,11 @@ namespace Jellyfin.Api.Tests
{
var user = new User(
"jellyfin",
- typeof(DefaultAuthenticationProvider).FullName,
- typeof(DefaultPasswordResetProvider).FullName);
+ typeof(DefaultAuthenticationProvider).FullName!,
+ typeof(DefaultPasswordResetProvider).FullName!);
+
+ user.AddDefaultPermissions();
+ user.AddDefaultPreferences();
// Set administrator flag.
user.SetPermission(PermissionKind.IsAdministrator, role.Equals(UserRoles.Administrator, StringComparison.OrdinalIgnoreCase));
diff --git a/tests/Jellyfin.Common.Tests/Crc32Tests.cs b/tests/Jellyfin.Common.Tests/Crc32Tests.cs
new file mode 100644
index 000000000..e95a2867f
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Crc32Tests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text;
+using MediaBrowser.Common;
+using Xunit;
+
+namespace Jellyfin.Common.Tests
+{
+ public static class Crc32Tests
+ {
+ [Fact]
+ public static void Compute_Empty_Zero()
+ {
+ Assert.Equal<uint>(0, Crc32.Compute(Array.Empty<byte>()));
+ }
+
+ [Theory]
+ [InlineData(0x414fa339, "The quick brown fox jumps over the lazy dog")]
+ public static void Compute_Valid_Success(uint expected, string data)
+ {
+ Assert.Equal(expected, Crc32.Compute(Encoding.UTF8.GetBytes(data)));
+ }
+
+ [Theory]
+ [InlineData(0x414fa339, "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67")]
+ [InlineData(0x190a55ad, "0000000000000000000000000000000000000000000000000000000000000000")]
+ [InlineData(0xff6cab0b, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")]
+ [InlineData(0x91267e8a, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")]
+ public static void Compute_ValidHex_Success(uint expected, string data)
+ {
+ Assert.Equal(expected, Crc32.Compute(Convert.FromHexString(data)));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs
new file mode 100644
index 000000000..fd77694b3
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs
@@ -0,0 +1,39 @@
+using System.Text.Json;
+using MediaBrowser.Common.Json.Converters;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public class JsonStringConverterTests
+ {
+ private readonly JsonSerializerOptions _jsonSerializerOptions
+ = new ()
+ {
+ Converters =
+ {
+ new JsonStringConverter()
+ }
+ };
+
+ [Theory]
+ [InlineData("\"test\"", "test")]
+ [InlineData("123", "123")]
+ [InlineData("123.45", "123.45")]
+ [InlineData("true", "true")]
+ [InlineData("false", "false")]
+ public void Deserialize_String_Valid_Success(string input, string output)
+ {
+ var deserialized = JsonSerializer.Deserialize<string>(input, _jsonSerializerOptions);
+ Assert.Equal(deserialized, output);
+ }
+
+ [Fact]
+ public void Deserialize_Int32asInt32_Valid_Success()
+ {
+ const string? input = "123";
+ const int output = 123;
+ var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions);
+ Assert.Equal(deserialized, output);
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
index c39ef0ce9..415682e85 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -16,7 +16,7 @@ namespace Jellyfin.MediaEncoding.Tests
var path = Path.Join("Test Data", fileName);
using (var stream = File.OpenRead(path))
{
- await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.GetOptions()).ConfigureAwait(false);
+ await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.Options).ConfigureAwait(false);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
index 4b363843a..a720bdade 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -7,18 +7,13 @@ namespace Jellyfin.Naming.Tests.Video
{
public sealed class CleanStringTests
{
- private readonly NamingOptions _namingOptions = new NamingOptions();
+ private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions());
[Theory]
[InlineData("Super movie 480p.mp4", "Super movie")]
[InlineData("Super movie 480p 2001.mp4", "Super movie")]
[InlineData("Super movie [480p].mp4", "Super movie")]
[InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")]
- [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")]
- [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")]
- [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")]
- [InlineData(@"American Psycho.mkv", "American Psycho.mkv")]
- [InlineData(@"[rec].mkv", "[rec].mkv")]
[InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
@@ -28,19 +23,26 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
[InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
- [InlineData(null, null)]
// FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
- public void CleanStringTest(string input, string expectedName)
+ public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName)
{
- if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName))
- {
- // TODO: compare spans when XUnit supports it
- Assert.Equal(expectedName, newName.ToString());
- }
- else
- {
- Assert.Equal(expectedName, input);
- }
+ Assert.True(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName));
+ // TODO: compare spans when XUnit supports it
+ Assert.Equal(expectedName, newName.ToString());
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("Super movie(2009).mp4")]
+ [InlineData("[rec].mkv")]
+ [InlineData("American.Psycho.mkv")]
+ [InlineData("American Psycho.mkv")]
+ [InlineData("Run lola run (lola rennt) (2009).mp4")]
+ public void CleanStringTest_DoesntNeedCleaning_False(string? input)
+ {
+ Assert.False(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName));
+ Assert.True(newName.IsEmpty);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index bc5e6fa63..6e803593e 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -295,12 +295,9 @@ namespace Jellyfin.Naming.Tests.Video
FullName = i
}).ToList()).ToList();
- Assert.Single(result);
+ Assert.Equal(7, result.Count);
Assert.Empty(result[0].Extras);
- Assert.Equal(6, result[0].AlternateVersions.Count);
- Assert.False(result[0].AlternateVersions[2].Is3D);
- Assert.True(result[0].AlternateVersions[3].Is3D);
- Assert.True(result[0].AlternateVersions[4].Is3D);
+ Assert.Empty(result[0].AlternateVersions);
}
[Fact]
@@ -369,6 +366,44 @@ namespace Jellyfin.Naming.Tests.Video
}
[Fact]
+ public void Resolve_GivenFolderNameWithBracketsAndHyphens_GroupsBasedOnFolderName()
+ {
+ var files = new[]
+ {
+ @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 1.mkv",
+ @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv"
+ };
+
+ var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+ }).ToList()).ToList();
+
+ Assert.Single(result);
+ Assert.Empty(result[0].Extras);
+ Assert.Single(result[0].AlternateVersions);
+ }
+
+ [Fact]
+ public void Resolve_GivenUnclosedBrackets_DoesNotGroup()
+ {
+ var files = new[]
+ {
+ @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 1].mkv",
+ @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv"
+ };
+
+ var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
+ {
+ IsDirectory = false,
+ FullName = i
+ }).ToList()).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ [Fact]
public void TestEmptyList()
{
var result = _videoListResolver.Resolve(new List<FileSystemMetadata>()).ToList();
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
new file mode 100644
index 000000000..876519215
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -0,0 +1,65 @@
+using System;
+using Emby.Server.Implementations.Library.Resolvers.TV;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library
+{
+ public class EpisodeResolverTest
+ {
+ [Fact]
+ public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode()
+ {
+ var season = new Season { Name = "Season 1" };
+ var parent = new Folder { Name = "extras" };
+ var libraryManagerMock = new Mock<ILibraryManager>();
+ libraryManagerMock.Setup(x => x.GetItemById(It.IsAny<Guid>())).Returns(season);
+
+ var episodeResolver = new EpisodeResolver(libraryManagerMock.Object);
+ var itemResolveArgs = new ItemResolveArgs(
+ Mock.Of<IServerApplicationPaths>(),
+ Mock.Of<IDirectoryService>())
+ {
+ Parent = parent,
+ CollectionType = CollectionType.TvShows,
+ Path = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv"
+ };
+
+ Assert.Null(episodeResolver.Resolve(itemResolveArgs));
+ }
+
+ [Fact]
+ public void Resolve_GivenVideoInExtrasSeriesFolder_ResolvesToEpisode()
+ {
+ var series = new Series { Name = "Extras" };
+
+ // Have to create a mock because of moq proxies not being castable to a concrete implementation
+ // https://github.com/jellyfin/jellyfin/blob/ab0cff8556403e123642dc9717ba778329554634/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs#L48
+ var episodeResolver = new EpisodeResolverMock(Mock.Of<ILibraryManager>());
+ var itemResolveArgs = new ItemResolveArgs(
+ Mock.Of<IServerApplicationPaths>(),
+ Mock.Of<IDirectoryService>())
+ {
+ Parent = series,
+ CollectionType = CollectionType.TvShows,
+ Path = "Extras/Extras S01E01.mkv"
+ };
+ Assert.NotNull(episodeResolver.Resolve(itemResolveArgs));
+ }
+
+ private class EpisodeResolverMock : EpisodeResolver
+ {
+ public EpisodeResolverMock(ILibraryManager libraryManager) : base(libraryManager)
+ {
+ }
+
+ protected override TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) => new ();
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index a6fe90566..e5508243f 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -40,12 +40,16 @@ namespace Jellyfin.Server.Implementations.Tests.Library
}
[Theory]
+ [InlineData(null, null, null)]
+ [InlineData(null, "/my/path", "/another/path")]
+ [InlineData("/my/path", null, "/another/path")]
+ [InlineData("/my/path", "/another/path", null)]
[InlineData("", "", "")]
[InlineData("/my/path", "", "")]
[InlineData("", "/another/path", "")]
[InlineData("", "", "/new/subpath")]
[InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff")]
- public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string path, string subPath, string newSubPath)
+ public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string? path, string? subPath, string? newSubPath)
{
Assert.False(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result));
Assert.Null(result);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
new file mode 100644
index 000000000..7e04a1ec1
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunManagerTests.cs
@@ -0,0 +1,58 @@
+using System;
+using Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.LiveTv
+{
+ public class HdHomerunManagerTests
+ {
+ [Fact]
+ public void WriteNullTerminatedString_Empty_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 1, 0
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteNullTerminatedString(buffer, string.Empty);
+
+ Assert.Equal(expected.Length, len);
+ Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteNullTerminatedString_Valid_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 10, (byte)'T', (byte)'h', (byte)'e', (byte)' ', (byte)'q', (byte)'u', (byte)'i', (byte)'c', (byte)'k', 0
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteNullTerminatedString(buffer, "The quick");
+
+ Assert.Equal(expected.Length, len);
+ Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ }
+
+ [Fact]
+ public void WriteGetMessage_Valid_Success()
+ {
+ ReadOnlySpan<byte> expected = stackalloc byte[]
+ {
+ 0, 4,
+ 0, 12,
+ 3,
+ 10, (byte)'/', (byte)'t', (byte)'u', (byte)'n', (byte)'e', (byte)'r', (byte)'0', (byte)'/', (byte)'N', 0,
+ 0xc0, 0xc9, 0x87, 0x33
+ };
+
+ Span<byte> buffer = stackalloc byte[128];
+ int len = HdHomerunManager.WriteGetMessage(buffer, 0, "N");
+
+ Assert.Equal(expected.Length, len);
+ Assert.True(expected.SequenceEqual(buffer.Slice(0, len)));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index 86d6326d8..f5411dcb8 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
public sealed class DashboardControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
- private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions();
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.Options;
public DashboardControllerTests(JellyfinApplicationFactory factory)
{
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
new file mode 100644
index 000000000..169a5a6c5
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.StartupDtos;
+using MediaBrowser.Common.Json;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ public sealed class StartupControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+
+ public StartupControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ [Priority(-2)]
+ public async Task Configuration_EditConfig_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var config = new StartupConfigurationDto()
+ {
+ UICulture = "NewCulture",
+ MetadataCountryCode = "be",
+ PreferredMetadataLanguage = "nl"
+ };
+
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(config, _jsonOptions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ using var postResponse = await client.PostAsync("/Startup/Configuration", postContent).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
+
+ using var getResponse = await client.GetAsync("/Startup/Configuration").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
+
+ using var responseStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var newConfig = await JsonSerializer.DeserializeAsync<StartupConfigurationDto>(responseStream, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(config.UICulture, newConfig!.UICulture);
+ Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
+ Assert.Equal(config.PreferredMetadataLanguage, newConfig.PreferredMetadataLanguage);
+ }
+
+ [Fact]
+ [Priority(-2)]
+ public async Task User_DefaultUser_NameWithoutPassword()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+
+ using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var user = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
+ Assert.NotEmpty(user!.Name);
+ Assert.Null(user.Password);
+ }
+
+ [Fact]
+ [Priority(-1)]
+ public async Task User_EditUser_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var user = new StartupUserDto()
+ {
+ Name = "NewName",
+ Password = "NewPassword"
+ };
+
+ using var postContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions));
+ postContent.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
+ var postResponse = await client.PostAsync("/Startup/User", postContent).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
+
+ var getResponse = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
+
+ var contentStream = await getResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var newUser = await JsonSerializer.DeserializeAsync<StartupUserDto>(contentStream, _jsonOptions).ConfigureAwait(false);
+ Assert.Equal(user.Name, newUser!.Name);
+ Assert.NotEmpty(newUser.Password);
+ Assert.NotEqual(user.Password, newUser.Password);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task CompleteWizard_Success()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>())).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
+ }
+
+ [Fact]
+ [Priority(1)]
+ public async Task GetFirstUser_CompleteWizard_Unauthorized()
+ {
+ var client = _factory.CreateClient();
+
+ using var response = await client.GetAsync("/Startup/User").ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 49004966b..34cef9005 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -17,13 +17,13 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="Xunit.Priority" Version="1.1.6" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
<PackageReference Include="Moq" Version="4.16.0" />
</ItemGroup>
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 65ea28e94..a310b0ea9 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -5,6 +5,8 @@
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -22,7 +24,6 @@
<!-- Code Analyzers -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
@@ -32,8 +33,4 @@
<ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
</Project>