aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs9
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs3
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs23
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs4
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/gl.json52
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json3
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs93
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs22
-rw-r--r--Jellyfin.Api/Controllers/LibraryStructureController.cs2
-rw-r--r--Jellyfin.Networking/Manager/NetworkManager.cs37
-rw-r--r--MediaBrowser.Common/Net/IPHost.cs97
-rw-r--r--MediaBrowser.Common/Plugins/IPluginManager.cs10
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs5
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs1
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs47
-rw-r--r--MediaBrowser.Model/Configuration/AccessSchedule.cs27
-rw-r--r--MediaBrowser.Model/Entities/CollectionTypeOptions.cs16
-rw-r--r--MediaBrowser.Model/Entities/VirtualFolderInfo.cs2
-rw-r--r--MediaBrowser.Model/Updates/InstallationInfo.cs7
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj4
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj4
-rw-r--r--tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs74
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj4
37 files changed, 359 insertions, 221 deletions
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 8f60c3f78..ec8716029 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -316,9 +316,12 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
- // DLNA will only work over http, so we must reset to http:// : {port}
- uri.Scheme = "http://";
- uri.Port = _netConfig.HttpServerPortNumber;
+ if (_appHost.PublishedServerUrl == null)
+ {
+ // DLNA will only work over http, so we must reset to http:// : {port}.
+ uri.Scheme = "http";
+ uri.Port = _netConfig.HttpServerPortNumber;
+ }
var device = new SsdpRootDevice
{
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index b1c20815e..26a951ac6 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -134,6 +134,9 @@ namespace Emby.Server.Implementations
public bool CoreStartupHasCompleted { get; private set; }
+ /// <inheritdoc />
+ public Uri PublishedServerUrl => _startupOptions.PublishedServerUrl;
+
public virtual bool CanLaunchWebBrowser
{
get
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 3011a37e3..1ab2bdfbe 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Collections
var name = _localizationManager.GetLocalizedString("Collections");
- await _libraryManager.AddVirtualFolder(name, CollectionType.BoxSets, libraryOptions, true).ConfigureAwait(false);
+ await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false);
return FindFolders(path).First();
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index db27862ce..d9ffe64b3 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1240,11 +1240,20 @@ namespace Emby.Server.Implementations.Library
return info;
}
- private string GetCollectionType(string path)
+ private CollectionTypeOptions? GetCollectionType(string path)
{
- return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
- .Select(Path.GetFileNameWithoutExtension)
- .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false);
+ foreach (var file in files)
+ {
+ // TODO: @bond use a ReadOnlySpan<char> here when Enum.TryParse supports it
+ // https://github.com/dotnet/runtime/issues/20008
+ if (Enum.TryParse<CollectionTypeOptions>(Path.GetExtension(file), true, out var res))
+ {
+ return res;
+ }
+ }
+
+ return null;
}
/// <summary>
@@ -2956,7 +2965,7 @@ namespace Emby.Server.Implementations.Library
throw new InvalidOperationException();
}
- public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
+ public async Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2990,9 +2999,9 @@ namespace Emby.Server.Implementations.Library
{
Directory.CreateDirectory(virtualFolderPath);
- if (!string.IsNullOrEmpty(collectionType))
+ if (collectionType != null)
{
- var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+ var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection");
File.WriteAllBytes(path, Array.Empty<byte>());
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 2c0de661d..13b5a1c55 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -2604,7 +2604,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Movies",
- CollectionType = CollectionType.Movies
+ CollectionType = CollectionTypeOptions.Movies
};
}
@@ -2615,7 +2615,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Shows",
- CollectionType = CollectionType.TvShows
+ CollectionType = CollectionTypeOptions.TvShows
};
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 977a1c2d7..b029b7042 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -112,5 +112,8 @@
"TaskRefreshLibraryDescription": "Skandeer u media versameling vir nuwe lêers en verfris metadata.",
"TaskRefreshLibrary": "Skandeer Media Versameling",
"TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.",
- "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde"
+ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde",
+ "Undefined": "Ongedefineerd",
+ "Forced": "Geforseer",
+ "Default": "Oorspronklik"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 05181116d..5d7ed243f 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.",
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
- "Forced": "Forzado"
+ "Forced": "Forzado",
+ "Default": "Predeterminado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index faee2519a..12bcd793e 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -7,5 +7,55 @@
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autenticouse correctamente",
"Artists": "Artistas",
- "Application": "Aplicativo"
+ "Application": "Aplicativo",
+ "NotificationOptionServerRestartRequired": "Necesario un reinicio do servidor",
+ "NotificationOptionPluginUpdateInstalled": "Actualización do Plugin instalada",
+ "NotificationOptionPluginUninstalled": "Plugin desinstalado",
+ "NotificationOptionPluginInstalled": "Plugin instalado",
+ "NotificationOptionPluginError": "Fallo do Plugin",
+ "NotificationOptionNewLibraryContent": "Novo contido engadido",
+ "NotificationOptionInstallationFailed": "Fallo na instalación",
+ "NotificationOptionCameraImageUploaded": "Imaxe da cámara subida",
+ "NotificationOptionAudioPlaybackStopped": "Reproducción de audio parada",
+ "NotificationOptionAudioPlayback": "Reproducción de audio comezada",
+ "NotificationOptionApplicationUpdateInstalled": "Actualización da aplicación instalada",
+ "NotificationOptionApplicationUpdateAvailable": "Actualización da aplicación dispoñible",
+ "NewVersionIsAvailable": "Unha nova versión do Servidor Jellyfin está dispoñible para descarga.",
+ "NameSeasonUnknown": "Tempada descoñecida",
+ "NameSeasonNumber": "Tempada {0}",
+ "NameInstallFailed": "{0} instalación fallida",
+ "MusicVideos": "Vídeos Musicais",
+ "Music": "Música",
+ "Movies": "Películas",
+ "MixedContent": "Contido Mixto",
+ "MessageServerConfigurationUpdated": "A configuración do servidor foi actualizada",
+ "MessageNamedServerConfigurationUpdatedWithValue": "A sección de configuración {0} do servidor foi actualizada",
+ "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado a {0}",
+ "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado",
+ "Latest": "Último",
+ "LabelRunningTimeValue": "Tempo de execución: {0}",
+ "LabelIpAddressValue": "Enderezo IP: {0}",
+ "ItemRemovedWithName": "{0} foi eliminado da biblioteca",
+ "ItemAddedWithName": "{0} foi engadido a biblioteca",
+ "Inherit": "Herdar",
+ "HomeVideos": "Videos caseiros",
+ "HeaderRecordingGroups": "Grupos de Grabación",
+ "HeaderNextUp": "De seguido",
+ "HeaderLiveTV": "TV en directo",
+ "HeaderFavoriteSongs": "Cancións Favoritas",
+ "HeaderFavoriteShows": "Series de TV Favoritas",
+ "HeaderFavoriteEpisodes": "Episodios Favoritos",
+ "HeaderFavoriteArtists": "Artistas Favoritos",
+ "HeaderFavoriteAlbums": "Álbunes Favoritos",
+ "HeaderContinueWatching": "Seguir mirando",
+ "HeaderAlbumArtists": "Artistas de Album",
+ "Genres": "Xéneros",
+ "Forced": "Forzado",
+ "Folders": "Cartafoles",
+ "Favorites": "Favoritos",
+ "FailedLoginAttemptWithUserName": "Intento de incio de sesión fallido {0}",
+ "DeviceOnlineWithName": "{0} conectouse",
+ "DeviceOfflineWithName": "{0} desconectouse",
+ "Default": "Por defecto",
+ "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index e5707e78c..ef8070503 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
"NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
- "NotificationOptionInstallationFailed": "Telepítési hiba",
+ "NotificationOptionInstallationFailed": "Telepítés sikertelen",
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
"NotificationOptionPluginError": "Bővítmény hiba",
"NotificationOptionPluginInstalled": "Bővítmény telepítve",
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index 105ef7be9..ba3513870 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -1,7 +1,7 @@
{
"Albums": "Album",
"AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
- "AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
+ "AppDeviceValues": "Aplikasi : {0}, Perangkat : {1}",
"LabelRunningTimeValue": "Waktu berjalan: {0}",
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 345d41e9e..b5a7fa5b8 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLogDescription": "Radera aktivitets logg inlägg som är äldre än definerad ålder.",
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad",
- "Forced": "Tvingad"
+ "Forced": "Tvingad",
+ "Default": "Standard"
}
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index c26ccfd88..7bc9f0a7e 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -1,8 +1,10 @@
#nullable enable
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
@@ -11,9 +13,11 @@ using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
+using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -33,6 +37,21 @@ namespace Emby.Server.Implementations.Plugins
private readonly IList<LocalPlugin> _plugins;
private readonly Version _minimumVersion;
+ private IHttpClientFactory? _httpClientFactory;
+
+ private IHttpClientFactory HttpClientFactory
+ {
+ get
+ {
+ if (_httpClientFactory == null)
+ {
+ _httpClientFactory = _appHost.Resolve<IHttpClientFactory>();
+ }
+
+ return _httpClientFactory;
+ }
+ }
+
/// <summary>
/// Initializes a new instance of the <see cref="PluginManager"/> class.
/// </summary>
@@ -332,32 +351,74 @@ namespace Emby.Server.Implementations.Plugins
ChangePluginState(plugin, PluginStatus.Malfunctioned);
}
- /// <summary>
- /// Saves the manifest back to disk.
- /// </summary>
- /// <param name="manifest">The <see cref="PluginManifest"/> to save.</param>
- /// <param name="path">The path where to save the manifest.</param>
- /// <returns>True if successful.</returns>
+ /// <inheritdoc/>
public bool SaveManifest(PluginManifest manifest, string path)
{
- if (manifest == null)
- {
- return false;
- }
-
try
{
var data = JsonSerializer.Serialize(manifest, _jsonOptions);
File.WriteAllText(Path.Combine(path, "meta.json"), data);
return true;
}
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception e)
-#pragma warning restore CA1031 // Do not catch general exception types
+ catch (ArgumentException e)
+ {
+ _logger.LogWarning(e, "Unable to save plugin manifest due to invalid value. {Path}", path);
+ return false;
+ }
+ }
+
+ /// <inheritdoc/>
+ public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path)
+ {
+ if (packageInfo == null)
{
- _logger.LogWarning(e, "Unable to save plugin manifest. {Path}", path);
return false;
}
+
+ var versionInfo = packageInfo.Versions.First(v => v.Version == version.ToString());
+ var imagePath = string.Empty;
+
+ if (!string.IsNullOrEmpty(packageInfo.ImageUrl))
+ {
+ var url = new Uri(packageInfo.ImageUrl);
+ imagePath = Path.Join(path, url.Segments[^1]);
+
+ await using var fileStream = File.OpenWrite(imagePath);
+
+ try
+ {
+ await using var downloadStream = await HttpClientFactory
+ .CreateClient(NamedClient.Default)
+ .GetStreamAsync(url)
+ .ConfigureAwait(false);
+
+ await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
+ imagePath = string.Empty;
+ }
+ }
+
+ var manifest = new PluginManifest
+ {
+ Category = packageInfo.Category,
+ Changelog = versionInfo.Changelog ?? string.Empty,
+ Description = packageInfo.Description,
+ Id = new Guid(packageInfo.Id),
+ Name = packageInfo.Name,
+ Overview = packageInfo.Overview,
+ Owner = packageInfo.Owner,
+ TargetAbi = versionInfo.TargetAbi ?? string.Empty,
+ Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
+ Version = versionInfo.Version,
+ Status = PluginStatus.Active,
+ AutoUpdate = true,
+ ImagePath = imagePath
+ };
+
+ return SaveManifest(manifest, path);
}
/// <summary>
@@ -410,7 +471,7 @@ namespace Emby.Server.Implementations.Plugins
if (plugin == null)
{
// Create a dummy record for the providers.
- // TODO: remove this code, if all provided have been released as separate plugins.
+ // TODO: remove this code once all provided have been released as separate plugins.
plugin = new LocalPlugin(
instance.AssemblyFilePath,
true,
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index abcb4313f..7af52ea65 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -192,17 +192,12 @@ namespace Emby.Server.Implementations.Updates
var version = package.Versions[i];
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
- // Update the manifests, if anything changes.
if (plugin != null)
{
- if (!string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal))
- {
- plugin.Manifest.TargetAbi = version.TargetAbi ?? string.Empty;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
+ await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
}
- // Remove versions with a target abi that is greater then the current application version.
+ // Remove versions with a target ABI greater then the current application version.
if (Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi)
{
package.Versions.RemoveAt(i);
@@ -294,7 +289,8 @@ namespace Emby.Server.Implementations.Updates
Name = package.Name,
Version = v.VersionNumber,
SourceUrl = v.SourceUrl,
- Checksum = v.Checksum
+ Checksum = v.Checksum,
+ PackageInfo = package
};
}
}
@@ -571,24 +567,16 @@ namespace Emby.Server.Implementations.Updates
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
+ await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
_pluginManager.ImportPluginFrom(targetDir);
}
private async Task<bool> InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
{
- // Set last update time if we were installed before
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
- if (plugin != null)
- {
- plugin.Manifest.Timestamp = DateTime.UtcNow;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
- // Do the install
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
-
- // Do plugin-specific processing
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
return plugin != null;
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index 94995650c..328efea26 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddVirtualFolder(
[FromQuery] string? name,
- [FromQuery] string? collectionType,
+ [FromQuery] CollectionTypeOptions? collectionType,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
[FromQuery] bool refreshLibrary = false)
diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs
index 8bb97937c..51fcb6d9a 100644
--- a/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Net;
@@ -691,11 +692,11 @@ namespace Jellyfin.Networking.Manager
/// Checks the string to see if it matches any interface names.
/// </summary>
/// <param name="token">String to check.</param>
- /// <param name="index">Interface index number.</param>
+ /// <param name="index">Interface index numbers that match.</param>
/// <returns><c>true</c> if an interface name matches the token, <c>False</c> otherwise.</returns>
- private bool IsInterface(string token, out int index)
+ private bool TryGetInterfaces(string token, [NotNullWhen(true)] out List<int>? index)
{
- index = -1;
+ index = null;
// Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1.
// Null check required here for automated testing.
@@ -712,13 +713,13 @@ namespace Jellyfin.Networking.Manager
if ((!partial && string.Equals(interfc, token, StringComparison.OrdinalIgnoreCase))
|| (partial && interfc.StartsWith(token, true, CultureInfo.InvariantCulture)))
{
- index = interfcIndex;
- return true;
+ index ??= new List<int>();
+ index.Add(interfcIndex);
}
}
}
- return false;
+ return index != null;
}
/// <summary>
@@ -730,14 +731,14 @@ namespace Jellyfin.Networking.Manager
{
// Is it the name of an interface (windows) eg, Wireless LAN adapter Wireless Network Connection 1.
// Null check required here for automated testing.
- if (IsInterface(token, out int index))
+ if (TryGetInterfaces(token, out var indices))
{
_logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token);
- // Replace interface tags with the interface IP's.
+ // Replace all the interface tags with the interface IP's.
foreach (IPNetAddress iface in _interfaceAddresses)
{
- if (Math.Abs(iface.Tag) == index
+ if (indices.Contains(Math.Abs(iface.Tag))
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
{
@@ -916,11 +917,19 @@ namespace Jellyfin.Networking.Manager
// Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded.
if (config.IgnoreVirtualInterfaces)
{
- var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',');
- var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length];
- Array.Copy(lanAddresses, newList, lanAddresses.Length);
- Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length);
- lanAddresses = newList;
+ // each virtual interface name must be pre-pended with the exclusion symbol !
+ var virtualInterfaceNames = config.VirtualInterfaceNames.Split(',').Select(p => "!" + p).ToArray();
+ if (lanAddresses.Length > 0)
+ {
+ var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length];
+ Array.Copy(lanAddresses, newList, lanAddresses.Length);
+ Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length);
+ lanAddresses = newList;
+ }
+ else
+ {
+ lanAddresses = virtualInterfaceNames;
+ }
}
// Read and parse bind addresses and exclusions, removing ones that don't exist.
diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs
index 4cede9ab1..4a7c70190 100644
--- a/MediaBrowser.Common/Net/IPHost.cs
+++ b/MediaBrowser.Common/Net/IPHost.cs
@@ -128,62 +128,63 @@ namespace MediaBrowser.Common.Net
/// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
public static bool TryParse(string host, out IPHost hostObj)
{
- if (!string.IsNullOrEmpty(host))
+ if (string.IsNullOrWhiteSpace(host))
{
- // See if it's an IPv6 with port address e.g. [::1]:120.
- int i = host.IndexOf("]:", StringComparison.OrdinalIgnoreCase);
- if (i != -1)
- {
- return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
- }
- else
- {
- // See if it's an IPv6 in [] with no port.
- i = host.IndexOf(']', StringComparison.OrdinalIgnoreCase);
- if (i != -1)
- {
- return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
- }
+ hostObj = IPHost.None;
+ return false;
+ }
- // Is it a host or IPv4 with port?
- string[] hosts = host.Split(':');
+ // See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
+ int i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase);
+ if (i != -1)
+ {
+ return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
+ }
- if (hosts.Length > 2)
- {
- hostObj = new IPHost(string.Empty, IPAddress.None);
- return false;
- }
+ if (IPNetAddress.TryParse(host, out var netAddress))
+ {
+ // Host name is an ip address, so fake resolve.
+ hostObj = new IPHost(host, netAddress.Address);
+ return true;
+ }
- // Remove port from IPv4 if it exists.
- host = hosts[0];
+ // Is it a host, IPv4/6 with/out port?
+ string[] hosts = host.Split(':');
- if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
- {
- hostObj = new IPHost(host, new IPAddress(Ipv4Loopback));
- return true;
- }
+ if (hosts.Length <= 2)
+ {
+ // This is either a hostname: port, or an IP4:port.
+ host = hosts[0];
- if (IPNetAddress.TryParse(host, out IPNetAddress netIP))
- {
- // Host name is an ip address, so fake resolve.
- hostObj = new IPHost(host, netIP.Address);
- return true;
- }
+ if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
+ {
+ hostObj = new IPHost(host);
+ return true;
}
- // Only thing left is to see if it's a host string.
- if (!string.IsNullOrEmpty(host))
+ if (IPAddress.TryParse(host, out var netIP))
{
- // Use regular expression as CheckHostName isn't RFC5892 compliant.
- // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
- Regex re = new Regex(@"^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
- if (re.Match(host).Success)
- {
- hostObj = new IPHost(host);
- return true;
- }
+ // Host name is an ip address, so fake resolve.
+ hostObj = new IPHost(host, netIP);
+ return true;
}
}
+ else
+ {
+ // Invalid host name, as it cannot contain :
+ hostObj = new IPHost(string.Empty, IPAddress.None);
+ return false;
+ }
+
+ // Use regular expression as CheckHostName isn't RFC5892 compliant.
+ // Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
+ string pattern = @"(?im)^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$";
+
+ if (Regex.IsMatch(host, pattern))
+ {
+ hostObj = new IPHost(host);
+ return true;
+ }
hostObj = IPHost.None;
return false;
@@ -344,10 +345,14 @@ namespace MediaBrowser.Common.Net
{
output += "Any Address,";
}
- else
+ else if (i.AddressFamily == AddressFamily.InterNetwork)
{
output += $"{i}/32,";
}
+ else
+ {
+ output += $"{i}/128,";
+ }
}
output = output[0..^1];
diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs
index 3da34d8bb..fc2fcb517 100644
--- a/MediaBrowser.Common/Plugins/IPluginManager.cs
+++ b/MediaBrowser.Common/Plugins/IPluginManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
+using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common.Plugins
@@ -45,6 +46,15 @@ namespace MediaBrowser.Common.Plugins
bool SaveManifest(PluginManifest manifest, string path);
/// <summary>
+ /// Generates a manifest from repository data.
+ /// </summary>
+ /// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
+ /// <param name="version">Version to be installed.</param>
+ /// <param name="path">The path where to save the manifest.</param>
+ /// <returns>True if successful.</returns>
+ Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path);
+
+ /// <summary>
/// Imports plugin details from a folder.
/// </summary>
/// <param name="folder">Folder of the plugin.</param>
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 92b2d43ce..6378625e8 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -53,6 +53,11 @@ namespace MediaBrowser.Controller
string FriendlyName { get; }
/// <summary>
+ /// Gets the configured published server url.
+ /// </summary>
+ Uri PublishedServerUrl { get; }
+
+ /// <summary>
/// Gets the system info.
/// </summary>
/// <param name="source">The originator of the request.</param>
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 6700761fc..80fcf71f3 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.Library
Guid GetMusicGenreId(string name);
- Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary);
+ Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary);
Task RemoveVirtualFolder(string name, bool refreshLibrary);
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 45872b5c0..a52019384 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -209,6 +209,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_ffmpegPath = path;
EncoderLocation = location;
+ return true;
}
else
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 61daf50b3..3d6b4f98a 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
- <PackageReference Include="libse" Version="3.5.8" />
+ <PackageReference Include="libse" Version="3.6.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index a9d118ef5..d19538730 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -165,33 +165,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream,
CancellationToken cancellationToken)
{
- var inputFile = mediaSource.Path;
+ var fileInfo = await GetReadableFile(mediaSource, subtitleStream, cancellationToken).ConfigureAwait(false);
- var protocol = mediaSource.Protocol;
- if (subtitleStream.IsExternal)
- {
- protocol = _mediaSourceManager.GetPathProtocol(subtitleStream.Path);
- }
-
- var fileInfo = await GetReadableFile(mediaSource.Path, inputFile, mediaSource, subtitleStream, cancellationToken).ConfigureAwait(false);
-
- var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
+ var stream = await GetSubtitleStream(fileInfo, cancellationToken).ConfigureAwait(false);
return (stream, fileInfo.Format);
}
- private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
+ private async Task<Stream> GetSubtitleStream(SubtitleInfo fileInfo, CancellationToken cancellationToken)
{
- if (requiresCharset)
+ if (fileInfo.IsExternal)
{
- using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
+ using (var stream = await GetStream(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false))
{
var result = CharsetDetector.DetectFromStream(stream).Detected;
stream.Position = 0;
if (result != null)
{
- _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path);
+ _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, fileInfo.Path);
using var reader = new StreamReader(stream, result.Encoding);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
@@ -201,12 +193,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- return File.OpenRead(path);
+ return File.OpenRead(fileInfo.Path);
}
private async Task<SubtitleInfo> GetReadableFile(
- string mediaPath,
- string inputFile,
MediaSourceInfo mediaSource,
MediaStream subtitleStream,
CancellationToken cancellationToken)
@@ -238,9 +228,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
// Extract
- var outputPath = GetSubtitleCachePath(mediaPath, mediaSource, subtitleStream.Index, "." + outputFormat);
+ var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
- await ExtractTextSubtitle(inputFile, mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
+ await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -252,13 +242,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (GetReader(currentFormat, false) == null)
{
// Convert
- var outputPath = GetSubtitleCachePath(mediaPath, mediaSource, subtitleStream.Index, ".srt");
+ var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
}
+ if (subtitleStream.IsExternal)
+ {
+ return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true);
+ }
+
return new SubtitleInfo(subtitleStream.Path, mediaSource.Protocol, currentFormat, true);
}
@@ -501,7 +496,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary>
/// Extracts the text subtitle.
/// </summary>
- /// <param name="inputFile">The input file.</param>
/// <param name="mediaSource">The mediaSource.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="outputCodec">The output codec.</param>
@@ -510,7 +504,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <returns>Task.</returns>
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
private async Task ExtractTextSubtitle(
- string inputFile,
MediaSourceInfo mediaSource,
int subtitleStreamIndex,
string outputCodec,
@@ -526,7 +519,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!File.Exists(outputPath))
{
await ExtractTextSubtitleInternal(
- _mediaEncoder.GetInputArgument(inputFile, mediaSource),
+ _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
subtitleStreamIndex,
outputCodec,
outputPath,
@@ -692,15 +685,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private string GetSubtitleCachePath(string mediaPath, MediaSourceInfo mediaSource, int subtitleStreamIndex, string outputSubtitleExtension)
+ private string GetSubtitleCachePath(MediaSourceInfo mediaSource, int subtitleStreamIndex, string outputSubtitleExtension)
{
if (mediaSource.Protocol == MediaProtocol.File)
{
var ticksParam = string.Empty;
- var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaSource.Path);
- ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan<char> filename = (mediaSource.Path + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
var prefix = filename.Slice(0, 1);
@@ -708,7 +701,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
else
{
- ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan<char> filename = (mediaSource.Path + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
var prefix = filename.Slice(0, 1);
diff --git a/MediaBrowser.Model/Configuration/AccessSchedule.cs b/MediaBrowser.Model/Configuration/AccessSchedule.cs
deleted file mode 100644
index 7bd355449..000000000
--- a/MediaBrowser.Model/Configuration/AccessSchedule.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Jellyfin.Data.Enums;
-
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Configuration
-{
- public class AccessSchedule
- {
- /// <summary>
- /// Gets or sets the day of week.
- /// </summary>
- /// <value>The day of week.</value>
- public DynamicDayOfWeek DayOfWeek { get; set; }
-
- /// <summary>
- /// Gets or sets the start hour.
- /// </summary>
- /// <value>The start hour.</value>
- public double StartHour { get; set; }
-
- /// <summary>
- /// Gets or sets the end hour.
- /// </summary>
- /// <value>The end hour.</value>
- public double EndHour { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Entities/CollectionTypeOptions.cs b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs
new file mode 100644
index 000000000..e1894d84a
--- /dev/null
+++ b/MediaBrowser.Model/Entities/CollectionTypeOptions.cs
@@ -0,0 +1,16 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Entities
+{
+ public enum CollectionTypeOptions
+ {
+ Movies = 0,
+ TvShows = 1,
+ Music = 2,
+ MusicVideos = 3,
+ HomeVideos = 4,
+ BoxSets = 5,
+ Books = 6,
+ Mixed = 7
+ }
+}
diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
index 1b0e59240..ea3df3726 100644
--- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
+++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Model.Entities
/// Gets or sets the type of the collection.
/// </summary>
/// <value>The type of the collection.</value>
- public string CollectionType { get; set; }
+ public CollectionTypeOptions? CollectionType { get; set; }
public LibraryOptions LibraryOptions { get; set; }
diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs
index eebe1a903..cc600de9d 100644
--- a/MediaBrowser.Model/Updates/InstallationInfo.cs
+++ b/MediaBrowser.Model/Updates/InstallationInfo.cs
@@ -1,4 +1,5 @@
#nullable disable
+
using System;
using System.Text.Json.Serialization;
@@ -45,5 +46,11 @@ namespace MediaBrowser.Model.Updates
/// </summary>
/// <value>The checksum.</value>
public string Checksum { get; set; }
+
+ /// <summary>
+ /// Gets or sets package information for the installation.
+ /// </summary>
+ /// <value>The package information.</value>
+ public PackageInfo PackageInfo { get; set; }
}
}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index c6a8ffbd0..873ff0ab4 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -18,11 +18,11 @@
<PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 47e235441..278f34109 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index fb18a8a8d..b02a68a3d 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 7e4a2efad..850db1c75 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index ec9cc656a..e729dbb09 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index ebdad7c72..b6d2c63bd 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 247e6aa7a..99185c975 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
index 36ff93a45..fd77397ba 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/Jellyfin.Networking.Tests.csproj
@@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>
<!-- Code Analyzers-->
diff --git a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
index b7c1510d2..9f928ded1 100644
--- a/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
+++ b/tests/Jellyfin.Networking.Tests/NetworkTesting/NetworkParseTests.cs
@@ -13,34 +13,6 @@ namespace Jellyfin.Networking.Tests
{
public class NetworkParseTests
{
- /// <summary>
- /// Tries to identify the string and return an object of that class.
- /// </summary>
- /// <param name="addr">String to parse.</param>
- /// <param name="result">IPObject to return.</param>
- /// <returns>True if the value parsed successfully.</returns>
- private static bool TryParse(string addr, out IPObject result)
- {
- if (!string.IsNullOrEmpty(addr))
- {
- // Is it an IP address
- if (IPNetAddress.TryParse(addr, out IPNetAddress nw))
- {
- result = nw;
- return true;
- }
-
- if (IPHost.TryParse(addr, out IPHost h))
- {
- result = h;
- return true;
- }
- }
-
- result = IPNetAddress.None;
- return false;
- }
-
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
{
var configManager = new Mock<IConfigurationManager>
@@ -52,15 +24,20 @@ namespace Jellyfin.Networking.Tests
}
/// <summary>
- /// Checks the ability to ignore interfaces
+ /// Checks the ability to ignore virtual interfaces.
/// </summary>
/// <param name="interfaces">Mock network setup, in the format (IP address, interface index, interface name) | .... </param>
/// <param name="lan">LAN addresses.</param>
/// <param name="value">Bind addresses that are excluded.</param>
[Theory]
+ // All valid
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.0/24", "[192.168.1.208/24,200.200.200.200/24]")]
+ // eth16 only
[InlineData("192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
- [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.1.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.208/24]")]
+ // All interfaces excluded.
+ [InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
+ // vEthernet1 and vEthernet212 should be excluded.
+ [InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
{
var conf = new NetworkConfiguration()
@@ -118,11 +95,33 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
[InlineData("fe80::7add:12ff:febb:c67b%16")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
+ [InlineData("192.168.1.2/255.255.255.0")]
+ [InlineData("192.168.1.2/24")]
+ public void ValidHostStrings(string address)
+ {
+ Assert.True(IPHost.TryParse(address, out _));
+ }
+
+ /// <summary>
+ /// Checks IP address formats.
+ /// </summary>
+ /// <param name="address"></param>
+ [Theory]
+ [InlineData("127.0.0.1")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
+ [InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
+ [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
+ [InlineData("fe80::7add:12ff:febb:c67b%16:123")]
+ [InlineData("[fe80::7add:12ff:febb:c67b%16]")]
[InlineData("192.168.1.2/255.255.255.0")]
[InlineData("192.168.1.2/24")]
public void ValidIPStrings(string address)
{
- Assert.True(TryParse(address, out _));
+ Assert.True(IPNetAddress.TryParse(address, out _));
}
@@ -138,7 +137,8 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")]
public void InvalidAddressString(string address)
{
- Assert.False(TryParse(address, out _));
+ Assert.False(IPNetAddress.TryParse(address, out _));
+ Assert.False(IPHost.TryParse(address, out _));
}
@@ -172,11 +172,11 @@ namespace Jellyfin.Networking.Tests
"[]")]
[InlineData(
"192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10",
- "[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]",
+ "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]",
"[192.158.1.2/16,127.0.0.1/32]",
"[10.10.10.10/32]",
"[10.10.10.10/32]",
- "[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
+ "[192.158.0.0/16,127.0.0.1/32,::1/128,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
[InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8",
"[192.158.1.2/16,192.169.1.2/8]",
"[192.158.1.2/16,192.169.1.2/8]",
@@ -333,8 +333,8 @@ namespace Jellyfin.Networking.Tests
public void TestSubnetContains(string network, string ip)
{
- Assert.True(TryParse(network, out IPObject? networkObj));
- Assert.True(TryParse(ip, out IPObject? ipObj));
+ Assert.True(IPNetAddress.TryParse(network, out var networkObj));
+ Assert.True(IPNetAddress.TryParse(ip, out var ipObj));
Assert.True(networkObj.Contains(ipObj));
}
@@ -468,7 +468,7 @@ namespace Jellyfin.Networking.Tests
// User on internal network, no binding specified - so result is the 1st internal.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
- // User on external network, internal binding only - so asumption is a proxy forward, return external override.
+ // User on external network, internal binding only - so assumption is a proxy forward, return external override.
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
// User on external network, no binding - so result is the 1st external which is overriden.
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 14b8cbd54..1ad8171be 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -22,8 +22,8 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index bc076caed..d6aab3f85 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -14,8 +14,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
- <PackageReference Include="Moq" Version="4.16.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+ <PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3" />