Compare commits

..

7 Commits

Author SHA1 Message Date
Jellyfin Release Bot
4187c6f620 Bump version to 10.11.2 2025-11-02 21:28:56 -05:00
Tim Eisele
e7dbb3afec Skip too large extracted season numbers (#15326) 2025-11-02 09:11:48 -07:00
vinnyspb
f994dd6211 Update file size when refreshing metadata (#15325) 2025-11-01 14:18:19 -06:00
Cody Robibero
da254ee968 return instead of break, add check to more migrations (#15322) 2025-11-01 14:17:22 -06:00
Bill Thornton
4ad3141875 Update password reset to always return the same response structure (#15254) 2025-11-01 14:17:09 -06:00
evanreichard
b5f0199a25 fix: in optimistic locking, key off table is locked (#15328) 2025-11-01 14:15:26 -06:00
Nyanmisaka
6bf88c049e Ignore initial delay in audio-only containers (#15247) 2025-10-29 20:40:28 -06:00
20 changed files with 112 additions and 49 deletions

View File

@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -113,8 +113,10 @@ namespace Emby.Naming.TV
var numberString = match.Groups["seasonnumber"];
if (numberString.Success)
{
var seasonNumber = int.Parse(numberString.Value, CultureInfo.InvariantCulture);
return (seasonNumber, true);
if (int.TryParse(numberString.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var seasonNumber))
{
return (seasonNumber, true);
}
}
return (null, false);

View File

@@ -1625,8 +1625,11 @@ public class DynamicHlsController : BaseJellyfinApiController
var useLegacySegmentOption = _mediaEncoder.EncoderVersion < _minFFmpegHlsSegmentOptions;
// fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont";
if (state.VideoStream is not null && state.IsOutputVideo)
{
// fMP4 needs this flag to write the audio packet DTS/PTS including the initial delay into MOOF::TRAF::TFDT
hlsArguments += $" {(useLegacySegmentOption ? "-hls_ts_options" : "-hls_segment_options")} movflags=+frag_discont";
}
segmentFormat = "fmp4" + outputFmp4HeaderArg;
}

View File

@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text.Json;
@@ -92,33 +93,38 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc />
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork)
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork)
{
byte[] bytes = new byte[4];
RandomNumberGenerator.Fill(bytes);
string pin = BitConverter.ToString(bytes);
DateTime expireTime = DateTime.UtcNow.AddMinutes(30);
string filePath = _passwordResetFileBase + user.Id + ".json";
SerializablePasswordReset spr = new SerializablePasswordReset
{
ExpirationDate = expireTime,
Pin = pin,
PinFile = filePath,
UserName = user.Username
};
var usernameHash = enteredUsername.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
var pinFile = _passwordResetFileBase + usernameHash + ".json";
FileStream fileStream = AsyncFile.Create(filePath);
await using (fileStream.ConfigureAwait(false))
if (user is not null && isInNetwork)
{
await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
byte[] bytes = new byte[4];
RandomNumberGenerator.Fill(bytes);
string pin = BitConverter.ToString(bytes);
SerializablePasswordReset spr = new SerializablePasswordReset
{
ExpirationDate = expireTime,
Pin = pin,
PinFile = pinFile,
UserName = user.Username
};
FileStream fileStream = AsyncFile.Create(pinFile);
await using (fileStream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
}
}
return new ForgotPasswordResult
{
Action = ForgotPasswordAction.PinCode,
PinExpirationDate = expireTime,
PinFile = filePath
PinFile = pinFile
};
}

View File

@@ -508,23 +508,18 @@ namespace Jellyfin.Server.Implementations.Users
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
{
var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername);
var passwordResetProvider = GetPasswordResetProvider(user);
var result = await passwordResetProvider
.StartForgotPasswordProcess(user, enteredUsername, isInNetwork)
.ConfigureAwait(false);
if (user is not null && isInNetwork)
{
var passwordResetProvider = GetPasswordResetProvider(user);
var result = await passwordResetProvider
.StartForgotPasswordProcess(user, isInNetwork)
.ConfigureAwait(false);
await UpdateUserAsync(user).ConfigureAwait(false);
return result;
}
return new ForgotPasswordResult
{
Action = ForgotPasswordAction.InNetworkRequired,
PinFile = string.Empty
};
return result;
}
/// <inheritdoc/>
@@ -760,8 +755,13 @@ namespace Jellyfin.Server.Implementations.Users
return GetAuthenticationProviders(user)[0];
}
private IPasswordResetProvider GetPasswordResetProvider(User user)
private IPasswordResetProvider GetPasswordResetProvider(User? user)
{
if (user is null)
{
return _defaultPasswordResetProvider;
}
return GetPasswordResetProviders(user)[0];
}

View File

@@ -71,7 +71,7 @@ namespace Jellyfin.Server.Migrations.Routines
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'ActivityLog' doesn't exist in {ActivityLogPath}, nothing to migrate", activityLogPath);
break;
return;
}
}

View File

@@ -50,9 +50,28 @@ namespace Jellyfin.Server.Migrations.Routines
public void Perform()
{
var dataPath = _appPaths.DataPath;
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
var dbFilePath = Path.Combine(dataPath, DbFilename);
if (!File.Exists(dbFilePath))
{
_logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
return;
}
using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
{
connection.Open();
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Tokens';");
foreach (var row in tableQuery)
{
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'Tokens' doesn't exist in {Path}, nothing to migrate", dbFilePath);
return;
}
}
using var dbContext = _dbProvider.CreateDbContext();
var authenticatedDevices = connection.Query("SELECT * FROM Tokens");

View File

@@ -78,9 +78,27 @@ namespace Jellyfin.Server.Migrations.Routines
var displayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var customDisplayPrefs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var dbFilePath = Path.Combine(_paths.DataPath, DbFilename);
if (!File.Exists(dbFilePath))
{
_logger.LogWarning("{Path} doesn't exist, nothing to migrate", dbFilePath);
return;
}
using (var connection = new SqliteConnection($"Filename={dbFilePath}"))
{
connection.Open();
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='userdisplaypreferences';");
foreach (var row in tableQuery)
{
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'userdisplaypreferences' doesn't exist in {Path}, nothing to migrate", dbFilePath);
return;
}
}
using var dbContext = _provider.CreateDbContext();
var results = connection.Query("SELECT * FROM userdisplaypreferences");

View File

@@ -75,7 +75,7 @@ public class MigrateUserDb : IMigrationRoutine
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbPath);
break;
return;
}
}

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -15,11 +13,12 @@ namespace MediaBrowser.Controller.Authentication
bool IsEnabled { get; }
Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
Task<ForgotPasswordResult> StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork);
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
}
#nullable disable
public class PasswordPinCreationResult
{
public string PinFile { get; set; }

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -1,11 +1,15 @@
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Model.Users
{
public enum ForgotPasswordAction
{
[Obsolete("Returning different actions represents a security concern.")]
ContactAdmin = 0,
PinCode = 1,
[Obsolete("Returning different actions represents a security concern.")]
InNetworkRequired = 2
}
}

View File

@@ -229,6 +229,11 @@ namespace MediaBrowser.Providers.Manager
if (file is not null)
{
item.DateModified = file.LastWriteTimeUtc;
if (!file.IsDirectory)
{
item.Size = file.Length;
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("10.11.1")]
[assembly: AssemblyFileVersion("10.11.1")]
[assembly: AssemblyVersion("10.11.2")]
[assembly: AssemblyFileVersion("10.11.2")]

View File

@@ -52,10 +52,14 @@ public class OptimisticLockBehavior : IEntityFrameworkCoreLockingBehavior
_logger = logger;
_writePolicy = Policy
.HandleInner<Exception>(e => e.Message.Contains("database is locked", StringComparison.InvariantCultureIgnoreCase))
.HandleInner<Exception>(e =>
e.Message.Contains("database is locked", StringComparison.InvariantCultureIgnoreCase) ||
e.Message.Contains("database table is locked", StringComparison.InvariantCultureIgnoreCase))
.WaitAndRetry(sleepDurations.Length, backoffProvider, RetryHandle);
_writeAsyncPolicy = Policy
.HandleInner<Exception>(e => e.Message.Contains("database is locked", StringComparison.InvariantCultureIgnoreCase))
.HandleInner<Exception>(e =>
e.Message.Contains("database is locked", StringComparison.InvariantCultureIgnoreCase) ||
e.Message.Contains("database table is locked", StringComparison.InvariantCultureIgnoreCase))
.WaitAndRetryAsync(sleepDurations.Length, backoffProvider, RetryHandle);
void RetryHandle(Exception exception, TimeSpan timespan, int retryNo, Context context)

View File

@@ -15,7 +15,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.11.1</VersionPrefix>
<VersionPrefix>10.11.2</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -66,6 +66,9 @@ public class SeasonPathParserTests
[InlineData("/Drive/SPECIALS", "/Drive", 0, true)]
[InlineData("/Drive/Episode 1 Season 2", "/Drive", null, false)]
[InlineData("/Drive/Episode 1 SEASON 2", "/Drive", null, false)]
[InlineData("/media/YouTube/Devyn Johnston/2024-01-24 4070 Ti SUPER in under 7 minutes", "/media/YouTube/Devyn Johnston", null, false)]
[InlineData("/media/YouTube/Devyn Johnston/2025-01-28 5090 vs 2 SFF Cases", "/media/YouTube/Devyn Johnston", null, false)]
[InlineData("/Drive/202401244070", "/Drive", null, false)]
public void GetSeasonNumberFromPathTest(string path, string? parentPath, int? seasonNumber, bool isSeasonDirectory)
{
var result = SeasonPathParser.Parse(path, parentPath, true, true);