Cleanup file related code (#14023)

This commit is contained in:
Bond-009 2025-05-04 16:40:34 +02:00 committed by GitHub
parent 4096c973c6
commit 0c3ba30de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 104 additions and 56 deletions

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.AppBase namespace Emby.Server.Implementations.AppBase
@ -91,10 +92,7 @@ namespace Emby.Server.Implementations.AppBase
/// <inheritdoc /> /// <inheritdoc />
public void CreateAndCheckMarker(string path, string markerName, bool recursive = false) public void CreateAndCheckMarker(string path, string markerName, bool recursive = false)
{ {
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
{
Directory.CreateDirectory(path);
}
CheckOrCreateMarker(path, $".jellyfin-{markerName}", recursive); CheckOrCreateMarker(path, $".jellyfin-{markerName}", recursive);
} }
@ -115,7 +113,7 @@ namespace Emby.Server.Implementations.AppBase
var markerPath = Path.Combine(path, markerName); var markerPath = Path.Combine(path, markerName);
if (!File.Exists(markerPath)) if (!File.Exists(markerPath))
{ {
File.Create(markerPath).Dispose(); FileHelper.CreateEmpty(markerPath);
} }
} }
} }

View File

@ -159,13 +159,13 @@ namespace Emby.Server.Implementations.IO
catch (IOException) catch (IOException)
{ {
// Cross device move requires a copy // Cross device move requires a copy
Directory.CreateDirectory(destination); var directory = Directory.CreateDirectory(destination);
foreach (string file in Directory.GetFiles(source)) foreach (var file in directory.EnumerateFiles())
{ {
File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), true); file.CopyTo(Path.Combine(destination, file.Name), true);
} }
Directory.Delete(source, true); directory.Delete(true);
} }
} }

View File

@ -20,7 +20,7 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
} }
var parentDir = directory.Parent; var parentDir = directory.Parent;
if (parentDir == null || parentDir.FullName == directory.FullName) if (parentDir is null)
{ {
return null; return null;
} }

View File

@ -2945,7 +2945,7 @@ namespace Emby.Server.Implementations.Library
{ {
var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values? var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values?
await File.WriteAllBytesAsync(path, []).ConfigureAwait(false); FileHelper.CreateEmpty(path);
} }
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);

View File

@ -681,17 +681,17 @@ namespace Emby.Server.Implementations.Library
mediaInfo = await _mediaEncoder.GetMediaInfo( mediaInfo = await _mediaEncoder.GetMediaInfo(
new MediaInfoRequest new MediaInfoRequest
{ {
MediaSource = mediaSource, MediaSource = mediaSource,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false ExtractChapters = false
}, },
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
if (cacheFilePath is not null) if (cacheFilePath is not null)
{ {
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
FileStream createStream = File.Create(cacheFilePath); FileStream createStream = AsyncFile.Create(cacheFilePath);
await using (createStream.ConfigureAwait(false)) await using (createStream.ConfigureAwait(false))
{ {
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);

View File

@ -520,7 +520,7 @@ namespace Emby.Server.Implementations.Localization
public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT) public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT)
{ {
// Unlikely case the dictionary is not (yet) initialized properly // Unlikely case the dictionary is not (yet) initialized properly
if (_iso6392BtoT == null) if (_iso6392BtoT is null)
{ {
isoT = null; isoT = null;
return false; return false;

View File

@ -125,7 +125,7 @@ public class SyncPlayController : BaseJellyfinApiController
{ {
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var group = _syncPlayManager.GetGroup(currentSession, id); var group = _syncPlayManager.GetGroup(currentSession, id);
return group == null ? NotFound() : Ok(group); return group is null ? NotFound() : Ok(group);
} }
/// <summary> /// <summary>

View File

@ -72,10 +72,7 @@ public static class StorageHelper
private static void TestDataDirectorySize(string path, ILogger logger, long threshold = -1) private static void TestDataDirectorySize(string path, ILogger logger, long threshold = -1)
{ {
logger.LogDebug("Check path {TestPath} for storage capacity", path); logger.LogDebug("Check path {TestPath} for storage capacity", path);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
{
Directory.CreateDirectory(path);
}
var drive = new DriveInfo(path); var drive = new DriveInfo(path);
if (threshold != -1 && drive.AvailableFreeSpace < threshold) if (threshold != -1 && drive.AvailableFreeSpace < threshold)

View File

@ -97,28 +97,28 @@ public class TrickplayManager : ITrickplayManager
var existingResolution = resolution.Key; var existingResolution = resolution.Key;
var tileWidth = resolution.Value.TileWidth; var tileWidth = resolution.Value.TileWidth;
var tileHeight = resolution.Value.TileHeight; var tileHeight = resolution.Value.TileHeight;
var shouldBeSavedWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; var shouldBeSavedWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia;
var localOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false); var localOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false));
var mediaOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true); var mediaOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true));
if (shouldBeSavedWithMedia && Directory.Exists(localOutputDir)) if (shouldBeSavedWithMedia && localOutputDir.Exists)
{ {
var localDirFiles = Directory.GetFiles(localOutputDir); var localDirFiles = localOutputDir.EnumerateFiles();
var mediaDirExists = Directory.Exists(mediaOutputDir); var mediaDirExists = mediaOutputDir.Exists;
if (localDirFiles.Length > 0 && ((mediaDirExists && Directory.GetFiles(mediaOutputDir).Length == 0) || !mediaDirExists)) if (localDirFiles.Any() && ((mediaDirExists && mediaOutputDir.EnumerateFiles().Any()) || !mediaDirExists))
{ {
// Move images from local dir to media dir // Move images from local dir to media dir
MoveContent(localOutputDir, mediaOutputDir); MoveContent(localOutputDir.FullName, mediaOutputDir.FullName);
_logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir); _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir);
} }
} }
else if (!shouldBeSavedWithMedia && Directory.Exists(mediaOutputDir)) else if (!shouldBeSavedWithMedia && mediaOutputDir.Exists)
{ {
var mediaDirFiles = Directory.GetFiles(mediaOutputDir); var mediaDirFiles = mediaOutputDir.EnumerateFiles();
var localDirExists = Directory.Exists(localOutputDir); var localDirExists = localOutputDir.Exists;
if (mediaDirFiles.Length > 0 && ((localDirExists && Directory.GetFiles(localOutputDir).Length == 0) || !localDirExists)) if (mediaDirFiles.Any() && ((localDirExists && localOutputDir.EnumerateFiles().Any()) || !localDirExists))
{ {
// Move images from media dir to local dir // Move images from media dir to local dir
MoveContent(mediaOutputDir, localOutputDir); MoveContent(mediaOutputDir.FullName, localOutputDir.FullName);
_logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir); _logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir);
} }
} }
@ -131,10 +131,10 @@ public class TrickplayManager : ITrickplayManager
var parent = Directory.GetParent(sourceFolder); var parent = Directory.GetParent(sourceFolder);
if (parent is not null) if (parent is not null)
{ {
var parentContent = Directory.GetDirectories(parent.FullName); var parentContent = parent.EnumerateDirectories();
if (parentContent.Length == 0) if (!parentContent.Any())
{ {
Directory.Delete(parent.FullName); parent.Delete();
} }
} }
} }
@ -220,13 +220,13 @@ public class TrickplayManager : ITrickplayManager
var tileWidth = options.TileWidth; var tileWidth = options.TileWidth;
var tileHeight = options.TileHeight; var tileHeight = options.TileHeight;
var saveWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; var saveWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia;
var outputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia); var outputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia));
// Import existing trickplay tiles // Import existing trickplay tiles
if (!replace && Directory.Exists(outputDir)) if (!replace && outputDir.Exists)
{ {
var existingFiles = Directory.GetFiles(outputDir); var existingFiles = outputDir.GetFiles();
if (existingFiles.Length > 0) if (existingFiles.Length > 0)
{ {
var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false); var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false);
@ -251,9 +251,9 @@ public class TrickplayManager : ITrickplayManager
foreach (var tile in existingFiles) foreach (var tile in existingFiles)
{ {
var image = _imageEncoder.GetImageSize(tile); var image = _imageEncoder.GetImageSize(tile.FullName);
localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight)); localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight));
var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000)); var bitrate = (int)Math.Ceiling((decimal)tile.Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000));
localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate); localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate);
} }
@ -296,7 +296,7 @@ public class TrickplayManager : ITrickplayManager
.ToList(); .ToList();
// Create tiles // Create tiles
var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir); var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir.FullName);
// Save tiles info // Save tiles info
try try
@ -319,7 +319,7 @@ public class TrickplayManager : ITrickplayManager
// Make sure no files stay in metadata folders on failure // Make sure no files stay in metadata folders on failure
// if tiles info wasn't saved. // if tiles info wasn't saved.
Directory.Delete(outputDir, true); outputDir.Delete(true);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -215,7 +215,7 @@ namespace Jellyfin.Server.Extensions
}); });
// Add all xml doc files to swagger generator. // Add all xml doc files to swagger generator.
var xmlFiles = Directory.GetFiles( var xmlFiles = Directory.EnumerateFiles(
AppContext.BaseDirectory, AppContext.BaseDirectory,
"*.xml", "*.xml",
SearchOption.TopDirectoryOnly); SearchOption.TopDirectoryOnly);

View File

@ -133,9 +133,9 @@ namespace MediaBrowser.MediaEncoding.Attachments
var outputFolder = _pathManager.GetAttachmentFolderPath(mediaSource.Id); var outputFolder = _pathManager.GetAttachmentFolderPath(mediaSource.Id);
using (await _semaphoreLocks.LockAsync(outputFolder, cancellationToken).ConfigureAwait(false)) using (await _semaphoreLocks.LockAsync(outputFolder, cancellationToken).ConfigureAwait(false))
{ {
Directory.CreateDirectory(outputFolder); var directory = Directory.CreateDirectory(outputFolder);
var fileNames = Directory.GetFiles(outputFolder, "*", SearchOption.TopDirectoryOnly).Select(f => Path.GetFileName(f)); var fileNames = directory.GetFiles("*", SearchOption.TopDirectoryOnly).Select(f => f.Name).ToHashSet();
var missingFiles = mediaSource.MediaAttachments.Where(a => !fileNames.Contains(a.FileName) && !string.Equals(a.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)); var missingFiles = mediaSource.MediaAttachments.Where(a => a.FileName is not null && !fileNames.Contains(a.FileName) && !string.Equals(a.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase));
if (!missingFiles.Any()) if (!missingFiles.Any())
{ {
// Skip extraction if all files already exist // Skip extraction if all files already exist

View File

@ -26,6 +26,14 @@ namespace MediaBrowser.Model.IO
Options = FileOptions.Asynchronous Options = FileOptions.Asynchronous
}; };
/// <summary>
/// Creates, or truncates and overwrites, a file in the specified path.
/// </summary>
/// <param name="path">The path and name of the file to create.</param>
/// <returns>A <see cref="FileStream" /> that provides read/write access to the file specified in path.</returns>
public static FileStream Create(string path)
=> new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
/// <summary> /// <summary>
/// Opens an existing file for reading. /// Opens an existing file for reading.
/// </summary> /// </summary>

View File

@ -0,0 +1,20 @@
using System.IO;
namespace Jellyfin.Extensions;
/// <summary>
/// Provides helper functions for <see cref="File" />.
/// </summary>
public static class FileHelper
{
/// <summary>
/// Creates, or truncates a file in the specified path.
/// </summary>
/// <param name="path">The path and name of the file to create.</param>
public static void CreateEmpty(string path)
{
using (File.OpenHandle(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
}
}
}

View File

@ -363,7 +363,7 @@ namespace Jellyfin.LiveTv.Channels
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
FileStream createStream = File.Create(path); FileStream createStream = AsyncFile.Create(path);
await using (createStream.ConfigureAwait(false)) await using (createStream.ConfigureAwait(false))
{ {
await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false); await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
@ -866,7 +866,7 @@ namespace Jellyfin.LiveTv.Channels
{ {
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
var createStream = File.Create(path); var createStream = AsyncFile.Create(path);
await using (createStream.ConfigureAwait(false)) await using (createStream.ConfigureAwait(false))
{ {
await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false); await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);

View File

@ -0,0 +1,23 @@
using System.IO;
using Xunit;
namespace Jellyfin.Extensions.Tests;
public static class FileHelperTests
{
[Fact]
public static void CreateEmpty_Valid_Correct()
{
var path = Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
var fileInfo = new FileInfo(path);
Assert.False(fileInfo.Exists);
FileHelper.CreateEmpty(path);
fileInfo.Refresh();
Assert.True(fileInfo.Exists);
File.Delete(path);
}
}

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using AutoFixture; using AutoFixture;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.Plugins;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using Jellyfin.Extensions.Json.Converters; using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
@ -85,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
var dllPath = Path.GetDirectoryName(Path.Combine(_pluginPath, dllFile))!; var dllPath = Path.GetDirectoryName(Path.Combine(_pluginPath, dllFile))!;
Directory.CreateDirectory(dllPath); Directory.CreateDirectory(dllPath);
File.Create(Path.Combine(dllPath, filename)); FileHelper.CreateEmpty(Path.Combine(dllPath, filename));
var metafilePath = Path.Combine(_pluginPath, "meta.json"); var metafilePath = Path.Combine(_pluginPath, "meta.json");
File.WriteAllText(metafilePath, JsonSerializer.Serialize(manifest, _options)); File.WriteAllText(metafilePath, JsonSerializer.Serialize(manifest, _options));
@ -141,7 +142,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
foreach (var file in files) foreach (var file in files)
{ {
File.Create(Path.Combine(_pluginPath, file)); FileHelper.CreateEmpty(Path.Combine(_pluginPath, file));
} }
var metafilePath = Path.Combine(_pluginPath, "meta.json"); var metafilePath = Path.Combine(_pluginPath, "meta.json");

View File

@ -1,6 +1,7 @@
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -33,7 +34,7 @@ namespace Jellyfin.Server.Integration.Tests
// Write out for publishing // Write out for publishing
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json")); string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath); _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
await using var fs = File.Create(outputPath); await using var fs = AsyncFile.Create(outputPath);
await response.Content.CopyToAsync(fs); await response.Content.CopyToAsync(fs);
} }
} }