mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-30 18:22:48 -04:00 
			
		
		
		
	Cleanup file related code (#14023)
This commit is contained in:
		
							parent
							
								
									4096c973c6
								
							
						
					
					
						commit
						0c3ba30de2
					
				| @ -2,6 +2,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using Jellyfin.Extensions; | ||||
| using MediaBrowser.Common.Configuration; | ||||
| 
 | ||||
| namespace Emby.Server.Implementations.AppBase | ||||
| @ -91,10 +92,7 @@ namespace Emby.Server.Implementations.AppBase | ||||
|         /// <inheritdoc /> | ||||
|         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); | ||||
|         } | ||||
| @ -115,7 +113,7 @@ namespace Emby.Server.Implementations.AppBase | ||||
|             var markerPath = Path.Combine(path, markerName); | ||||
|             if (!File.Exists(markerPath)) | ||||
|             { | ||||
|                 File.Create(markerPath).Dispose(); | ||||
|                 FileHelper.CreateEmpty(markerPath); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -159,13 +159,13 @@ namespace Emby.Server.Implementations.IO | ||||
|             catch (IOException) | ||||
|             { | ||||
|                 // Cross device move requires a copy | ||||
|                 Directory.CreateDirectory(destination); | ||||
|                 foreach (string file in Directory.GetFiles(source)) | ||||
|                 var directory = Directory.CreateDirectory(destination); | ||||
|                 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); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,7 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule | ||||
|         } | ||||
| 
 | ||||
|         var parentDir = directory.Parent; | ||||
|         if (parentDir == null || parentDir.FullName == directory.FullName) | ||||
|         if (parentDir is null) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
| @ -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? | ||||
| 
 | ||||
|                     await File.WriteAllBytesAsync(path, []).ConfigureAwait(false); | ||||
|                     FileHelper.CreateEmpty(path); | ||||
|                 } | ||||
| 
 | ||||
|                 CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); | ||||
|  | ||||
| @ -681,17 +681,17 @@ namespace Emby.Server.Implementations.Library | ||||
| 
 | ||||
|                 mediaInfo = await _mediaEncoder.GetMediaInfo( | ||||
|                     new MediaInfoRequest | ||||
|                 { | ||||
|                     MediaSource = mediaSource, | ||||
|                     MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, | ||||
|                     ExtractChapters = false | ||||
|                 }, | ||||
|                     { | ||||
|                         MediaSource = mediaSource, | ||||
|                         MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, | ||||
|                         ExtractChapters = false | ||||
|                     }, | ||||
|                     cancellationToken).ConfigureAwait(false); | ||||
| 
 | ||||
|                 if (cacheFilePath is not null) | ||||
|                 { | ||||
|                     Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); | ||||
|                     FileStream createStream = File.Create(cacheFilePath); | ||||
|                     FileStream createStream = AsyncFile.Create(cacheFilePath); | ||||
|                     await using (createStream.ConfigureAwait(false)) | ||||
|                     { | ||||
|                         await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
| @ -520,7 +520,7 @@ namespace Emby.Server.Implementations.Localization | ||||
|         public bool TryGetISO6392TFromB(string isoB, [NotNullWhen(true)] out string? isoT) | ||||
|         { | ||||
|             // Unlikely case the dictionary is not (yet) initialized properly | ||||
|             if (_iso6392BtoT == null) | ||||
|             if (_iso6392BtoT is null) | ||||
|             { | ||||
|                 isoT = null; | ||||
|                 return false; | ||||
|  | ||||
| @ -125,7 +125,7 @@ public class SyncPlayController : BaseJellyfinApiController | ||||
|     { | ||||
|         var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); | ||||
|         var group = _syncPlayManager.GetGroup(currentSession, id); | ||||
|         return group == null ? NotFound() : Ok(group); | ||||
|         return group is null ? NotFound() : Ok(group); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|  | ||||
| @ -72,10 +72,7 @@ public static class StorageHelper | ||||
|     private static void TestDataDirectorySize(string path, ILogger logger, long threshold = -1) | ||||
|     { | ||||
|         logger.LogDebug("Check path {TestPath} for storage capacity", path); | ||||
|         if (!Directory.Exists(path)) | ||||
|         { | ||||
|             Directory.CreateDirectory(path); | ||||
|         } | ||||
|         Directory.CreateDirectory(path); | ||||
| 
 | ||||
|         var drive = new DriveInfo(path); | ||||
|         if (threshold != -1 && drive.AvailableFreeSpace < threshold) | ||||
|  | ||||
| @ -97,28 +97,28 @@ public class TrickplayManager : ITrickplayManager | ||||
|             var existingResolution = resolution.Key; | ||||
|             var tileWidth = resolution.Value.TileWidth; | ||||
|             var tileHeight = resolution.Value.TileHeight; | ||||
|             var shouldBeSavedWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; | ||||
|             var localOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false); | ||||
|             var mediaOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true); | ||||
|             if (shouldBeSavedWithMedia && Directory.Exists(localOutputDir)) | ||||
|             var shouldBeSavedWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia; | ||||
|             var localOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false)); | ||||
|             var mediaOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true)); | ||||
|             if (shouldBeSavedWithMedia && localOutputDir.Exists) | ||||
|             { | ||||
|                 var localDirFiles = Directory.GetFiles(localOutputDir); | ||||
|                 var mediaDirExists = Directory.Exists(mediaOutputDir); | ||||
|                 if (localDirFiles.Length > 0 && ((mediaDirExists && Directory.GetFiles(mediaOutputDir).Length == 0) || !mediaDirExists)) | ||||
|                 var localDirFiles = localOutputDir.EnumerateFiles(); | ||||
|                 var mediaDirExists = mediaOutputDir.Exists; | ||||
|                 if (localDirFiles.Any() && ((mediaDirExists && mediaOutputDir.EnumerateFiles().Any()) || !mediaDirExists)) | ||||
|                 { | ||||
|                     // 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); | ||||
|                 } | ||||
|             } | ||||
|             else if (!shouldBeSavedWithMedia && Directory.Exists(mediaOutputDir)) | ||||
|             else if (!shouldBeSavedWithMedia && mediaOutputDir.Exists) | ||||
|             { | ||||
|                 var mediaDirFiles = Directory.GetFiles(mediaOutputDir); | ||||
|                 var localDirExists = Directory.Exists(localOutputDir); | ||||
|                 if (mediaDirFiles.Length > 0 && ((localDirExists && Directory.GetFiles(localOutputDir).Length == 0) || !localDirExists)) | ||||
|                 var mediaDirFiles = mediaOutputDir.EnumerateFiles(); | ||||
|                 var localDirExists = localOutputDir.Exists; | ||||
|                 if (mediaDirFiles.Any() && ((localDirExists && localOutputDir.EnumerateFiles().Any()) || !localDirExists)) | ||||
|                 { | ||||
|                     // 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); | ||||
|                 } | ||||
|             } | ||||
| @ -131,10 +131,10 @@ public class TrickplayManager : ITrickplayManager | ||||
|         var parent = Directory.GetParent(sourceFolder); | ||||
|         if (parent is not null) | ||||
|         { | ||||
|             var parentContent = Directory.GetDirectories(parent.FullName); | ||||
|             if (parentContent.Length == 0) | ||||
|             var parentContent = parent.EnumerateDirectories(); | ||||
|             if (!parentContent.Any()) | ||||
|             { | ||||
|                 Directory.Delete(parent.FullName); | ||||
|                 parent.Delete(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -220,13 +220,13 @@ public class TrickplayManager : ITrickplayManager | ||||
| 
 | ||||
|                 var tileWidth = options.TileWidth; | ||||
|                 var tileHeight = options.TileHeight; | ||||
|                 var saveWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia; | ||||
|                 var outputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia); | ||||
|                 var saveWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia; | ||||
|                 var outputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia)); | ||||
| 
 | ||||
|                 // 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) | ||||
|                     { | ||||
|                         var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false); | ||||
| @ -251,9 +251,9 @@ public class TrickplayManager : ITrickplayManager | ||||
| 
 | ||||
|                         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)); | ||||
|                             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); | ||||
|                         } | ||||
| 
 | ||||
| @ -296,7 +296,7 @@ public class TrickplayManager : ITrickplayManager | ||||
|                     .ToList(); | ||||
| 
 | ||||
|                 // Create tiles | ||||
|                 var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir); | ||||
|                 var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir.FullName); | ||||
| 
 | ||||
|                 // Save tiles info | ||||
|                 try | ||||
| @ -319,7 +319,7 @@ public class TrickplayManager : ITrickplayManager | ||||
| 
 | ||||
|                     // Make sure no files stay in metadata folders on failure | ||||
|                     // if tiles info wasn't saved. | ||||
|                     Directory.Delete(outputDir, true); | ||||
|                     outputDir.Delete(true); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|  | ||||
| @ -215,7 +215,7 @@ namespace Jellyfin.Server.Extensions | ||||
|                 }); | ||||
| 
 | ||||
|                 // Add all xml doc files to swagger generator. | ||||
|                 var xmlFiles = Directory.GetFiles( | ||||
|                 var xmlFiles = Directory.EnumerateFiles( | ||||
|                     AppContext.BaseDirectory, | ||||
|                     "*.xml", | ||||
|                     SearchOption.TopDirectoryOnly); | ||||
|  | ||||
| @ -133,9 +133,9 @@ namespace MediaBrowser.MediaEncoding.Attachments | ||||
|             var outputFolder = _pathManager.GetAttachmentFolderPath(mediaSource.Id); | ||||
|             using (await _semaphoreLocks.LockAsync(outputFolder, cancellationToken).ConfigureAwait(false)) | ||||
|             { | ||||
|                 Directory.CreateDirectory(outputFolder); | ||||
|                 var fileNames = Directory.GetFiles(outputFolder, "*", SearchOption.TopDirectoryOnly).Select(f => Path.GetFileName(f)); | ||||
|                 var missingFiles = mediaSource.MediaAttachments.Where(a => !fileNames.Contains(a.FileName) && !string.Equals(a.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)); | ||||
|                 var directory = Directory.CreateDirectory(outputFolder); | ||||
|                 var fileNames = directory.GetFiles("*", SearchOption.TopDirectoryOnly).Select(f => f.Name).ToHashSet(); | ||||
|                 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()) | ||||
|                 { | ||||
|                     // Skip extraction if all files already exist | ||||
|  | ||||
| @ -26,6 +26,14 @@ namespace MediaBrowser.Model.IO | ||||
|             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> | ||||
|         /// Opens an existing file for reading. | ||||
|         /// </summary> | ||||
|  | ||||
							
								
								
									
										20
									
								
								src/Jellyfin.Extensions/FileHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/Jellyfin.Extensions/FileHelper.cs
									
									
									
									
									
										Normal 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)) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -363,7 +363,7 @@ namespace Jellyfin.LiveTv.Channels | ||||
| 
 | ||||
|             Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||||
| 
 | ||||
|             FileStream createStream = File.Create(path); | ||||
|             FileStream createStream = AsyncFile.Create(path); | ||||
|             await using (createStream.ConfigureAwait(false)) | ||||
|             { | ||||
|                 await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false); | ||||
| @ -866,7 +866,7 @@ namespace Jellyfin.LiveTv.Channels | ||||
|             { | ||||
|                 Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||||
| 
 | ||||
|                 var createStream = File.Create(path); | ||||
|                 var createStream = AsyncFile.Create(path); | ||||
|                 await using (createStream.ConfigureAwait(false)) | ||||
|                 { | ||||
|                     await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false); | ||||
|  | ||||
							
								
								
									
										23
									
								
								tests/Jellyfin.Extensions.Tests/FileHelperTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/Jellyfin.Extensions.Tests/FileHelperTests.cs
									
									
									
									
									
										Normal 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); | ||||
|     } | ||||
| } | ||||
| @ -6,6 +6,7 @@ using System.Threading.Tasks; | ||||
| using AutoFixture; | ||||
| using Emby.Server.Implementations.Library; | ||||
| using Emby.Server.Implementations.Plugins; | ||||
| using Jellyfin.Extensions; | ||||
| using Jellyfin.Extensions.Json; | ||||
| using Jellyfin.Extensions.Json.Converters; | ||||
| using MediaBrowser.Common.Plugins; | ||||
| @ -85,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins | ||||
|             var dllPath = Path.GetDirectoryName(Path.Combine(_pluginPath, dllFile))!; | ||||
| 
 | ||||
|             Directory.CreateDirectory(dllPath); | ||||
|             File.Create(Path.Combine(dllPath, filename)); | ||||
|             FileHelper.CreateEmpty(Path.Combine(dllPath, filename)); | ||||
|             var metafilePath = Path.Combine(_pluginPath, "meta.json"); | ||||
| 
 | ||||
|             File.WriteAllText(metafilePath, JsonSerializer.Serialize(manifest, _options)); | ||||
| @ -141,7 +142,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins | ||||
| 
 | ||||
|             foreach (var file in files) | ||||
|             { | ||||
|                 File.Create(Path.Combine(_pluginPath, file)); | ||||
|                 FileHelper.CreateEmpty(Path.Combine(_pluginPath, file)); | ||||
|             } | ||||
| 
 | ||||
|             var metafilePath = Path.Combine(_pluginPath, "meta.json"); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| using System.IO; | ||||
| using System.Reflection; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Model.IO; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
| 
 | ||||
| @ -33,7 +34,7 @@ namespace Jellyfin.Server.Integration.Tests | ||||
|             // Write out for publishing | ||||
|             string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json")); | ||||
|             _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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user