< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.IO.FileSystemHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/IO/FileSystemHelper.cs
Line coverage
21%
Covered lines: 9
Uncovered lines: 32
Coverable lines: 41
Total lines: 143
Line coverage: 21.9%
Branch coverage
33%
Covered branches: 6
Total branches: 18
Branch coverage: 33.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 8/14/2025 - 12:11:05 AM Line coverage: 9.5% (2/21) Branch coverage: 50% (2/4) Total lines: 6411/18/2025 - 12:11:25 AM Line coverage: 21.9% (9/41) Branch coverage: 33.3% (6/18) Total lines: 143 8/14/2025 - 12:11:05 AM Line coverage: 9.5% (2/21) Branch coverage: 50% (2/4) Total lines: 6411/18/2025 - 12:11:25 AM Line coverage: 21.9% (9/41) Branch coverage: 33.3% (6/18) Total lines: 143

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
DeleteFile(...)100%210%
DeleteEmptyFolders(...)50%13416.66%
ResolveLinkTarget(...)28.57%881427.77%
ResolveLinkTarget(...)100%11100%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/IO/FileSystemHelper.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using MediaBrowser.Model.IO;
 6using Microsoft.Extensions.Logging;
 7
 8namespace MediaBrowser.Controller.IO;
 9
 10/// <summary>
 11/// Helper methods for file system management.
 12/// </summary>
 13public static class FileSystemHelper
 14{
 15    /// <summary>
 16    /// Deletes the file.
 17    /// </summary>
 18    /// <param name="fileSystem">The fileSystem.</param>
 19    /// <param name="path">The path.</param>
 20    /// <param name="logger">The logger.</param>
 21    public static void DeleteFile(IFileSystem fileSystem, string path, ILogger logger)
 22    {
 23        try
 24        {
 025            fileSystem.DeleteFile(path);
 026        }
 027        catch (UnauthorizedAccessException ex)
 28        {
 029            logger.LogError(ex, "Error deleting file {Path}", path);
 030        }
 031        catch (IOException ex)
 32        {
 033            logger.LogError(ex, "Error deleting file {Path}", path);
 034        }
 035    }
 36
 37    /// <summary>
 38    /// Recursively delete empty folders.
 39    /// </summary>
 40    /// <param name="fileSystem">The fileSystem.</param>
 41    /// <param name="path">The path.</param>
 42    /// <param name="logger">The logger.</param>
 43    public static void DeleteEmptyFolders(IFileSystem fileSystem, string path, ILogger logger)
 44    {
 445        foreach (var directory in fileSystem.GetDirectoryPaths(path))
 46        {
 047            DeleteEmptyFolders(fileSystem, directory, logger);
 048            if (!fileSystem.GetFileSystemEntryPaths(directory).Any())
 49            {
 50                try
 51                {
 052                    Directory.Delete(directory, false);
 053                }
 054                catch (UnauthorizedAccessException ex)
 55                {
 056                    logger.LogError(ex, "Error deleting directory {Path}", directory);
 057                }
 058                catch (IOException ex)
 59                {
 060                    logger.LogError(ex, "Error deleting directory {Path}", directory);
 061                }
 62            }
 63        }
 264    }
 65
 66    /// <summary>
 67    /// Gets the target of the specified file link.
 68    /// </summary>
 69    /// <remarks>
 70    /// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
 71    /// </remarks>
 72    /// <param name="linkPath">The path of the file link.</param>
 73    /// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next lin
 74    /// <returns>
 75    /// A <see cref="FileInfo"/> if the <paramref name="linkPath"/> is a link, regardless of if the target exists; other
 76    /// </returns>
 77    public static FileInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false)
 78    {
 79        // Check if the file exists so the native resolve handler won't throw at us.
 180        if (!File.Exists(linkPath))
 81        {
 082            return null;
 83        }
 84
 185        if (!returnFinalTarget)
 86        {
 087            return File.ResolveLinkTarget(linkPath, returnFinalTarget: false) as FileInfo;
 88        }
 89
 190        if (File.ResolveLinkTarget(linkPath, returnFinalTarget: false) is not FileInfo targetInfo)
 91        {
 092            return null;
 93        }
 94
 195        if (!targetInfo.Exists)
 96        {
 197            return targetInfo;
 98        }
 99
 0100        var currentPath = targetInfo.FullName;
 0101        var visited = new HashSet<string>(StringComparer.Ordinal) { linkPath, currentPath };
 0102        while (File.ResolveLinkTarget(currentPath, returnFinalTarget: false) is FileInfo linkInfo)
 103        {
 0104            var targetPath = linkInfo.FullName;
 105
 106            // If an infinite loop is detected, return the file info for the
 107            // first link in the loop we encountered.
 0108            if (!visited.Add(targetPath))
 109            {
 0110                return new FileInfo(targetPath);
 111            }
 112
 0113            targetInfo = linkInfo;
 0114            currentPath = targetPath;
 115
 116            // Exit if the target doesn't exist, so the native resolve handler won't throw at us.
 0117            if (!targetInfo.Exists)
 118            {
 119                break;
 120            }
 121        }
 122
 0123        return targetInfo;
 124    }
 125
 126    /// <summary>
 127    /// Gets the target of the specified file link.
 128    /// </summary>
 129    /// <remarks>
 130    /// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
 131    /// </remarks>
 132    /// <param name="fileInfo">The file info of the file link.</param>
 133    /// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next lin
 134    /// <returns>
 135    /// A <see cref="FileInfo"/> if the <paramref name="fileInfo"/> is a link, regardless of if the target exists; other
 136    /// </returns>
 137    public static FileInfo? ResolveLinkTarget(FileInfo fileInfo, bool returnFinalTarget = false)
 138    {
 1139        ArgumentNullException.ThrowIfNull(fileInfo);
 140
 1141        return ResolveLinkTarget(fileInfo.FullName, returnFinalTarget);
 142    }
 143}