Compare commits

...

10 Commits

Author SHA1 Message Date
Battseren Badral
573ce9ceaa Translated using Weblate (Mongolian)
Some checks failed
Stale PR Check / Check PRs with merge conflicts (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
OpenAPI / OpenAPI - HEAD (push) Has been cancelled
OpenAPI / OpenAPI - BASE (push) Has been cancelled
OpenAPI / OpenAPI - Difference (push) Has been cancelled
OpenAPI / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI / OpenAPI - Publish Stable Spec (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
Stale Issue Labeler / Check for stale issues (push) Has been cancelled
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2025-10-29 22:47:08 +00:00
Jacky He
f21fe9f95e Translated using Weblate (Chinese (Traditional Han script, Hong Kong))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/
2025-10-29 22:47:08 +00:00
Battseren Badral
f92eca3efb Translated using Weblate (Mongolian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2025-10-29 00:03:00 +00:00
Pascal Wiesmann
7d778d7bef Translated using Weblate (Alemannic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/
2025-10-28 09:23:31 +00:00
JJBlue
21f65e2e27 Backport pull request #15220 from jellyfin/release-10.11.z
Skip extracted files in migration if bad timestamp or no access

Original-merge: a305204cfa

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:31 -04:00
theguymadmax
28b0657608 Backport pull request #15217 from jellyfin/release-10.11.z
Normalize paths in database queries

Original-merge: 75f472e6a7

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:30 -04:00
crobibero
a489942454 Backport pull request #15212 from jellyfin/release-10.11.z
Skip invalid database migration

Original-merge: 2966d27c97

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:29 -04:00
Shadowghost
423c2654c0 Backport pull request #15209 from jellyfin/release-10.11.z
Improve symlink handling

Original-merge: e5656af1f2

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:27 -04:00
crobibero
4dc826644d Backport pull request #15197 from jellyfin/release-10.11.z
Filter plugins by id instead of name

Original-merge: 5691eee4f1

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:26 -04:00
crobibero
0f21222a0c Backport pull request #15196 from jellyfin/release-10.11.z
Skip directory entry when restoring from backup

Original-merge: 0e4031ae52

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
2025-10-27 15:43:25 -04:00
13 changed files with 85 additions and 198 deletions

View File

@@ -252,47 +252,40 @@ namespace Emby.Server.Implementations.IO
{
result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
// if (!result.IsDirectory)
// {
// result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
// }
if (info is FileInfo fileInfo)
{
result.Length = fileInfo.Length;
// Issue #2354 get the size of files behind symbolic links. Also Enum.HasFlag is bad as it boxes!
if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
result.CreationTimeUtc = GetCreationTimeUtc(info);
result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
if (fileInfo.LinkTarget is not null)
{
try
{
using (var fileHandle = File.OpenHandle(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
var targetFileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true);
if (targetFileInfo is not null)
{
result.Length = RandomAccess.GetLength(fileHandle);
result.Exists = targetFileInfo.Exists;
if (result.Exists)
{
result.Length = targetFileInfo.Length;
result.CreationTimeUtc = GetCreationTimeUtc(targetFileInfo);
result.LastWriteTimeUtc = GetLastWriteTimeUtc(targetFileInfo);
}
}
else
{
result.Exists = false;
}
}
catch (FileNotFoundException ex)
{
// Dangling symlinks cannot be detected before opening the file unfortunately...
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
result.Exists = false;
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
}
catch (IOException ex)
{
// IOException generally means the file is not accessible due to filesystem issues
// Catch this exception and mark the file as not exist to ignore it
_logger.LogError(ex, "Reading the file at {Path} failed due to an IO Exception. Marking the file as not existing", fileInfo.FullName);
result.Exists = false;
}
}
else
{
result.Length = fileInfo.Length;
}
}
result.CreationTimeUtc = GetCreationTimeUtc(info);
result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
}
else
{

View File

@@ -51,8 +51,7 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
}
// Fast path in case the ignore files isn't a symlink and is empty
if ((dirIgnoreFile.Attributes & FileAttributes.ReparsePoint) == 0
&& dirIgnoreFile.Length == 0)
if (dirIgnoreFile.LinkTarget is null && dirIgnoreFile.Length == 0)
{
return true;
}
@@ -93,6 +92,12 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
private static string GetFileContent(FileInfo dirIgnoreFile)
{
dirIgnoreFile = (FileInfo?)dirIgnoreFile.ResolveLinkTarget(returnFinalTarget: true) ?? dirIgnoreFile;
if (!dirIgnoreFile.Exists)
{
return string.Empty;
}
using (var reader = dirIgnoreFile.OpenText())
{
return reader.ReadToEnd();

View File

@@ -11,7 +11,7 @@
"Collections": "Sammlungen",
"DeviceOfflineWithName": "{0} wurde getrennt",
"DeviceOnlineWithName": "{0} ist verbunden",
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
"FailedLoginAttemptWithUserName": "Fählgschlagene Ameldeversuech vo {0}",
"Favorites": "Favorite",
"Folders": "Ordner",
"Genres": "Genre",

View File

@@ -109,9 +109,9 @@
"ScheduledTaskStartedWithName": "{0}-г эхлүүлэв",
"ServerNameNeedsToBeRestarted": "{0}-г дахин асаана уу",
"Shows": "Шоу",
"Sync": "Дахин",
"Sync": "Синхрончлох",
"System": "Систем",
"TvShows": "Цуварлууд",
"TvShows": "ТВ нэвтрүүлгүүд",
"Undefined": "Танисангүй",
"User": "Хэрэглэгч",
"UserCreatedWithName": "Хэрэглэгч {0}-г үүсгэлээ",

View File

@@ -137,5 +137,6 @@
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
"CleanupUserDataTask": "用戶資料清理工作"
"CleanupUserDataTask": "用戶資料清理工作",
"CleanupUserDataTaskDescription": "從用戶數據中清除已經被刪除超過 90 日的媒體相關資料。"
}

View File

@@ -223,15 +223,14 @@ namespace Emby.Server.Implementations.Updates
Guid id = default,
Version? specificVersion = null)
{
if (name is not null)
{
availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
}
if (!id.IsEmpty())
{
availablePackages = availablePackages.Where(x => x.Id.Equals(id));
}
else if (name is not null)
{
availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
}
if (specificVersion is not null)
{

View File

@@ -128,7 +128,8 @@ public class BackupService : IBackupService
var targetPath = Path.GetFullPath(Path.Combine(target, Path.GetRelativePath(source, item.FullName)));
if (!sourcePath.StartsWith(fullSourcePath, StringComparison.Ordinal)
|| !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal))
|| !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal)
|| Path.EndsInDirectorySeparator(item.FullName))
{
continue;
}

View File

@@ -1763,7 +1763,8 @@ public sealed class BaseItemRepository
if (!string.IsNullOrWhiteSpace(filter.Path))
{
baseQuery = baseQuery.Where(e => e.Path == filter.Path);
var pathToQuery = GetPathToSave(filter.Path);
baseQuery = baseQuery.Where(e => e.Path == pathToQuery);
}
if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))

View File

@@ -1,151 +0,0 @@
// The MIT License (MIT)
//
// Copyright (c) .NET Foundation and Contributors
//
// All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Jellyfin.Server.Infrastructure
{
/// <inheritdoc />
public class SymlinkFollowingPhysicalFileResultExecutor : PhysicalFileResultExecutor
{
/// <summary>
/// Initializes a new instance of the <see cref="SymlinkFollowingPhysicalFileResultExecutor"/> class.
/// </summary>
/// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param>
public SymlinkFollowingPhysicalFileResultExecutor(ILoggerFactory loggerFactory) : base(loggerFactory)
{
}
/// <inheritdoc />
protected override FileMetadata GetFileInfo(string path)
{
var fileInfo = new FileInfo(path);
var length = fileInfo.Length;
// This may or may not be fixed in .NET 6, but looks like it will not https://github.com/dotnet/aspnetcore/issues/34371
if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
using var fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
length = RandomAccess.GetLength(fileHandle);
}
return new FileMetadata
{
Exists = fileInfo.Exists,
Length = length,
LastModified = fileInfo.LastWriteTimeUtc
};
}
/// <inheritdoc />
protected override async Task WriteFileAsync(ActionContext context, PhysicalFileResult result, RangeItemHeaderValue? range, long rangeLength)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(result);
if (range is not null && rangeLength == 0)
{
return;
}
// It's a bit of wasted IO to perform this check again, but non-symlinks shouldn't use this code
if (!IsSymLink(result.FileName))
{
await base.WriteFileAsync(context, result, range, rangeLength).ConfigureAwait(false);
return;
}
var response = context.HttpContext.Response;
if (range is not null)
{
await SendFileAsync(
result.FileName,
response,
offset: range.From ?? 0L,
count: rangeLength).ConfigureAwait(false);
return;
}
await SendFileAsync(
result.FileName,
response,
offset: 0,
count: null).ConfigureAwait(false);
}
private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count, CancellationToken cancellationToken = default)
{
var fileInfo = GetFileInfo(filePath);
if (offset < 0 || offset > fileInfo.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
}
if (count.HasValue
&& (count.Value < 0 || count.Value > fileInfo.Length - offset))
{
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
}
// Copied from SendFileFallback.SendFileAsync
const int BufferSize = 1024 * 16;
var useRequestAborted = !cancellationToken.CanBeCanceled;
var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken;
var fileStream = new FileStream(
filePath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: BufferSize,
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
await using (fileStream.ConfigureAwait(false))
{
try
{
localCancel.ThrowIfCancellationRequested();
fileStream.Seek(offset, SeekOrigin.Begin);
await StreamCopyOperation
.CopyToAsync(fileStream, response.Body, count, BufferSize, localCancel)
.ConfigureAwait(true);
}
catch (OperationCanceledException) when (useRequestAborted)
{
}
}
}
private static bool IsSymLink(string path) => (File.GetAttributes(path) & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
}
}

View File

@@ -55,9 +55,25 @@ namespace Jellyfin.Server.Migrations.Routines
};
var dataPath = _paths.DataPath;
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
var activityLogPath = Path.Combine(dataPath, DbFilename);
if (!File.Exists(activityLogPath))
{
_logger.LogWarning("{ActivityLogDb} doesn't exist, nothing to migrate", activityLogPath);
return;
}
using (var connection = new SqliteConnection($"Filename={activityLogPath}"))
{
connection.Open();
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='ActivityLog';");
foreach (var row in tableQuery)
{
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'ActivityLog' doesn't exist in {ActivityLogPath}, nothing to migrate", activityLogPath);
break;
}
}
using var userDbConnection = new SqliteConnection($"Filename={Path.Combine(dataPath, "users.db")}");
userDbConnection.Open();

View File

@@ -122,6 +122,16 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine
{
lastWriteTimeUtc = File.GetLastWriteTimeUtc(filePath);
}
catch (ArgumentOutOfRangeException e)
{
_logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message);
return null;
}
catch (UnauthorizedAccessException e)
{
_logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message);
return null;
}
catch (IOException e)
{
_logger.LogDebug("Skipping {Path}: {Exception}", filePath, e.Message);

View File

@@ -57,11 +57,28 @@ public class MigrateUserDb : IMigrationRoutine
public void Perform()
{
var dataPath = _paths.DataPath;
var userDbPath = Path.Combine(dataPath, DbFilename);
if (!File.Exists(userDbPath))
{
_logger.LogWarning("{UserDbPath} doesn't exist, nothing to migrate", userDbPath);
return;
}
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
using (var connection = new SqliteConnection($"Filename={userDbPath}"))
{
connection.Open();
var tableQuery = connection.Query("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='LocalUsersv2';");
foreach (var row in tableQuery)
{
if (row.GetInt32(0) == 0)
{
_logger.LogWarning("Table 'LocalUsersv2' doesn't exist in {UserDbPath}, nothing to migrate", userDbPath);
break;
}
}
using var dbContext = _provider.CreateDbContext();
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");

View File

@@ -16,15 +16,12 @@ using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks;
using Jellyfin.Server.Implementations.Extensions;
using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.XbmcMetadata;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -69,8 +66,6 @@ namespace Jellyfin.Server
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
// TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, SymlinkFollowingPhysicalFileResultExecutor>();
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
services.AddJellyfinDbContext(_serverApplicationHost.ConfigurationManager, _configuration);
services.AddJellyfinApiSwagger();