diff --git a/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs b/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs index 0126d84fd..5ec9d5215 100644 --- a/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs +++ b/src/NzbDrone.Api/DiskSpace/DiskSpaceModule.cs @@ -1,47 +1,22 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using NzbDrone.Common; +using System.Collections.Generic; +using NzbDrone.Core.DiskSpace; namespace NzbDrone.Api.DiskSpace { public class DiskSpaceModule :NzbDroneRestModule { - private readonly IDiskProvider _diskProvider; + private readonly IDiskSpaceService _diskSpaceService; - public DiskSpaceModule(IDiskProvider diskProvider):base("diskspace") + public DiskSpaceModule(IDiskSpaceService diskSpaceService) + :base("diskspace") { - _diskProvider = diskProvider; + _diskSpaceService = diskSpaceService; GetResourceAll = GetFreeSpace; } public List GetFreeSpace() { - return (_diskProvider.GetFixedDrives() - .Select( - x => - new DiskSpaceResource() - { - DriveLetter = x, - FreeSpace = _diskProvider.GetAvailableSpace(x).Value, - TotalSpace = _diskProvider.GetTotalSize(x).Value - })).ToList(); - } - - static string SizeSuffix(Int64 value) - { - string[] suffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; - int i = 0; - decimal dValue = (decimal)value; - while (Math.Round(dValue / 1024) >= 1) - { - dValue /= 1024; - i++; - } - - return string.Format("{0:n1}{1}", dValue, suffixes[i]); + return ToListResource(_diskSpaceService.GetFreeSpace); } } } diff --git a/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs b/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs index 261ae6305..039e4b0c3 100644 --- a/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs +++ b/src/NzbDrone.Api/DiskSpace/DiskSpaceResource.cs @@ -8,7 +8,8 @@ namespace NzbDrone.Api.DiskSpace { public class DiskSpaceResource : RestResource { - public string DriveLetter { get; set; } + public string Path { get; set; } + public string Label { get; set; } public Int64 FreeSpace { get; set; } public Int64 TotalSpace { get; set; } } diff --git a/src/NzbDrone.Common/DiskProvider.cs b/src/NzbDrone.Common/DiskProvider.cs index a075b5e10..a3d9e0255 100644 --- a/src/NzbDrone.Common/DiskProvider.cs +++ b/src/NzbDrone.Common/DiskProvider.cs @@ -44,6 +44,7 @@ public interface IDiskProvider void EmptyFolder(string path); string[] GetFixedDrives(); long? GetTotalSize(string path); + string GetVolumeLabel(string path); } public class DiskProvider : IDiskProvider @@ -324,30 +325,6 @@ public void InheritFolderPermissions(string filename) return DriveFreeSpaceEx(root); } - private static long DriveFreeSpaceEx(string folderName) - { - if (string.IsNullOrEmpty(folderName)) - { - throw new ArgumentNullException("folderName"); - } - - if (!folderName.EndsWith("\\")) - { - folderName += '\\'; - } - - ulong free = 0; - ulong dummy1 = 0; - ulong dummy2 = 0; - - if (GetDiskFreeSpaceEx(folderName, out free, out dummy1, out dummy2)) - { - return (long)free; - } - - return 0; - } - public string ReadAllText(string filePath) { Ensure.That(() => filePath).IsValidPath(); @@ -485,7 +462,97 @@ public string[] GetFixedDrives() public long? GetTotalSize(string path) { - return (DriveInfo.GetDrives().Single(x => x.Name == path)).TotalSize; + Ensure.That(() => path).IsValidPath(); + + var root = GetPathRoot(path); + + if (!FolderExists(root)) + throw new DirectoryNotFoundException(root); + + if (OsInfo.IsLinux) + { + var drives = DriveInfo.GetDrives(); + + foreach (var drive in drives) + { + try + { + if (drive.IsReady && path.StartsWith(drive.Name, StringComparison.CurrentCultureIgnoreCase)) + { + return drive.TotalSize; + } + } + catch (InvalidOperationException e) + { + Logger.ErrorException("Couldn't get total space for " + path, e); + } + } + + return null; + } + + + return DriveTotalSizeEx(root); + } + + public string GetVolumeLabel(string path) + { + var driveInfo = DriveInfo.GetDrives().SingleOrDefault(d => d.Name == path); + + if (driveInfo == null) + { + return null; + } + + return driveInfo.VolumeLabel; + } + + private static long DriveFreeSpaceEx(string folderName) + { + if (string.IsNullOrEmpty(folderName)) + { + throw new ArgumentNullException("folderName"); + } + + if (!folderName.EndsWith("\\")) + { + folderName += '\\'; + } + + ulong free = 0; + ulong dummy1 = 0; + ulong dummy2 = 0; + + if (GetDiskFreeSpaceEx(folderName, out free, out dummy1, out dummy2)) + { + return (long)free; + } + + return 0; + } + + private static long DriveTotalSizeEx(string folderName) + { + if (string.IsNullOrEmpty(folderName)) + { + throw new ArgumentNullException("folderName"); + } + + if (!folderName.EndsWith("\\")) + { + folderName += '\\'; + } + + ulong total = 0; + ulong dummy1 = 0; + ulong dummy2 = 0; + + if (GetDiskFreeSpaceEx(folderName, out dummy1, out total, out dummy2)) + { + return (long)total; + } + + return 0; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpace.cs b/src/NzbDrone.Core/DiskSpace/DiskSpace.cs new file mode 100644 index 000000000..137ea6cbe --- /dev/null +++ b/src/NzbDrone.Core/DiskSpace/DiskSpace.cs @@ -0,0 +1,12 @@ +using System; + +namespace NzbDrone.Core.DiskSpace +{ + public class DiskSpace + { + public String Path { get; set; } + public String Label { get; set; } + public long FreeSpace { get; set; } + public long TotalSpace { get; set; } + } +} diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs new file mode 100644 index 000000000..98c5e64d7 --- /dev/null +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.DiskSpace +{ + public interface IDiskSpaceService + { + List GetFreeSpace(); + } + + public class DiskSpaceService : IDiskSpaceService + { + private readonly ISeriesService _seriesService; + private readonly IConfigService _configService; + private readonly IDiskProvider _diskProvider; + private readonly Logger _logger; + + public DiskSpaceService(ISeriesService seriesService, IConfigService configService, IDiskProvider diskProvider, Logger logger) + { + _seriesService = seriesService; + _configService = configService; + _diskProvider = diskProvider; + _logger = logger; + } + + public List GetFreeSpace() + { + var diskSpace = new List(); + diskSpace.AddRange(GetSeriesFreeSpace()); + diskSpace.AddRange(GetDroneFactoryFreeSpace()); + diskSpace.AddRange(GetFixedDisksFreeSpace()); + + return diskSpace.DistinctBy(d => d.Path).ToList(); + } + + private IEnumerable GetSeriesFreeSpace() + { + var seriesRootPaths = _seriesService.GetAllSeries().Select(s => _diskProvider.GetPathRoot(s.Path)).Distinct(); + + return GetDiskSpace(seriesRootPaths); + } + + private IEnumerable GetDroneFactoryFreeSpace() + { + if (!String.IsNullOrWhiteSpace(_configService.DownloadedEpisodesFolder)) + { + return GetDiskSpace(new[] { _configService.DownloadedEpisodesFolder }); + } + + return new List(); + } + + private IEnumerable GetFixedDisksFreeSpace() + { + return GetDiskSpace(_diskProvider.GetFixedDrives()); + } + + private IEnumerable GetDiskSpace(IEnumerable paths) + { + foreach (var path in paths) + { + DiskSpace diskSpace = null; + + try + { + var freeSpace = _diskProvider.GetAvailableSpace(path).Value; + var totalSpace = _diskProvider.GetTotalSize(path).Value; + + diskSpace = new DiskSpace + { + Path = path, + FreeSpace = freeSpace, + TotalSpace = totalSpace + }; + + diskSpace.Label = _diskProvider.GetVolumeLabel(path); + } + catch (Exception ex) + { + _logger.WarnException("Unable to get free space for: " + path, ex); + } + + if (diskSpace != null) + { + yield return diskSpace; + } + } + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 8fcb4cc1d..5c25f088e 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -219,6 +219,8 @@ + + diff --git a/src/UI/System/Info/DiskSpace/DiskSpaceLayout.js b/src/UI/System/Info/DiskSpace/DiskSpaceLayout.js index 9a8ef6026..ce8717d76 100644 --- a/src/UI/System/Info/DiskSpace/DiskSpaceLayout.js +++ b/src/UI/System/Info/DiskSpace/DiskSpaceLayout.js @@ -5,8 +5,9 @@ define([ 'backgrid', 'System/Info/DiskSpace/DiskSpaceCollection', 'Shared/LoadingView', + 'System/Info/DiskSpace/DiskSpacePathCell', 'Cells/FileSizeCell' -], function (vent,Marionette,Backgrid,DiskSpaceCollection,LoadingView,FileSizeCell) { +], function (vent,Marionette,Backgrid,DiskSpaceCollection,LoadingView, DiskSpacePathCell, FileSizeCell) { return Marionette.Layout.extend({ template: 'System/Info/DiskSpace/DiskSpaceLayoutTemplate', @@ -16,21 +17,19 @@ define([ columns: [ { - name: 'driveLetter', - label: 'Drive', - cell: 'string' + name: 'path', + label: 'Location', + cell: DiskSpacePathCell }, { name: 'freeSpace', label: 'Free Space', - cell: FileSizeCell, - sortable:true + cell: FileSizeCell }, { name: 'totalSpace', label: 'Total Space', - cell: FileSizeCell, - sortable:true + cell: FileSizeCell } ], diff --git a/src/UI/System/Info/DiskSpace/DiskSpacePathCell.js b/src/UI/System/Info/DiskSpace/DiskSpacePathCell.js new file mode 100644 index 000000000..bc9caa452 --- /dev/null +++ b/src/UI/System/Info/DiskSpace/DiskSpacePathCell.js @@ -0,0 +1,28 @@ +'use strict'; + +define( + [ + 'backgrid' + ], function (Backgrid) { + return Backgrid.Cell.extend({ + + className: 'disk-space-path-cell', + + render: function () { + this.$el.empty(); + + var path = this.model.get('path'); + var label = this.model.get('label'); + + var contents = path; + + if (label) { + contents += ' ({0})'.format(label); + } + + this.$el.html(contents); + + return this; + } + }); + });