aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Dlna/PlayTo/Device.cs41
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs5
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs15
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-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/Localization/Core/da.json2
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs23
-rw-r--r--Jellyfin.Api/Controllers/ImageByNameController.cs23
-rw-r--r--Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs7
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs6
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs12
-rw-r--r--MediaBrowser.Controller/Resolvers/BaseItemResolver.cs2
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs24
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs24
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs83
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs9
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs8
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs6
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkParseTests.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs65
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs119
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs11
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs53
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs11
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs3
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo2
43 files changed, 700 insertions, 200 deletions
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 7bf7047fb..abd99bbc3 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null)
{
return false;
@@ -259,7 +259,7 @@ namespace Emby.Dlna.PlayTo
{
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null)
{
return;
@@ -290,7 +290,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null)
{
return;
@@ -323,7 +323,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null)
{
return;
@@ -403,6 +403,10 @@ namespace Emby.Dlna.PlayTo
public async Task SetPlay(CancellationToken cancellationToken)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (avCommands == null)
+ {
+ return;
+ }
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
@@ -413,7 +417,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
if (command == null)
{
return;
@@ -437,7 +441,7 @@ namespace Emby.Dlna.PlayTo
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
+ var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
if (command == null)
{
return;
@@ -565,7 +569,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
if (command == null)
{
return;
@@ -615,7 +619,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
+ var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
if (command == null)
{
return;
@@ -702,6 +706,10 @@ namespace Emby.Dlna.PlayTo
}
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return null;
+ }
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
@@ -770,6 +778,11 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
+ if (rendererCommands == null)
+ {
+ return (false, null);
+ }
+
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl,
service,
@@ -951,6 +964,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
AvCommands = TransportCommands.Create(document);
return AvCommands;
@@ -979,6 +996,10 @@ namespace Emby.Dlna.PlayTo
var httpClient = new SsdpHttpClient(_httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
RendererCommands = TransportCommands.Create(document);
return RendererCommands;
@@ -1010,6 +1031,10 @@ namespace Emby.Dlna.PlayTo
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
+ if (document == null)
+ {
+ return null;
+ }
var friendlyNames = new List<string>();
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index a6793a708..8272e505a 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -178,6 +178,11 @@ namespace Emby.Dlna.PlayTo
if (controller == null)
{
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
+ if (device == null)
+ {
+ _logger.LogError("Ignoring device as xml response is invalid.");
+ return;
+ }
string deviceName = device.Properties.Name;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index b7643fb27..e750f5bbc 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -94,10 +94,17 @@ namespace Emby.Dlna.PlayTo
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.PreserveWhitespace,
- cancellationToken).ConfigureAwait(false);
+ try
+ {
+ return await XDocument.LoadAsync(
+ stream,
+ LoadOptions.PreserveWhitespace,
+ cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ return null;
+ }
}
private async Task<HttpResponseMessage> PostSoapDataAsync(
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 164e6d49d..3846de5fd 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -697,6 +697,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddScoped<MediaInfoHelper>();
ServiceCollection.AddScoped<AudioHelper>();
ServiceCollection.AddScoped<DynamicHlsHelper>();
+
+ ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
}
/// <summary>
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/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 4ee4eb989..051d6d009 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -1,5 +1,5 @@
{
- "Albums": "Albums",
+ "Albums": "Albummer",
"AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation",
"Artists": "Kunstnere",
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/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index f828b1d9d..b0fd59e5e 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers
if (isHeadRequest)
{
- return new FileContentResult(Array.Empty<byte>(), contentType);
+ httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
+ return new OkResult();
}
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Helpers
{
httpContext.Response.ContentType = contentType;
- // if the request is a head request, return a NoContent result with the same headers as it would with a GET request
+ // if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request
if (isHeadRequest)
{
- return new NoContentResult();
+ return new OkResult();
}
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
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/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 1c695cafa..864cb3050 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -4,21 +4,25 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Providers
{
public class MetadataResult<T>
{
- public List<LocalImageInfo> Images { get; set; }
-
- public List<UserItemData> UserDataList { get; set; }
-
public MetadataResult()
{
Images = new List<LocalImageInfo>();
+ RemoteImages = new List<(string url, ImageType type)>();
ResultLanguage = "en";
}
+ public List<LocalImageInfo> Images { get; set; }
+
+ public List<(string url, ImageType type)> RemoteImages { get; set; }
+
+ public List<UserItemData> UserDataList { get; set; }
+
public List<PersonInfo> People { get; set; }
public bool HasMetadata { get; set; }
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.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 8b3ca17ca..437b43eca 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -473,7 +473,7 @@ namespace MediaBrowser.Providers.Manager
if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) ||
(originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
return updateType;
@@ -493,7 +493,7 @@ namespace MediaBrowser.Providers.Manager
if (currentList.Length != item.Genres.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager
if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -529,7 +529,7 @@ namespace MediaBrowser.Providers.Manager
{
if (item.UpdateRatingToItems(children))
{
- updateType = updateType | ItemUpdateType.MetadataEdit;
+ updateType |= ItemUpdateType.MetadataEdit;
}
}
@@ -686,7 +686,7 @@ namespace MediaBrowser.Providers.Manager
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
- refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType;
+ refreshResult.UpdateType |= remoteResult.UpdateType;
refreshResult.ErrorMessage = remoteResult.ErrorMessage;
refreshResult.Failures += remoteResult.Failures;
}
@@ -706,9 +706,15 @@ namespace MediaBrowser.Providers.Manager
if (localItem.HasMetadata)
{
+ foreach (var remoteImage in localItem.RemoteImages)
+ {
+ await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false);
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ }
+
if (imageService.MergeImages(item, localItem.Images))
{
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate;
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
if (localItem.UserDataList != null)
@@ -717,7 +723,7 @@ namespace MediaBrowser.Providers.Manager
}
MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
+ refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
// Only one local provider allowed per item
if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item))
@@ -749,7 +755,7 @@ namespace MediaBrowser.Providers.Manager
var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
- refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType;
+ refreshResult.UpdateType |= remoteResult.UpdateType;
refreshResult.ErrorMessage = remoteResult.ErrorMessage;
refreshResult.Failures += remoteResult.Failures;
}
@@ -845,7 +851,7 @@ namespace MediaBrowser.Providers.Manager
MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
MergeNewData(temp.Item, id);
- refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
+ refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
}
else
{
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/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index c4bbaf301..3a13f3888 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -27,6 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
private readonly IConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
private Dictionary<string, string> _validProviderIds;
/// <summary>
@@ -37,12 +38,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public BaseNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
{
Logger = logger;
_config = config;
@@ -50,6 +53,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
_validProviderIds = new Dictionary<string, string>();
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
protected CultureInfo UsCulture { get; } = new CultureInfo("en-US");
@@ -785,6 +789,64 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
+ case "thumb":
+ {
+ var artType = reader.GetAttribute("aspect");
+ var val = reader.ReadElementContentAsString();
+
+ // skip:
+ // - empty aspect tag
+ // - empty uri
+ // - tag containing '.' because we can't set images for seasons, episodes or movie sets within series or movies
+ if (string.IsNullOrEmpty(artType) || string.IsNullOrEmpty(val) || artType.Contains('.', StringComparison.Ordinal))
+ {
+ break;
+ }
+
+ ImageType imageType = GetImageType(artType);
+
+ if (!Uri.TryCreate(val, UriKind.Absolute, out var uri))
+ {
+ Logger.LogError("Image location {Path} specified in nfo file for {ItemName} is not a valid URL or file path.", val, item.Name);
+ break;
+ }
+
+ if (uri.IsFile)
+ {
+ // only allow one item of each type
+ if (itemResult.Images.Any(x => x.Type == imageType))
+ {
+ break;
+ }
+
+ var fileSystemMetadata = _directoryService.GetFile(val);
+ // non existing file returns null
+ if (fileSystemMetadata == null || !fileSystemMetadata.Exists)
+ {
+ Logger.LogWarning("Artwork file {Path} specified in nfo file for {ItemName} does not exist.", uri, item.Name);
+ break;
+ }
+
+ itemResult.Images.Add(new LocalImageInfo()
+ {
+ FileInfo = fileSystemMetadata,
+ Type = imageType
+ });
+ }
+ else
+ {
+ // only allow one item of each type
+ if (itemResult.RemoteImages.Any(x => x.type == imageType))
+ {
+ break;
+ }
+
+ itemResult.RemoteImages.Add((uri.ToString(), imageType));
+ }
+
+ break;
+ }
+
default:
string readerName = reader.Name;
if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue))
@@ -1178,5 +1240,24 @@ namespace MediaBrowser.XbmcMetadata.Parsers
return string.IsNullOrWhiteSpace(value) ? Array.Empty<string>() : value.Split(separator, StringSplitOptions.RemoveEmptyEntries);
}
+
+ /// <summary>
+ /// Parses the ImageType from the nfo aspect property.
+ /// </summary>
+ /// <param name="aspect">The nfo aspect property.</param>
+ /// <returns>The image type.</returns>
+ private static ImageType GetImageType(string aspect)
+ {
+ return aspect switch
+ {
+ "banner" => ImageType.Banner,
+ "clearlogo" => ImageType.Logo,
+ "discart" => ImageType.Disc,
+ "landscape" => ImageType.Thumb,
+ "clearart" => ImageType.Art,
+ // unknown type (including "poster") --> primary
+ _ => ImageType.Primary,
+ };
+ }
}
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index eb93148c6..6b1607530 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public EpisodeNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
index 2d0eb8433..b466fa25a 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
@@ -25,13 +25,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="DirectoryService"/> interface.</param>
public MovieNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
index bd2607bd8..2f5fd40e2 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
@@ -21,13 +21,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="DirectoryService"/> interface.</param>
public SeasonNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index fbab8b521..f6574c365 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -22,13 +22,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeriesNfoParser(
ILogger logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, config, providerManager, userManager, userDataManager, directoryService)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
index 24f127411..bd557d783 100644
--- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="AlbumNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public AlbumNfoProvider(
ILogger<AlbumNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<MusicAlbum> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicAlbum>(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicAlbum>(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
index fac28ab59..54bb83114 100644
--- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public ArtistNfoProvider(
IFileSystem fileSystem,
ILogger<ArtistNfoProvider> logger,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<MusicArtist> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicArtist>(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicArtist>(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
index 64cfc098f..8574be3f3 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
@@ -21,6 +21,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
protected BaseVideoNfoProvider(
ILogger<BaseVideoNfoProvider<T>> logger,
@@ -28,7 +29,8 @@ namespace MediaBrowser.XbmcMetadata.Providers
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -36,6 +38,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
@@ -45,10 +48,12 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
Item = result.Item
};
- new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(tmpItem, path, cancellationToken);
+ new MovieNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(tmpItem, path, cancellationToken);
result.Item = (T)tmpItem.Item;
result.People = tmpItem.People;
+ result.Images = tmpItem.Images;
+ result.RemoteImages = tmpItem.RemoteImages;
if (tmpItem.UserDataList != null)
{
diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
index 7233f99dc..64b208345 100644
--- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public EpisodeNfoProvider(
ILogger<EpisodeNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Episode> result, string path, CancellationToken cancellationToken)
{
- new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new EpisodeNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
index 811d39a9d..cdbc5a918 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public MovieNfoProvider(
ILogger<MovieNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
index 09df509ee..9d1f3e61d 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public MusicVideoNfoProvider(
ILogger<MusicVideoNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
index 8f0ed6df7..97220cf7e 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeasonNfoProvider(
ILogger<SeasonNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Season> result, string path, CancellationToken cancellationToken)
{
- new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new SeasonNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
index 3e496dc58..9a9b94123 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
private readonly IProviderManager _providerManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
+ private readonly IDirectoryService _directoryService;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesNfoProvider"/> class.
@@ -30,13 +31,15 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public SeriesNfoProvider(
ILogger<SeriesNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
: base(fileSystem)
{
_logger = logger;
@@ -44,12 +47,13 @@ namespace MediaBrowser.XbmcMetadata.Providers
_providerManager = providerManager;
_userManager = userManager;
_userDataManager = userDataManager;
+ _directoryService = directoryService;
}
/// <inheritdoc />
protected override void Fetch(MetadataResult<Series> result, string path, CancellationToken cancellationToken)
{
- new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager).Fetch(result, path, cancellationToken);
+ new SeriesNfoParser(_logger, _config, _providerManager, _userManager, _userDataManager, _directoryService).Fetch(result, path, cancellationToken);
}
/// <inheritdoc />
diff --git a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
index 4717d81e6..93b1be62f 100644
--- a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
@@ -21,14 +21,16 @@ namespace MediaBrowser.XbmcMetadata.Providers
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
+ /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
public VideoNfoProvider(
ILogger<VideoNfoProvider> logger,
IFileSystem fileSystem,
IConfigurationManager config,
IProviderManager providerManager,
IUserManager userManager,
- IUserDataManager userDataManager)
- : base(logger, fileSystem, config, providerManager, userManager, userDataManager)
+ IUserDataManager userDataManager,
+ IDirectoryService directoryService)
+ : base(logger, fileSystem, config, providerManager, userManager, userDataManager, directoryService)
{
}
}
diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
index c3469035e..28b5a4691 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs
@@ -83,7 +83,7 @@ namespace Jellyfin.Networking.Tests
/// <summary>
/// Checks IP address formats.
/// </summary>
- /// <param name="address"></param>
+ /// <param name="address">IP Address.</param>
[Theory]
[InlineData("127.0.0.1")]
[InlineData("127.0.0.1:123")]
@@ -107,7 +107,7 @@ namespace Jellyfin.Networking.Tests
/// <summary>
/// Checks IP address formats.
/// </summary>
- /// <param name="address"></param>
+ /// <param name="address">IP Address.</param>
[Theory]
[InlineData("127.0.0.1")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
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.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 b0a38736a..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,6 +17,7 @@
<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>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index 053e0a89e..9ad093a2b 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -37,8 +37,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
-
- _parser = new EpisodeNfoParser(new NullLogger<EpisodeNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ var directoryService = new Mock<IDirectoryService>();
+
+ _parser = new EpisodeNfoParser(
+ new NullLogger<EpisodeNfoParser>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index ff4795569..d4ce74132 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -9,7 +9,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.System;
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
@@ -23,6 +25,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
private readonly MovieNfoParser _parser;
private readonly IUserDataManager _userDataManager;
private readonly User _testUser;
+ private readonly FileSystemMetadata _localImageFileMetadata;
public MovieNfoParserTests()
{
@@ -52,8 +55,25 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
userData.Setup(x => x.GetUserData(_testUser, It.IsAny<BaseItem>()))
.Returns(new UserItemData());
+ var directoryService = new Mock<IDirectoryService>();
+ _localImageFileMetadata = new FileSystemMetadata()
+ {
+ Exists = true,
+ FullName = MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows ?
+ "C:\\media\\movies\\Justice League (2017).jpg"
+ : "/media/movies/Justice League (2017).jpg"
+ };
+ directoryService.Setup(x => x.GetFile(_localImageFileMetadata.FullName))
+ .Returns(_localImageFileMetadata);
+
_userDataManager = userData.Object;
- _parser = new MovieNfoParser(new NullLogger<MovieNfoParser>(), configManager.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new MovieNfoParser(
+ new NullLogger<MovieNfoParser>(),
+ configManager.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
@@ -134,6 +154,37 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
// Movie set
Assert.Equal("702342", item.ProviderIds[MetadataProvider.TmdbCollection.ToString()]);
Assert.Equal("Justice League Collection", item.CollectionName);
+
+ // Images
+ Assert.Equal(6, result.RemoteImages.Count);
+
+ var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList();
+ Assert.Single(posters);
+ Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url);
+
+ var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList();
+ Assert.Single(logos);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url);
+
+ var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList();
+ Assert.Single(banners);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url);
+
+ var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList();
+ Assert.Single(thumbs);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url);
+
+ var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList();
+ Assert.Single(art);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url);
+
+ var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList();
+ Assert.Single(discArt);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url);
+
+ // Local Image - contains only one item depending on operating system
+ Assert.Single(result.Images);
+ Assert.Equal(_localImageFileMetadata.Name, result.Images[0].FileInfo.Name);
}
[Theory]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
index 63f6dfce8..2129f3422 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -1,7 +1,6 @@
#pragma warning disable CA5369
using System;
-using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -38,8 +36,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new BaseNfoParser<MusicAlbum>(new NullLogger<BaseNfoParser<MusicAlbum>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new BaseNfoParser<MusicAlbum>(
+ new NullLogger<BaseNfoParser<MusicAlbum>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 438d47cac..3d8e13e96 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -35,8 +35,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new BaseNfoParser<MusicArtist>(new NullLogger<BaseNfoParser<MusicArtist>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new BaseNfoParser<MusicArtist>(
+ new NullLogger<BaseNfoParser<MusicArtist>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
index 898554936..bf887cab1 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicVideoNfoParserTests.cs
@@ -30,8 +30,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new MovieNfoParser(new NullLogger<BaseNfoParser<MusicVideo>>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new MovieNfoParser(
+ new NullLogger<BaseNfoParser<MusicVideo>>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
index a677cd724..0e61fa2a1 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeasonNfoProviderTests.cs
@@ -31,8 +31,15 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new SeasonNfoParser(new NullLogger<SeasonNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new SeasonNfoParser(
+ new NullLogger<SeasonNfoParser>(),
+ config.Object,
+ providerManager.Object,
+ user.Object,
+ userData.Object,
+ directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
index 80923957d..bdedae205 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
@@ -29,8 +29,9 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
.Returns(new XbmcMetadataOptions());
var user = new Mock<IUserManager>();
var userData = new Mock<IUserDataManager>();
+ var directoryService = new Mock<IDirectoryService>();
- _parser = new SeriesNfoParser(new NullLogger<SeriesNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object);
+ _parser = new SeriesNfoParser(new NullLogger<SeriesNfoParser>(), config.Object, providerManager.Object, user.Object, userData.Object, directoryService.Object);
}
[Fact]
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
index 72e27fe50..c35d37b18 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
@@ -59,6 +59,8 @@
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg">http://image.tmdb.org/t/p/original/exLtrlI7JjKcfQVTccI7XdQRFMz.jpg</thumb>
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/paLcue01KpfQftorfjKqqD4qvlL.jpg">http://image.tmdb.org/t/p/original/paLcue01KpfQftorfjKqqD4qvlL.jpg</thumb>
<thumb aspect="poster" preview="http://image.tmdb.org/t/p/w500/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg">http://image.tmdb.org/t/p/original/yVDIfiKIsCbdFcgLXW34bAsnQvy.jpg</thumb>
+ <thumb aspect="poster">C:\media\movies\Justice League (2017).jpg</thumb>
+ <thumb aspect="poster">/media/movies/Justice League (2017).jpg</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-585e9ca3bcf6a.png</thumb>
<thumb aspect="clearlogo" preview="https://assets.fanart.tv/preview/movies/141052/hdmovielogo/justice-league-57b476a831d74.png">https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-57b476a831d74.png</thumb>