aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-openapi.yml69
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs17
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs14
-rw-r--r--Jellyfin.Server/Program.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs2
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs10
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs33
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs15
10 files changed, 132 insertions, 33 deletions
diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml
index 24e738475..d7f1e3076 100644
--- a/.github/workflows/ci-openapi.yml
+++ b/.github/workflows/ci-openapi.yml
@@ -3,6 +3,8 @@ on:
push:
branches:
- master
+ tags:
+ - 'v*'
pull_request_target:
permissions: {}
@@ -138,10 +140,11 @@ jobs:
No changes to OpenAPI specification found. See history of this comment for previous changes.
- publish:
+ publish-unstable:
name: OpenAPI - Publish Unstable Spec
if: |
github.event_name != 'pull_request_target' &&
+ ${{ ! startsWith(github.ref, 'refs/tags/v') }} &&
contains(github.repository_owner, 'jellyfin')
runs-on: ubuntu-latest
needs:
@@ -201,3 +204,67 @@ jobs:
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
fi
) 200>/run/workflows/openapi-unstable.lock
+
+ publish-stable:
+ name: OpenAPI - Publish Stable Spec
+ if: |
+ startsWith(github.ref, 'refs/tags/v') &&
+ contains(github.repository_owner, 'jellyfin')
+ runs-on: ubuntu-latest
+ needs:
+ - openapi-head
+ steps:
+ - name: Set version number
+ id: version
+ run: |-
+ echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
+ - name: Download openapi-head
+ uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
+ with:
+ name: openapi-head
+ path: openapi-head
+ - name: Upload openapi.json (stable) to repository server
+ uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
+ with:
+ host: "${{ secrets.REPO_HOST }}"
+ username: "${{ secrets.REPO_USER }}"
+ key: "${{ secrets.REPO_KEY }}"
+ source: openapi-head/openapi.json
+ strip_components: 1
+ target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
+ - name: Move openapi.json (stable) into place
+ uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
+ with:
+ host: "${{ secrets.REPO_HOST }}"
+ username: "${{ secrets.REPO_USER }}"
+ key: "${{ secrets.REPO_KEY }}"
+ debug: false
+ script_stop: false
+ script: |
+ if ! test -d /run/workflows; then
+ sudo mkdir -p /run/workflows
+ sudo chown ${{ secrets.REPO_USER }} /run/workflows
+ fi
+ (
+ flock -x -w 300 200 || exit 1
+ TGT_DIR="/srv/repository/main/openapi"
+ LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
+ # If new and previous spec don't differ (diff retcode 0), remove incoming and finish
+ if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
+ rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
+ exit 0
+ fi
+ # Move new spec into place
+ sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
+ # Delete previous jellyfin-openapi-stable_previous.json
+ sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
+ # Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
+ sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
+ # Create new jellyfin-openapi-stable.json symlink
+ sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
+ # Check that the previous openapi stable spec link is correct
+ if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
+ sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
+ sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
+ fi
+ ) 200>/run/workflows/openapi-stable.lock
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 6add7e0b3..c394b25bd 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -422,7 +422,7 @@ namespace Emby.Server.Implementations
// Initialize runtime stat collection
if (ConfigurationManager.Configuration.EnableMetrics)
{
- DotNetRuntimeStatsBuilder.Default().StartCollecting();
+ _disposableParts.Add(DotNetRuntimeStatsBuilder.Default().StartCollecting());
}
var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 67854a2a7..d5afac266 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -80,12 +80,14 @@ namespace Emby.Server.Implementations.IO
public virtual string MakeAbsolutePath(string folderPath, string filePath)
{
// path is actually a stream
- if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
+ if (string.IsNullOrWhiteSpace(filePath))
{
return filePath;
}
- if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
+ var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\');
+
+ if (isAbsolutePath)
{
// absolute local path
return filePath;
@@ -97,17 +99,10 @@ namespace Emby.Server.Implementations.IO
return filePath;
}
- var firstChar = filePath[0];
- if (firstChar == '/')
- {
- // for this we don't really know
- return filePath;
- }
-
var filePathSpan = filePath.AsSpan();
- // relative path
- if (firstChar == '\\')
+ // relative path on windows
+ if (filePath[0] == '\\')
{
filePathSpan = filePathSpan.Slice(1);
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index c229f3538..1a69627fa 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -13,7 +13,7 @@
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"External": "External",
- "FailedLoginAttemptWithUserName": "Failed login try from {0}",
+ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites",
"Folders": "Folders",
"Forced": "Forced",
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index bae201c70..ac453a5b0 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -321,7 +321,11 @@ namespace Emby.Server.Implementations.Localization
// Try splitting by : to handle "Germany: FSK-18"
if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
{
- return GetRatingLevel(rating.AsSpan().RightPart(':').ToString());
+ var ratingLevelRightPart = rating.AsSpan().RightPart(':');
+ if (ratingLevelRightPart.Length != 0)
+ {
+ return GetRatingLevel(ratingLevelRightPart.ToString());
+ }
}
// Handle prefix country code to handle "DE-18"
@@ -332,8 +336,12 @@ namespace Emby.Server.Implementations.Localization
// Extract culture from country prefix
var culture = FindLanguageInfo(ratingSpan.LeftPart('-').ToString());
- // Check rating system of culture
- return GetRatingLevel(ratingSpan.RightPart('-').ToString(), culture?.TwoLetterISOLanguageName);
+ var ratingLevelRightPart = ratingSpan.RightPart('-');
+ if (ratingLevelRightPart.Length != 0)
+ {
+ // Check rating system of culture
+ return GetRatingLevel(ratingLevelRightPart.ToString(), culture?.TwoLetterISOLanguageName);
+ }
}
return null;
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index fd7696906..295fb8112 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -185,6 +185,7 @@ namespace Jellyfin.Server
}
catch (Exception ex)
{
+ _restartOnShutdown = false;
_logger.LogCritical(ex, "Error while starting server");
}
finally
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 236e47f95..0f2ec1f8f 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -594,7 +594,7 @@ namespace MediaBrowser.Controller.Entities
}
var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
- var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : 2 * Environment.ProcessorCount;
+ var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : Environment.ProcessorCount;
var actionBlock = new ActionBlock<int>(
async i =>
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 1da44b048..37160b3ba 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -412,7 +412,9 @@ public class NetworkManager : INetworkManager, IDisposable
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
}
- _interfaces = interfaces;
+ // Users may have complex networking configuration that multiple interfaces sharing the same IP address
+ // Only return one IP for binding, and let the OS handle the rest
+ _interfaces = interfaces.DistinctBy(iface => iface.Address).ToList();
}
}
@@ -1017,7 +1019,7 @@ public class NetworkManager : INetworkManager, IDisposable
result = string.Empty;
int count = _interfaces.Count;
- if (count == 1 && (_interfaces[0].Equals(IPAddress.Any) || _interfaces[0].Equals(IPAddress.IPv6Any)))
+ if (count == 1 && (_interfaces[0].Address.Equals(IPAddress.Any) || _interfaces[0].Address.Equals(IPAddress.IPv6Any)))
{
// Ignore IPAny addresses.
count = 0;
@@ -1049,7 +1051,7 @@ public class NetworkManager : INetworkManager, IDisposable
return true;
}
- _logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
+ _logger.LogDebug("{Source}: External request received, no matching external bind address found, trying internal addresses", source);
}
else
{
@@ -1087,7 +1089,7 @@ public class NetworkManager : INetworkManager, IDisposable
if (extResult.Length == 0)
{
result = string.Empty;
- _logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
+ _logger.LogDebug("{Source}: External request received, but no external interface found. Need to route through internal network", source);
return false;
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index d991f5574..95a5b8179 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -20,26 +20,37 @@ namespace Jellyfin.Server.Implementations.Tests.IO
_sut = _fixture.Create<ManagedFileSystem>();
}
- [Theory]
+ [SkippableTheory]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
- public void MakeAbsolutePathCorrectlyHandlesRelativeFilePaths(
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3")]
+ public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnUnixLike(
string folderPath,
string filePath,
string expectedAbsolutePath)
{
+ Skip.If(OperatingSystem.IsWindows());
+
+ var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
+ Assert.Equal(expectedAbsolutePath, generatedPath);
+ }
+
+ [SkippableTheory]
+ [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")]
+ [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")]
+ [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")]
+ [InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3")]
+ public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnWindows(
+ string folderPath,
+ string filePath,
+ string expectedAbsolutePath)
+ {
+ Skip.If(!OperatingSystem.IsWindows());
+
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
- if (OperatingSystem.IsWindows())
- {
- var expectedWindowsPath = expectedAbsolutePath.Replace('/', '\\');
- Assert.Equal(expectedWindowsPath, generatedPath.Split(':')[1]);
- }
- else
- {
- Assert.Equal(expectedAbsolutePath, generatedPath);
- }
+ Assert.Equal(expectedAbsolutePath, generatedPath);
}
[Theory]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
index 0f7f5c194..0a4a836cb 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Emby.Server.Implementations.Localization;
using MediaBrowser.Controller.Configuration;
@@ -158,6 +159,20 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
}
[Theory]
+ [InlineData("-NO RATING SHOWN-")]
+ [InlineData(":NO RATING SHOWN:")]
+ public async Task GetRatingLevel_Split_Success(string value)
+ {
+ var localizationManager = Setup(new ServerConfiguration()
+ {
+ UICulture = "en-US"
+ });
+ await localizationManager.LoadAll();
+
+ Assert.Null(localizationManager.GetRatingLevel(value));
+ }
+
+ [Theory]
[InlineData("Default", "Default")]
[InlineData("HeaderLiveTV", "Live TV")]
public void GetLocalizedString_Valid_Success(string key, string expected)