From 401e19547c95d99dd70229a517b5181adc3c150a 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 movie path roots (cherry picked from commit 63fdf8ca8ff9b22ce4cf8764cc05aad5d1d0ae62) Closes #10219 --- .../DiskSpace/DiskSpaceServiceFixture.cs | 25 ++++++++---- .../DiskSpace/DiskSpaceService.cs | 15 +++++-- .../RootFolders/RootFolderService.cs | 39 ++++++++++++------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs index 211564e0a..8325bf1c9 100644 --- a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs +++ b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Core.DiskSpace; using NzbDrone.Core.Movies; +using NzbDrone.Core.RootFolders; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; @@ -16,14 +17,14 @@ public class DiskSpaceServiceFixture : CoreTest { private string _moviesFolder; private string _moviesFolder2; - private string _droneFactoryFolder; + private string _rootFolder; [SetUp] public void SetUp() { _moviesFolder = @"G:\fasdlfsdf\movies".AsOsAgnostic(); _moviesFolder2 = @"G:\fasdlfsdf\movies2".AsOsAgnostic(); - _droneFactoryFolder = @"G:\dronefactory".AsOsAgnostic(); + _rootFolder = @"G:\fasdlfsdf".AsOsAgnostic(); Mocker.GetMock() .Setup(v => v.GetMounts()) @@ -51,6 +52,13 @@ private void GivenMovies(params Movie[] movies) .Returns(movies.ToDictionary(x => x.Id, x => x.Path)); } + private void GivenRootFolder(string moviePath, string rootFolderPath) + { + Mocker.GetMock() + .Setup(v => v.GetBestRootFolderPath(moviePath, null)) + .Returns(rootFolderPath); + } + private void GivenExistingFolder(string folder) { Mocker.GetMock() @@ -62,8 +70,8 @@ private void GivenExistingFolder(string folder) public void should_check_diskspace_for_movies_folders() { GivenMovies(new Movie { Path = _moviesFolder }); - - GivenExistingFolder(_moviesFolder); + GivenRootFolder(_moviesFolder, _rootFolder); + GivenExistingFolder(_rootFolder); var freeSpace = Subject.GetFreeSpace(); @@ -74,9 +82,9 @@ public void should_check_diskspace_for_movies_folders() public void should_check_diskspace_for_same_root_folder_only_once() { GivenMovies(new Movie { Id = 1, Path = _moviesFolder }, new Movie { Id = 2, Path = _moviesFolder2 }); - - GivenExistingFolder(_moviesFolder); - GivenExistingFolder(_moviesFolder2); + GivenRootFolder(_moviesFolder, _rootFolder); + GivenRootFolder(_moviesFolder, _rootFolder); + GivenExistingFolder(_rootFolder); var freeSpace = Subject.GetFreeSpace(); @@ -87,9 +95,10 @@ public void should_check_diskspace_for_same_root_folder_only_once() } [Test] - public void should_not_check_diskspace_for_missing_movie_folders() + public void should_not_check_diskspace_for_missing_movie_root_folders() { GivenMovies(new Movie { Path = _moviesFolder }); + GivenRootFolder(_moviesFolder, _rootFolder); var freeSpace = Subject.GetFreeSpace(); diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs index 23f0654a7..523ed474a 100644 --- a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Movies; +using NzbDrone.Core.RootFolders; namespace NzbDrone.Core.DiskSpace { @@ -18,14 +19,16 @@ public interface IDiskSpaceService public class DiskSpaceService : IDiskSpaceService { private readonly IMovieService _movieService; + 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(IMovieService movieService, IDiskProvider diskProvider, Logger logger) + public DiskSpaceService(IMovieService movieService, IRootFolderService rootFolderService, IDiskProvider diskProvider, Logger logger) { _movieService = movieService; + _rootFolderService = rootFolderService; _diskProvider = diskProvider; _logger = logger; } @@ -43,9 +46,15 @@ public List GetFreeSpace() private IEnumerable GetMoviesRootPaths() { + // Get all movie 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 _movieService.AllMoviePaths() - .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 52da032b7..fd6ccb9b1 100644 --- a/src/NzbDrone.Core/RootFolders/RootFolderService.cs +++ b/src/NzbDrone.Core/RootFolders/RootFolderService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; @@ -32,6 +33,8 @@ public class RootFolderService : IRootFolderService private readonly INamingConfigService _namingConfigService; private readonly Logger _logger; + private readonly ICached _cache; + private static readonly HashSet SpecialFolders = new HashSet { "$recycle.bin", @@ -50,6 +53,7 @@ public RootFolderService(IRootFolderRepository rootFolderRepository, IMovieRepository movieRepository, IConfigService configService, INamingConfigService namingConfigService, + ICacheManager cacheManager, Logger logger) { _rootFolderRepository = rootFolderRepository; @@ -58,6 +62,8 @@ public RootFolderService(IRootFolderRepository rootFolderRepository, _configService = configService; _namingConfigService = namingConfigService; _logger = logger; + + _cache = cacheManager.GetCache(GetType()); } public List All() @@ -115,7 +121,7 @@ public RootFolder Add(RootFolder rootFolder) 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); @@ -123,6 +129,7 @@ public RootFolder Add(RootFolder rootFolder) var moviePaths = _movieRepository.AllMoviePaths(); GetDetails(rootFolder, moviePaths, true); + _cache.Clear(); return rootFolder; } @@ -130,6 +137,7 @@ public RootFolder Add(RootFolder rootFolder) public void Remove(int id) { _rootFolderRepository.Delete(id); + _cache.Clear(); } private List GetUnmappedFolders(string path, Dictionary moviePaths) @@ -201,18 +209,7 @@ public RootFolder Get(int id, bool timeout) public string GetBestRootFolderPath(string path, List rootFolders = null) { - var allRootFoldersToConsider = rootFolders ?? All(); - - var possibleRootFolder = allRootFoldersToConsider.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, rootFolders), TimeSpan.FromDays(1)); } private void GetDetails(RootFolder rootFolder, Dictionary moviePaths, bool timeout) @@ -228,5 +225,21 @@ private void GetDetails(RootFolder rootFolder, Dictionary moviePath } }).Wait(timeout ? 5000 : -1); } + + private string GetBestRootFolderPathInternal(string path, List rootFolders = null) + { + var allRootFoldersToConsider = rootFolders ?? All(); + + var possibleRootFolder = allRootFoldersToConsider.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; + } } }