using System; using System.Globalization; using System.IO; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.StorageHelpers; /// /// Contains methods to help with checking for storage and returning storage data for jellyfin folders. /// public static class StorageHelper { private const long TwoGigabyte = 2_147_483_647L; private const long FiveHundredAndTwelveMegaByte = 536_870_911L; private static readonly string[] _byteHumanizedSuffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; /// /// Tests the available storage capacity on the jellyfin paths with estimated minimum values. /// /// The application paths. /// Logger. public static void TestCommonPathsForStorageCapacity(IApplicationPaths applicationPaths, ILogger logger) { TestDataDirectorySize(applicationPaths.DataPath, logger, TwoGigabyte); TestDataDirectorySize(applicationPaths.LogDirectoryPath, logger, FiveHundredAndTwelveMegaByte); TestDataDirectorySize(applicationPaths.CachePath, logger, TwoGigabyte); TestDataDirectorySize(applicationPaths.ProgramDataPath, logger, TwoGigabyte); TestDataDirectorySize(applicationPaths.TempDirectory, logger, TwoGigabyte); } /// /// Gets the free space of a specific directory. /// /// Path to a folder. /// The number of bytes available space. public static FolderStorageInfo GetFreeSpaceOf(string path) { try { var driveInfo = new DriveInfo(path); return new FolderStorageInfo() { Path = path, FreeSpace = driveInfo.AvailableFreeSpace, UsedSpace = driveInfo.TotalSize - driveInfo.AvailableFreeSpace, StorageType = driveInfo.DriveType.ToString(), DeviceId = driveInfo.Name, }; } catch { return new FolderStorageInfo() { Path = path, FreeSpace = -1, UsedSpace = -1, StorageType = null, DeviceId = null }; } } /// /// Gets the underlying drive data from a given path and checks if the available storage capacity matches the threshold. /// /// The path to a folder to evaluate. /// The logger. /// The threshold to check for or -1 to just log the data. /// Thrown when the threshold is not available on the underlying storage. private static void TestDataDirectorySize(string path, ILogger logger, long threshold = -1) { logger.LogDebug("Check path {TestPath} for storage capacity", path); Directory.CreateDirectory(path); var drive = new DriveInfo(path); if (threshold != -1 && drive.AvailableFreeSpace < threshold) { throw new InvalidOperationException($"The path `{path}` has insufficient free space. Required: at least {HumanizeStorageSize(threshold)}."); } logger.LogInformation( "Storage path `{TestPath}` ({StorageType}) successfully checked with {FreeSpace} free which is over the minimum of {MinFree}.", path, drive.DriveType, HumanizeStorageSize(drive.AvailableFreeSpace), HumanizeStorageSize(threshold)); } /// /// Formats a size in bytes into a common human readable form. /// /// /// Taken and slightly modified from https://stackoverflow.com/a/4975942/1786007 . /// /// The size in bytes. /// A human readable approximate representation of the argument. public static string HumanizeStorageSize(long byteCount) { if (byteCount == 0) { return $"0{_byteHumanizedSuffixes[0]}"; } var bytes = Math.Abs(byteCount); var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); var num = Math.Round(bytes / Math.Pow(1024, place), 1); return (Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture) + _byteHumanizedSuffixes[place]; } }