From 63fdf8ca8ff9b22ce4cf8764cc05aad5d1d0ae62 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 28 Jul 2024 16:58:16 -0700 Subject: [PATCH] Cache root folders and improve getting disk space for series path roots --- .../DiskSpace/DiskSpaceServiceFixture.cs | 25 ++++++++----- .../DiskSpace/DiskSpaceService.cs | 15 ++++++-- .../RootFolders/RootFolderService.cs | 35 +++++++++++++------ 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs index 875fe0b35..114c8295e 100644 --- a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs +++ b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs @@ -5,6 +5,7 @@ using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.DiskSpace; +using NzbDrone.Core.RootFolders; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; @@ -16,14 +17,14 @@ namespace NzbDrone.Core.Test.DiskSpace { private string _seriesFolder; private string _seriesFolder2; - private string _droneFactoryFolder; + private string _rootFolder; [SetUp] public void SetUp() { _seriesFolder = @"G:\fasdlfsdf\series".AsOsAgnostic(); _seriesFolder2 = @"G:\fasdlfsdf\series2".AsOsAgnostic(); - _droneFactoryFolder = @"G:\dronefactory".AsOsAgnostic(); + _rootFolder = @"G:\fasdlfsdf".AsOsAgnostic(); Mocker.GetMock() .Setup(v => v.GetMounts()) @@ -51,6 +52,13 @@ namespace NzbDrone.Core.Test.DiskSpace .Returns(new Dictionary(seriesPaths.Select((value, i) => new KeyValuePair(i, value)))); } + private void GivenRootFolder(string seriesPath, string rootFolderPath) + { + Mocker.GetMock() + .Setup(v => v.GetBestRootFolderPath(seriesPath)) + .Returns(rootFolderPath); + } + private void GivenExistingFolder(string folder) { Mocker.GetMock() @@ -62,8 +70,8 @@ namespace NzbDrone.Core.Test.DiskSpace public void should_check_diskspace_for_series_folders() { GivenSeries(_seriesFolder); - - GivenExistingFolder(_seriesFolder); + GivenRootFolder(_seriesFolder, _rootFolder); + GivenExistingFolder(_rootFolder); var freeSpace = Subject.GetFreeSpace(); @@ -74,9 +82,9 @@ namespace NzbDrone.Core.Test.DiskSpace public void should_check_diskspace_for_same_root_folder_only_once() { GivenSeries(_seriesFolder, _seriesFolder2); - - GivenExistingFolder(_seriesFolder); - GivenExistingFolder(_seriesFolder2); + GivenRootFolder(_seriesFolder, _rootFolder); + GivenRootFolder(_seriesFolder2, _rootFolder); + GivenExistingFolder(_rootFolder); var freeSpace = Subject.GetFreeSpace(); @@ -87,9 +95,10 @@ namespace NzbDrone.Core.Test.DiskSpace } [Test] - public void should_not_check_diskspace_for_missing_series_folders() + public void should_not_check_diskspace_for_missing_series_root_folders() { GivenSeries(_seriesFolder); + GivenRootFolder(_seriesFolder, _rootFolder); var freeSpace = Subject.GetFreeSpace(); diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs index c9846b084..7b3950347 100644 --- a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; +using NzbDrone.Core.RootFolders; using NzbDrone.Core.Tv; namespace NzbDrone.Core.DiskSpace @@ -18,14 +19,16 @@ namespace NzbDrone.Core.DiskSpace public class DiskSpaceService : IDiskSpaceService { private readonly ISeriesService _seriesService; + private readonly IRootFolderService _rootFolderService; private readonly IDiskProvider _diskProvider; private readonly Logger _logger; private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled); - public DiskSpaceService(ISeriesService seriesService, IDiskProvider diskProvider, Logger logger) + public DiskSpaceService(ISeriesService seriesService, IRootFolderService rootFolderService, IDiskProvider diskProvider, Logger logger) { _seriesService = seriesService; + _rootFolderService = rootFolderService; _diskProvider = diskProvider; _logger = logger; } @@ -43,9 +46,15 @@ namespace NzbDrone.Core.DiskSpace private IEnumerable GetSeriesRootPaths() { + // Get all series paths and find the correct root folder for each. For each unique root folder path, + // ensure the path exists and get its path root and return all unique path roots. + return _seriesService.GetAllSeriesPaths() - .Where(s => s.Value.IsPathValid(PathValidationType.CurrentOs) && _diskProvider.FolderExists(s.Value)) - .Select(s => _diskProvider.GetPathRoot(s.Value)) + .Where(s => s.Value.IsPathValid(PathValidationType.CurrentOs)) + .Select(s => _rootFolderService.GetBestRootFolderPath(s.Value)) + .Distinct() + .Where(r => _diskProvider.FolderExists(r)) + .Select(r => _diskProvider.GetPathRoot(r)) .Distinct(); } diff --git a/src/NzbDrone.Core/RootFolders/RootFolderService.cs b/src/NzbDrone.Core/RootFolders/RootFolderService.cs index da039e967..09bbf7134 100644 --- a/src/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/src/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Organizer; @@ -30,6 +31,8 @@ namespace NzbDrone.Core.RootFolders private readonly INamingConfigService _namingConfigService; private readonly Logger _logger; + private readonly ICached _cache; + private static readonly HashSet SpecialFolders = new HashSet { "$recycle.bin", @@ -47,6 +50,7 @@ namespace NzbDrone.Core.RootFolders IDiskProvider diskProvider, ISeriesRepository seriesRepository, INamingConfigService namingConfigService, + ICacheManager cacheManager, Logger logger) { _rootFolderRepository = rootFolderRepository; @@ -54,6 +58,8 @@ namespace NzbDrone.Core.RootFolders _seriesRepository = seriesRepository; _namingConfigService = namingConfigService; _logger = logger; + + _cache = cacheManager.GetCache(GetType()); } public List All() @@ -110,13 +116,14 @@ namespace NzbDrone.Core.RootFolders if (!_diskProvider.FolderWritable(rootFolder.Path)) { - throw new UnauthorizedAccessException(string.Format("Root folder path '{0}' is not writable by user '{1}'", rootFolder.Path, Environment.UserName)); + throw new UnauthorizedAccessException($"Root folder path '{rootFolder.Path}' is not writable by user '{Environment.UserName}'"); } _rootFolderRepository.Insert(rootFolder); var seriesPaths = _seriesRepository.AllSeriesPaths(); GetDetails(rootFolder, seriesPaths, true); + _cache.Clear(); return rootFolder; } @@ -124,6 +131,7 @@ namespace NzbDrone.Core.RootFolders public void Remove(int id) { _rootFolderRepository.Delete(id); + _cache.Clear(); } private List GetUnmappedFolders(string path, Dictionary seriesPaths) @@ -186,16 +194,7 @@ namespace NzbDrone.Core.RootFolders public string GetBestRootFolderPath(string path) { - var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path)).MaxBy(r => r.Path.Length); - - if (possibleRootFolder == null) - { - var osPath = new OsPath(path); - - return osPath.Directory.ToString().TrimEnd(osPath.IsUnixPath ? '/' : '\\'); - } - - return possibleRootFolder?.Path; + return _cache.Get(path, () => GetBestRootFolderPathInternal(path), TimeSpan.FromDays(1)); } private void GetDetails(RootFolder rootFolder, Dictionary seriesPaths, bool timeout) @@ -211,5 +210,19 @@ namespace NzbDrone.Core.RootFolders } }).Wait(timeout ? 5000 : -1); } + + private string GetBestRootFolderPathInternal(string path) + { + var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path)).MaxBy(r => r.Path.Length); + + if (possibleRootFolder == null) + { + var osPath = new OsPath(path); + + return osPath.Directory.ToString().TrimEnd(osPath.IsUnixPath ? '/' : '\\'); + } + + return possibleRootFolder.Path; + } } }