< Summary

Information
Class: MyNet.IO.DirectoryService
Assembly: MyNet.IO
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/DirectoryService.cs
Tag: 323_28699572109
Line coverage
74%
Covered lines: 43
Uncovered lines: 15
Coverable lines: 58
Total lines: 192
Line coverage: 74.1%
Branch coverage
70%
Covered branches: 17
Total branches: 24
Branch coverage: 70.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
ResolveRoot(...)50%22100%
CreateSubDirectory(...)50%22100%
CreateFile(...)75%4483.33%
GetFileName(...)100%22100%
NormalizeExtension(...)66.66%6687.5%
Delete()100%1150%
Clean()100%4470%
TryDelete(...)50%3250%
TryDelete(...)100%1150%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.IO/DirectoryService.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="DirectoryService.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Diagnostics.CodeAnalysis;
 9using System.IO;
 10using Microsoft.Extensions.Logging;
 11using Microsoft.Extensions.Logging.Abstractions;
 12
 13namespace MyNet.IO;
 14
 15/// <summary>
 16/// Manages a directory root and provides helpers to create uniquely named files and sub-directories.
 17/// The root path is created on demand, supports environment-variable expansion, and falls back to
 18/// the system temporary directory when no path is provided.
 19/// </summary>
 20/// <remarks>
 21/// Initializes a new instance of the <see cref="DirectoryService"/> class.
 22/// Initializes a new instance of <see cref="DirectoryService"/>.
 23/// </remarks>
 24/// <param name="root">
 25/// The root directory path. Environment variables (e.g. <c>%TEMP%</c>) are expanded automatically.
 26/// Pass an empty string or <c>null</c> to use the system temporary directory.
 27/// </param>
 28/// <param name="logger">Optional logger. Defaults to the application-wide logger for this class.</param>
 29[SuppressMessage("Performance", "CA1823:Avoid unused private fields", Justification = "Used by LoggerMessage source gene
 30public partial class DirectoryService(string root, ILogger<DirectoryService>? logger = null) : IDirectoryService
 31{
 2432    private readonly ILogger<DirectoryService> _logger = logger ?? NullLogger<DirectoryService>.Instance;
 33
 34    /// <summary>
 35    /// Gets the absolute root path managed by this instance. The directory is created on demand if it does not exist.
 36    /// </summary>
 2437    public string RootDirectory { get; } = ResolveRoot(root);
 38
 39    /// <summary>
 40    /// Resolves the root directory from the supplied raw path. Falls back to the system temporary directory when <param
 41    /// </summary>
 42    /// <param name="root">The raw root path.</param>
 43    /// <returns>The resolved absolute root path.</returns>
 44    private static string ResolveRoot(string? root)
 45    {
 2446        var path = string.IsNullOrWhiteSpace(root)
 2447            ? Path.GetTempPath()
 2448            : Environment.ExpandEnvironmentVariables(root);
 49
 2450        FileHelper.EnsureDirectoryExists(path);
 51
 2452        return path;
 53    }
 54
 55    /// <inheritdoc/>
 56    public string CreateSubDirectory(string name)
 57    {
 358        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Subdirectory name cannot be empty.", nameof(na
 59
 360        var fullPath = Path.Combine(RootDirectory, name);
 361        Directory.CreateDirectory(fullPath);
 62
 363        return fullPath;
 64    }
 65
 66    /// <inheritdoc/>
 67    public string CreateFile(string? fileExtension = null, string? preferredFileName = null)
 68    {
 69        const int maxAttempts = 10;
 70
 1871        for (var i = 0; i < maxAttempts; i++)
 72        {
 973            var path = GetFileName(fileExtension, preferredFileName);
 74
 975            if (FileHelper.TryCreateFile(path))
 676                return path;
 77
 378            preferredFileName = null;
 79        }
 80
 081        throw new IOException("Unable to create a unique file after multiple attempts.");
 82    }
 83
 84    /// <inheritdoc/>
 85    public string GetFileName(string? fileExtension = null, string? preferredFileName = null)
 86    {
 1587        var extension = NormalizeExtension(fileExtension);
 88
 1589        var baseName = string.IsNullOrWhiteSpace(preferredFileName) ? Path.GetRandomFileName() : Path.GetFileNameWithout
 90
 1591        return Path.Combine(RootDirectory, Path.ChangeExtension(baseName, extension));
 92    }
 93
 94    /// <summary>
 95    /// Normalizes a raw file-extension token into a canonical form with a leading dot (e.g. <c>.csv</c>).
 96    /// Falls back to <c>.tmp</c> when both parameters are empty.
 97    /// </summary>
 98    private static string NormalizeExtension(string? extension)
 99    {
 15100        if (string.IsNullOrWhiteSpace(extension))
 3101            return ".tmp";
 102
 12103        extension = extension.Trim();
 104
 12105        if (extension.StartsWith('*'))
 0106            extension = extension[1..];
 107
 12108        if (!extension.StartsWith('.'))
 12109            extension = "." + extension;
 110
 12111        return extension;
 112    }
 113
 114    /// <inheritdoc/>
 115    public void Delete()
 116    {
 117        try
 118        {
 3119            Directory.Delete(RootDirectory, true);
 3120        }
 0121        catch (Exception ex)
 122        {
 0123            LogFailedToDeleteRoot(ex);
 0124        }
 3125    }
 126
 127    /// <inheritdoc/>
 128    public void Clean()
 129    {
 130        try
 131        {
 3132            var dir = new DirectoryInfo(RootDirectory);
 133
 12134            foreach (var file in dir.GetFiles())
 3135                TryDelete(file);
 136
 12137            foreach (var subDir in dir.GetDirectories())
 3138                TryDelete(subDir);
 3139        }
 0140        catch (Exception ex)
 141        {
 0142            LogFailedToClean(ex);
 0143        }
 3144    }
 145
 146    /// <summary>
 147    /// Attempts to delete a directory recursively, logging a warning on failure.
 148    /// </summary>
 149    /// <param name="info">The directory to delete.</param>
 150    private void TryDelete(FileSystemInfo info)
 151    {
 152        try
 153        {
 3154            if (info is DirectoryInfo dir)
 3155                dir.Delete(true);
 156            else
 0157                info.Delete();
 3158        }
 0159        catch (Exception ex)
 160        {
 0161            LogFailedToDeleteItem(ex);
 0162        }
 3163    }
 164
 165    /// <summary>
 166    /// Attempts to delete a single file, logging a warning on failure.
 167    /// </summary>
 168    private void TryDelete(FileInfo info)
 169    {
 170        try
 171        {
 3172            info.Delete();
 3173        }
 0174        catch (Exception ex)
 175        {
 0176            LogFailedToDeleteAFile(ex);
 0177        }
 3178    }
 179
 180    [LoggerMessage(LogLevel.Warning, "Failed to clean directory.")]
 181    private partial void LogFailedToClean(Exception exception);
 182
 183    [LoggerMessage(LogLevel.Warning, "Failed to delete root directory.")]
 184    private partial void LogFailedToDeleteRoot(Exception exception);
 185
 186    [LoggerMessage(LogLevel.Warning, "Failed to delete file system item.")]
 187    private partial void LogFailedToDeleteItem(Exception exception);
 188
 189    [LoggerMessage(LogLevel.Warning, "Failed to delete a file.")]
 190    private partial void LogFailedToDeleteAFile(Exception exception);
 191}
 192