aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs8
-rw-r--r--Jellyfin.Api/Controllers/EnvironmentController.cs6
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs6
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs2
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ImageByNameController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs12
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs16
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs2
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs4
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs8
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs20
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs2
-rw-r--r--Jellyfin.Api/Controllers/VideoAttachmentsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs4
-rw-r--r--Jellyfin.Api/Extensions/DtoExtensions.cs10
-rw-r--r--Jellyfin.Api/Helpers/AudioHelper.cs9
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs11
-rw-r--r--Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs4
-rw-r--r--Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs6
-rw-r--r--Jellyfin.Api/Helpers/HlsHelpers.cs5
-rw-r--r--Jellyfin.Api/Helpers/ProgressiveFileCopier.cs6
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs2
-rw-r--r--Jellyfin.Api/Helpers/StreamingHelpers.cs6
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs19
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj8
-rw-r--r--Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs65
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs15
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs15
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs6
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs7
-rw-r--r--Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs7
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs4
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs2
-rw-r--r--Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs2
-rw-r--r--Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs6
-rw-r--r--Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs14
38 files changed, 210 insertions, 120 deletions
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index e07690e11..6e59da798 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -14,6 +14,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -1347,7 +1348,9 @@ namespace Jellyfin.Api.Controllers
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
- var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
+ var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+
+ var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer);
var segmentFormat = GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.');
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
@@ -1565,8 +1568,7 @@ namespace Jellyfin.Api.Controllers
private string GetSegmentPath(StreamState state, string playlist, int index)
{
- var folder = Path.GetDirectoryName(playlist);
-
+ var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
var filename = Path.GetFileNameWithoutExtension(playlist);
return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer));
diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs
index ce88b0b99..6dd536254 100644
--- a/Jellyfin.Api/Controllers/EnvironmentController.cs
+++ b/Jellyfin.Api/Controllers/EnvironmentController.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.EnvironmentDtos;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -103,6 +104,11 @@ namespace Jellyfin.Api.Controllers
if (validatePathDto.ValidateWritable)
{
+ if (validatePathDto.Path == null)
+ {
+ throw new ResourceNotFoundException(nameof(validatePathDto.Path));
+ }
+
var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString());
try
{
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 2a567c846..008bb58d1 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -78,8 +78,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery
{
User = user,
- MediaTypes = (mediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
- IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ MediaTypes = (mediaTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
+ IncludeItemTypes = (includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
Recursive = true,
EnableTotalRecordCount = false,
DtoOptions = new DtoOptions
@@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers
var genreQuery = new InternalItemsQuery(user)
{
IncludeItemTypes =
- (includeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+ (includeItemTypes ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries),
DtoOptions = new DtoOptions
{
Fields = Array.Empty<ItemFields>(),
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 89f4beefd..9c222135d 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -176,7 +176,7 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
- private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+ private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
where T : BaseItem, new()
{
var result = libraryManager.GetItemList(new InternalItemsQuery
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 054e586ce..3b75e8d43 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO;
@@ -134,7 +135,8 @@ namespace Jellyfin.Api.Controllers
var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
.FirstOrDefault(i =>
string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase)
- && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
+ && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+ ?? throw new ResourceNotFoundException($"Provided path ({transcodeFolderPath}) is not valid.");
return GetFileResult(file, playlistPath);
}
diff --git a/Jellyfin.Api/Controllers/ImageByNameController.cs b/Jellyfin.Api/Controllers/ImageByNameController.cs
index 980c3273d..198dbc51f 100644
--- a/Jellyfin.Api/Controllers/ImageByNameController.cs
+++ b/Jellyfin.Api/Controllers/ImageByNameController.cs
@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="theme">Theme to search.</param>
/// <param name="name">File name to search for.</param>
/// <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)
+ private ActionResult GetImageFile(string basePath, string theme, string? name)
{
var themeFolder = Path.Combine(basePath, theme);
if (Directory.Exists(themeFolder))
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 4a67c1aed..76e53b9a5 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
@@ -1268,7 +1269,7 @@ namespace Jellyfin.Api.Controllers
Response.Headers.Add(key, value);
}
- Response.ContentType = imageContentType;
+ Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept);
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index ab73aa428..a7c1a6388 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -334,10 +334,16 @@ namespace Jellyfin.Api.Controllers
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
+ if (result.Content.Headers.ContentType?.MediaType == null)
+ {
+ throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
+ }
+
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+ Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
await using var fileStream = new FileStream(
@@ -351,7 +357,9 @@ namespace Jellyfin.Api.Controllers
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+ var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+
+ Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 0d5c1d278..1b115d800 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers
: null;
var dtoOptions = new DtoOptions().AddClientFields(Request);
- BaseItem parent = item.GetParent();
+ BaseItem? parent = item.GetParent();
while (parent != null)
{
@@ -467,7 +467,7 @@ namespace Jellyfin.Api.Controllers
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
- parent = parent.GetParent();
+ parent = parent?.GetParent();
}
return baseItemDtos;
@@ -681,12 +681,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
/// <response code="200">Similar items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
- [HttpGet("Artists/{itemId}/Similar")]
+ [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists")]
[HttpGet("Items/{itemId}/Similar")]
- [HttpGet("Albums/{itemId}/Similar")]
- [HttpGet("Shows/{itemId}/Similar")]
- [HttpGet("Movies/{itemId}/Similar")]
- [HttpGet("Trailers/{itemId}/Similar")]
+ [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums")]
+ [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
+ [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
+ [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
@@ -893,7 +893,7 @@ namespace Jellyfin.Api.Controllers
return _libraryManager.GetItemsResult(query).TotalRecordCount;
}
- private BaseItem TranslateParentItem(BaseItem item, User user)
+ private BaseItem? TranslateParentItem(BaseItem item, User user)
{
return item.GetParent() is AggregateFolder
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 458f40b2d..29c0f1df4 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -1073,7 +1073,7 @@ namespace Jellyfin.Api.Controllers
var client = _httpClientFactory.CreateClient(NamedClient.Default);
// https://json.schedulesdirect.org/20141201/available/countries
// Can't dispose the response as it's required up the call chain.
- var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
+ var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries"))
.ConfigureAwait(false);
return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index ab9e4cf8e..229d9ff02 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -139,7 +139,7 @@ namespace Jellyfin.Api.Controllers
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
- MusicGenre item;
+ MusicGenre? item;
if (genreName.IndexOf(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
- private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
+ private T? GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions)
where T : BaseItem, new()
{
var result = libraryManager.GetItemList(new InternalItemsQuery
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 1d9de14d2..1f797d6bc 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -54,6 +54,11 @@ namespace Jellyfin.Api.Controllers
string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid))
.FirstOrDefault();
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return result;
}
@@ -149,12 +154,13 @@ namespace Jellyfin.Api.Controllers
/// <param name="repositoryInfos">The list of package repositories.</param>
/// <response code="204">Package repositories saved.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
- [HttpOptions("Repositories")]
+ [HttpPost("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
{
_serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
+ _serverConfigurationManager.SaveConfiguration();
return NoContent();
}
}
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 5f095443b..5284888d8 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -157,9 +157,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesImageFile]
- public async Task<ActionResult> GetRemoteImage([FromQuery, Required] string imageUrl)
+ public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
{
- var urlHash = imageUrl.GetMD5();
+ var urlHash = imageUrl.ToString().GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
string? contentPath = null;
@@ -245,17 +245,25 @@ namespace Jellyfin.Api.Controllers
/// <param name="urlHash">The URL hash.</param>
/// <param name="pointerCachePath">The pointer cache path.</param>
/// <returns>Task.</returns>
- private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
+ private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath)
{
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
- var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
+ if (response.Content.Headers.ContentType?.MediaType == null)
+ {
+ throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType));
+ }
+
+ var ext = response.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
+ Directory.CreateDirectory(fullCacheDirectory);
await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+
+ var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
+ Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
.ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 62c870cb1..e75f0d06b 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers
}
}
- private T GetParentWithImage<T>(BaseItem item, ImageType type)
+ private T? GetParentWithImage<T>(BaseItem item, ImageType type)
where T : BaseItem
{
return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index 09a1c93e6..418c0c123 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -46,7 +46,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<FileStreamResult>> GetAttachment(
+ public async Task<ActionResult> GetAttachment(
[FromRoute, Required] Guid videoId,
[FromRoute, Required] string mediaSourceId,
[FromRoute, Required] int index)
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index d7bcf79c1..389dc8a08 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -11,6 +11,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -361,7 +362,8 @@ namespace Jellyfin.Api.Controllers
var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts";
- var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
+ var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+ var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format;
var segmentFormat = format.TrimStart('.');
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs
index 6dee9db38..f2abd515d 100644
--- a/Jellyfin.Api/Extensions/DtoExtensions.cs
+++ b/Jellyfin.Api/Extensions/DtoExtensions.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using Jellyfin.Api.Helpers;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -43,7 +45,7 @@ namespace Jellyfin.Api.Extensions
client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
{
- int oldLen = dtoOptions.Fields.Length;
+ int oldLen = dtoOptions.Fields.Count;
var arr = new ItemFields[oldLen + 1];
dtoOptions.Fields.CopyTo(arr, 0);
arr[oldLen] = ItemFields.RecursiveItemCount;
@@ -61,7 +63,7 @@ namespace Jellyfin.Api.Extensions
client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
{
- int oldLen = dtoOptions.Fields.Length;
+ int oldLen = dtoOptions.Fields.Count;
var arr = new ItemFields[oldLen + 1];
dtoOptions.Fields.CopyTo(arr, 0);
arr[oldLen] = ItemFields.ChildCount;
@@ -90,7 +92,7 @@ namespace Jellyfin.Api.Extensions
bool? enableImages,
bool? enableUserData,
int? imageTypeLimit,
- ImageType[] enableImageTypes)
+ IReadOnlyList<ImageType> enableImageTypes)
{
dtoOptions.EnableImages = enableImages ?? true;
@@ -104,7 +106,7 @@ namespace Jellyfin.Api.Extensions
dtoOptions.EnableUserData = enableUserData.Value;
}
- if (enableImageTypes.Length != 0)
+ if (enableImageTypes.Count != 0)
{
dtoOptions.ImageTypes = enableImageTypes;
}
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
index a3f2d88ce..21ec2d32f 100644
--- a/Jellyfin.Api/Helpers/AudioHelper.cs
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -1,8 +1,10 @@
-using System.Net.Http;
+using System;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
@@ -98,6 +100,11 @@ namespace Jellyfin.Api.Helpers
TranscodingJobType transcodingJobType,
StreamingRequestDto streamingRequest)
{
+ if (_httpContextAccessor.HttpContext == null)
+ {
+ throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
+ }
+
bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
var cancellationTokenSource = new CancellationTokenSource();
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index ea012f837..e7fac50c6 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Helpers
StreamingRequestDto streamingRequest,
bool enableAdaptiveBitrateStreaming)
{
- var isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == WebRequestMethods.Http.Head;
+ var isHeadRequest = _httpContextAccessor.HttpContext?.Request.Method == WebRequestMethods.Http.Head;
var cancellationTokenSource = new CancellationTokenSource();
return await GetMasterPlaylistInternal(
streamingRequest,
@@ -130,6 +130,11 @@ namespace Jellyfin.Api.Helpers
TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource)
{
+ if (_httpContextAccessor.HttpContext == null)
+ {
+ throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
+ }
+
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
_httpContextAccessor.HttpContext.Request,
@@ -487,14 +492,14 @@ namespace Jellyfin.Api.Helpers
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
{
- string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
+ string? profile = state.GetRequestedProfiles("h264").FirstOrDefault();
return HlsCodecStringHelpers.GetH264String(profile, level);
}
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
- string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
+ string? profile = state.GetRequestedProfiles("h265").FirstOrDefault();
return HlsCodecStringHelpers.GetH265String(profile, level);
}
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index 366301d3e..20c94cdda 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -37,8 +37,8 @@ namespace Jellyfin.Api.Helpers
}
// Can't dispose the response as it's required up the call chain.
- var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
- var contentType = response.Content.Headers.ContentType.ToString();
+ var response = await httpClient.GetAsync(new Uri(state.MediaPath)).ConfigureAwait(false);
+ var contentType = response.Content.Headers.ContentType?.ToString();
httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
index 95f1906ef..1bd3d67ff 100644
--- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -23,7 +23,7 @@ namespace Jellyfin.Api.Helpers
/// </summary>
/// <param name="profile">AAC profile.</param>
/// <returns>AAC codec string.</returns>
- public static string GetAACString(string profile)
+ public static string GetAACString(string? profile)
{
StringBuilder result = new StringBuilder("mp4a", 9);
@@ -46,7 +46,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="profile">H.264 profile.</param>
/// <param name="level">H.264 level.</param>
/// <returns>H.264 string.</returns>
- public static string GetH264String(string profile, int level)
+ public static string GetH264String(string? profile, int level)
{
StringBuilder result = new StringBuilder("avc1", 11);
@@ -80,7 +80,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="profile">H.265 profile.</param>
/// <param name="level">H.265 level.</param>
/// <returns>H.265 string.</returns>
- public static string GetH265String(string profile, int level)
+ public static string GetH265String(string? profile, int level)
{
// The h265 syntax is a bit of a mystery at the time this comment was written.
// This is what I've found through various sources:
diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs
index 242496697..45ce90566 100644
--- a/Jellyfin.Api/Helpers/HlsHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsHelpers.cs
@@ -45,6 +45,11 @@ namespace Jellyfin.Api.Helpers
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
+ if (line == null)
+ {
+ // Nothing currently in buffer.
+ break;
+ }
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
{
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
index e00ed3304..8bddf00d5 100644
--- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
+++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs
@@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Models.PlaybackDtos;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.IO;
@@ -90,6 +91,11 @@ namespace Jellyfin.Api.Helpers
allowAsyncFileRead = true;
}
+ if (_path == null)
+ {
+ throw new ResourceNotFoundException(nameof(_path));
+ }
+
await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
var eofCount = 0;
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 13d6da317..f06f038ab 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Helpers
}
return removeEmpty
- ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
+ ? value.Split(separator, StringSplitOptions.RemoveEmptyEntries)
: value.Split(separator);
}
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index f4ec29bde..0566f4c4d 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -83,8 +83,12 @@ namespace Jellyfin.Api.Helpers
}
streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
+ if (httpRequest.Path.Value == null)
+ {
+ throw new ResourceNotFoundException(nameof(httpRequest.Path));
+ }
- var url = httpRequest.Path.Value.Split('.').Last();
+ var url = httpRequest.Path.Value.Split('.')[^1];
if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
{
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 0db1fabff..168dc27a8 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -12,6 +12,7 @@ using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -102,7 +103,7 @@ namespace Jellyfin.Api.Helpers
/// </summary>
/// <param name="playSessionId">Playback session id.</param>
/// <returns>The transcoding job.</returns>
- public TranscodingJobDto GetTranscodingJob(string playSessionId)
+ public TranscodingJobDto? GetTranscodingJob(string playSessionId)
{
lock (_activeTranscodingJobs)
{
@@ -116,7 +117,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="path">Path to the transcoding file.</param>
/// <param name="type">The <see cref="TranscodingJobType"/>.</param>
/// <returns>The transcoding job.</returns>
- public TranscodingJobDto GetTranscodingJob(string path, TranscodingJobType type)
+ public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
{
lock (_activeTranscodingJobs)
{
@@ -193,10 +194,9 @@ namespace Jellyfin.Api.Helpers
/// Called when [transcode kill timer stopped].
/// </summary>
/// <param name="state">The state.</param>
- private async void OnTranscodeKillTimerStopped(object state)
+ private async void OnTranscodeKillTimerStopped(object? state)
{
- var job = (TranscodingJobDto)state;
-
+ var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state));
if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
{
var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
@@ -489,7 +489,8 @@ namespace Jellyfin.Api.Helpers
CancellationTokenSource cancellationTokenSource,
string? workingDirectory = null)
{
- Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+ var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
+ Directory.CreateDirectory(directory);
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
@@ -523,7 +524,7 @@ namespace Jellyfin.Api.Helpers
RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath,
Arguments = commandLineArguments,
- WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
+ WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? string.Empty : workingDirectory,
ErrorDialog = false
},
EnableRaisingEvents = true
@@ -827,7 +828,7 @@ namespace Jellyfin.Api.Helpers
{
lock (_transcodingLocks)
{
- if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result))
+ if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result))
{
result = new SemaphoreSlim(1, 1);
_transcodingLocks[outputPath] = result;
@@ -837,7 +838,7 @@ namespace Jellyfin.Api.Helpers
}
}
- private void OnPlaybackProgress(object sender, PlaybackProgressEventArgs e)
+ private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
{
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index da0852ceb..da6e5fa2d 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -6,17 +6,19 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.1</TargetFramework>
+ <TargetFramework>net5.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
+ <NoWarn>AD0001</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.9" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
</ItemGroup>
diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs
index 4f012cab2..e90f48d2f 100644
--- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs
+++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.ModelBinders
{
@@ -11,6 +13,17 @@ namespace Jellyfin.Api.ModelBinders
/// </summary>
public class CommaDelimitedArrayModelBinder : IModelBinder
{
+ private readonly ILogger<CommaDelimitedArrayModelBinder> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinder"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedArrayModelBinder}"/> interface.</param>
+ public CommaDelimitedArrayModelBinder(ILogger<CommaDelimitedArrayModelBinder> logger)
+ {
+ _logger = logger;
+ }
+
/// <inheritdoc/>
public Task BindModelAsync(ModelBindingContext bindingContext)
{
@@ -20,16 +33,8 @@ namespace Jellyfin.Api.ModelBinders
if (valueProviderResult.Length > 1)
{
- var result = Array.CreateInstance(elementType, valueProviderResult.Length);
-
- for (int i = 0; i < valueProviderResult.Length; i++)
- {
- var value = converter.ConvertFromString(valueProviderResult.Values[i].Trim());
-
- result.SetValue(value, i);
- }
-
- bindingContext.Result = ModelBindingResult.Success(result);
+ var typedValues = GetParsedResult(valueProviderResult.Values, elementType, converter);
+ bindingContext.Result = ModelBindingResult.Success(typedValues);
}
else
{
@@ -37,13 +42,8 @@ namespace Jellyfin.Api.ModelBinders
if (value != null)
{
- var values = Array.ConvertAll(
- value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries),
- x => converter.ConvertFromString(x?.Trim()));
-
- var typedValues = Array.CreateInstance(elementType, values.Length);
- values.CopyTo(typedValues, 0);
-
+ var splitValues = value.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ var typedValues = GetParsedResult(splitValues, elementType, converter);
bindingContext.Result = ModelBindingResult.Success(typedValues);
}
else
@@ -55,5 +55,36 @@ namespace Jellyfin.Api.ModelBinders
return Task.CompletedTask;
}
+
+ private Array GetParsedResult(IReadOnlyList<string> values, Type elementType, TypeConverter converter)
+ {
+ var parsedValues = new object?[values.Count];
+ var convertedCount = 0;
+ for (var i = 0; i < values.Count; i++)
+ {
+ try
+ {
+ parsedValues[i] = converter.ConvertFromString(values[i].Trim());
+ convertedCount++;
+ }
+ catch (FormatException e)
+ {
+ _logger.LogWarning(e, "Error converting value.");
+ }
+ }
+
+ var typedValues = Array.CreateInstance(elementType, convertedCount);
+ var typedValueIndex = 0;
+ for (var i = 0; i < parsedValues.Length; i++)
+ {
+ if (parsedValues[i] != null)
+ {
+ typedValues.SetValue(parsedValues[i], typedValueIndex);
+ typedValueIndex++;
+ }
+ }
+
+ return typedValues;
+ }
}
}
diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
index 33eda33cb..7de44aa65 100644
--- a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using System;
+using System.Collections.Generic;
namespace Jellyfin.Api.Models.LibraryDtos
{
@@ -10,25 +11,21 @@ namespace Jellyfin.Api.Models.LibraryDtos
/// <summary>
/// Gets or sets the metadata savers.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataSavers", Justification = "Imported from ServiceStack")]
- public LibraryOptionInfoDto[] MetadataSavers { get; set; } = null!;
+ public IReadOnlyList<LibraryOptionInfoDto> MetadataSavers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
/// Gets or sets the metadata readers.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataReaders", Justification = "Imported from ServiceStack")]
- public LibraryOptionInfoDto[] MetadataReaders { get; set; } = null!;
+ public IReadOnlyList<LibraryOptionInfoDto> MetadataReaders { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
/// Gets or sets the subtitle fetchers.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SubtitleFetchers", Justification = "Imported from ServiceStack")]
- public LibraryOptionInfoDto[] SubtitleFetchers { get; set; } = null!;
+ public IReadOnlyList<LibraryOptionInfoDto> SubtitleFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
/// Gets or sets the type options.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "TypeOptions", Justification = "Imported from ServiceStack")]
- public LibraryTypeOptionsDto[] TypeOptions { get; set; } = null!;
+ public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>();
}
}
diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs
index ad031e95e..20f45196d 100644
--- a/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/LibraryTypeOptionsDto.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@@ -17,25 +18,21 @@ namespace Jellyfin.Api.Models.LibraryDtos
/// <summary>
/// Gets or sets the metadata fetchers.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "MetadataFetchers", Justification = "Imported from ServiceStack")]
- public LibraryOptionInfoDto[] MetadataFetchers { get; set; } = null!;
+ public IReadOnlyList<LibraryOptionInfoDto> MetadataFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
/// Gets or sets the image fetchers.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "ImageFetchers", Justification = "Imported from ServiceStack")]
- public LibraryOptionInfoDto[] ImageFetchers { get; set; } = null!;
+ public IReadOnlyList<LibraryOptionInfoDto> ImageFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
/// Gets or sets the supported image types.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "SupportedImageTypes", Justification = "Imported from ServiceStack")]
- public ImageType[] SupportedImageTypes { get; set; } = null!;
+ public IReadOnlyList<ImageType> SupportedImageTypes { get; set; } = Array.Empty<ImageType>();
/// <summary>
/// Gets or sets the default image options.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "DefaultImageOptions", Justification = "Imported from ServiceStack")]
- public ImageOption[] DefaultImageOptions { get; set; } = null!;
+ public IReadOnlyList<ImageOption> DefaultImageOptions { get; set; } = Array.Empty<ImageOption>();
}
}
diff --git a/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
index 970d8acdb..f43822da7 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
@@ -25,8 +26,7 @@ namespace Jellyfin.Api.Models.LiveTvDtos
/// <summary>
/// Gets or sets list of mappings.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "Mappings", Justification = "Imported from ServiceStack")]
- public NameValuePair[] Mappings { get; set; } = null!;
+ public IReadOnlyList<NameValuePair> Mappings { get; set; } = Array.Empty<NameValuePair>();
/// <summary>
/// Gets or sets provider name.
diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
index aa9865192..5ca4408d1 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using MediaBrowser.Common.Json.Converters;
@@ -143,8 +144,7 @@ namespace Jellyfin.Api.Models.LiveTvDtos
/// Optional.
/// </summary>
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "EnableImageTypes", Justification = "Imported from ServiceStack")]
- public ImageType[] EnableImageTypes { get; set; } = Array.Empty<ImageType>();
+ public IReadOnlyList<ImageType> EnableImageTypes { get; set; } = Array.Empty<ImageType>();
/// <summary>
/// Gets or sets include user data.
@@ -169,7 +169,6 @@ namespace Jellyfin.Api.Models.LiveTvDtos
/// Optional.
/// </summary>
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
- [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "Fields", Justification = "Imported from ServiceStack")]
- public ItemFields[] Fields { get; set; } = Array.Empty<ItemFields>();
+ public IReadOnlyList<ItemFields> Fields { get; set; } = Array.Empty<ItemFields>();
}
}
diff --git a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
index f797a3807..b0b3de855 100644
--- a/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
+++ b/Jellyfin.Api/Models/MediaInfoDtos/OpenLiveStreamDto.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
@@ -17,8 +18,6 @@ namespace Jellyfin.Api.Models.MediaInfoDtos
/// <summary>
/// Gets or sets the device play protocols.
/// </summary>
- [SuppressMessage("Microsoft.Performance", "CA1819:DontReturnArrays", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
- [SuppressMessage("Microsoft.Performance", "SA1011:ClosingBracketsSpace", MessageId = "DevicePlayProtocols", Justification = "Imported from ServiceStack")]
- public MediaProtocol[]? DirectPlayProtocols { get; set; }
+ public IReadOnlyList<MediaProtocol> DirectPlayProtocols { get; set; } = Array.Empty<MediaProtocol>();
}
}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
index b9507a4e5..9edc19bb6 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// Start kill timer.
/// </summary>
/// <param name="callback">Callback action.</param>
- public void StartKillTimer(Action<object> callback)
+ public void StartKillTimer(Action<object?> callback)
{
StartKillTimer(callback, PingTimeout);
}
@@ -206,7 +206,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// </summary>
/// <param name="callback">Callback action.</param>
/// <param name="intervalMs">Callback interval.</param>
- public void StartKillTimer(Action<object> callback, int intervalMs)
+ public void StartKillTimer(Action<object?> callback, int intervalMs)
{
if (HasExited)
{
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
index b5e42ea29..872a46824 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
return _config.GetConfiguration<EncodingOptions>("encoding");
}
- private async void TimerCallback(object state)
+ private async void TimerCallback(object? state)
{
if (_job.HasExited)
{
diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
index 77d55828d..ce5465116 100644
--- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
+++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs
@@ -56,7 +56,7 @@ namespace Jellyfin.Api.WebSocketListeners
base.Dispose(dispose);
}
- private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+ private void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
{
SendData(true);
}
diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
index 80314b923..94df23e56 100644
--- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
+++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs
@@ -64,19 +64,19 @@ namespace Jellyfin.Api.WebSocketListeners
base.Dispose(dispose);
}
- private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
+ private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e)
{
SendData(true);
e.Task.TaskProgress -= OnTaskProgress;
}
- private void OnTaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e)
+ private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e)
{
SendData(true);
e.Argument.TaskProgress += OnTaskProgress;
}
- private void OnTaskProgress(object sender, GenericEventArgs<double> e)
+ private void OnTaskProgress(object? sender, GenericEventArgs<double> e)
{
SendData(false);
}
diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
index 1cf43a005..d996ac69f 100644
--- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
+++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs
@@ -66,37 +66,37 @@ namespace Jellyfin.Api.WebSocketListeners
base.Dispose(dispose);
}
- private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
+ private async void OnSessionManagerSessionActivity(object? sender, SessionEventArgs e)
{
await SendData(false).ConfigureAwait(false);
}
- private async void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e)
+ private async void OnSessionManagerCapabilitiesChanged(object? sender, SessionEventArgs e)
{
await SendData(true).ConfigureAwait(false);
}
- private async void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e)
+ private async void OnSessionManagerPlaybackProgress(object? sender, PlaybackProgressEventArgs e)
{
await SendData(!e.IsAutomated).ConfigureAwait(false);
}
- private async void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
+ private async void OnSessionManagerPlaybackStopped(object? sender, PlaybackStopEventArgs e)
{
await SendData(true).ConfigureAwait(false);
}
- private async void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e)
+ private async void OnSessionManagerPlaybackStart(object? sender, PlaybackProgressEventArgs e)
{
await SendData(true).ConfigureAwait(false);
}
- private async void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
+ private async void OnSessionManagerSessionEnded(object? sender, SessionEventArgs e)
{
await SendData(true).ConfigureAwait(false);
}
- private async void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
+ private async void OnSessionManagerSessionStarted(object? sender, SessionEventArgs e)
{
await SendData(true).ConfigureAwait(false);
}