using System; using System.Collections.Generic; using System.IO; using System.Linq; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; namespace NzbDrone.Common.Disk { public interface IFileSystemLookupService { FileSystemResult LookupContents(string query, bool includeFiles, bool allowFoldersWithoutTrailingSlashes); } public class FileSystemLookupService : IFileSystemLookupService { private readonly IDiskProvider _diskProvider; private readonly IRuntimeInfo _runtimeInfo; private readonly HashSet _setToRemove = new HashSet { // Windows "boot", "bootmgr", "cache", "msocache", "recovery", "$recycle.bin", "recycler", "system volume information", "temporary internet files", "windows", // OS X ".fseventd", ".spotlight", ".trashes", ".vol", "cachedmessages", "caches", "trash", // QNAP ".@__thumb", // Synology "@eadir", "#recycle" }; public FileSystemLookupService(IDiskProvider diskProvider, IRuntimeInfo runtimeInfo) { _diskProvider = diskProvider; _runtimeInfo = runtimeInfo; } public FileSystemResult LookupContents(string query, bool includeFiles, bool allowFoldersWithoutTrailingSlashes) { if (query.IsNullOrWhiteSpace()) { if (OsInfo.IsWindows) { var result = new FileSystemResult(); result.Directories = GetDrives(); return result; } query = "/"; } if ( allowFoldersWithoutTrailingSlashes && query.IsPathValid(PathValidationType.CurrentOs) && _diskProvider.FolderExists(query)) { return GetResult(query, includeFiles); } var lastSeparatorIndex = query.LastIndexOf(Path.DirectorySeparatorChar); var path = query.Substring(0, lastSeparatorIndex + 1); if (lastSeparatorIndex != -1) { return GetResult(path, includeFiles); } return new FileSystemResult(); } private List GetDrives() { return _diskProvider.GetMounts() .Where(d => { // Fow Windows Services, exclude mapped network drives. if (_runtimeInfo.IsWindowsService) { return d.DriveType != DriveType.Network; } return true; }) .Select(d => new FileSystemModel { Type = FileSystemEntityType.Drive, Name = d.VolumeName, Path = d.RootDirectory, LastModified = null }) .ToList(); } private FileSystemResult GetResult(string path, bool includeFiles) { var result = new FileSystemResult(); try { result.Parent = GetParent(path); result.Directories = GetDirectories(path); if (includeFiles) { result.Files = GetFiles(path); } } catch (DirectoryNotFoundException) { return new FileSystemResult { Parent = GetParent(path) }; } catch (ArgumentException) { return new FileSystemResult(); } catch (IOException) { return new FileSystemResult { Parent = GetParent(path) }; } catch (UnauthorizedAccessException) { return new FileSystemResult { Parent = GetParent(path) }; } return result; } private List GetDirectories(string path) { var directories = _diskProvider.GetDirectoryInfos(path) .OrderBy(d => d.Name) .Select(d => new FileSystemModel { Name = d.Name, Path = GetDirectoryPath(d.FullName.GetActualCasing()), LastModified = d.LastWriteTimeUtc, Type = FileSystemEntityType.Folder }) .ToList(); directories.RemoveAll(d => _setToRemove.Contains(d.Name.ToLowerInvariant())); return directories; } private List GetFiles(string path) { return _diskProvider.GetFileInfos(path) .OrderBy(d => d.Name) .Select(d => new FileSystemModel { Name = d.Name, Path = d.FullName.GetActualCasing(), LastModified = d.LastWriteTimeUtc, Extension = d.Extension, Size = d.Length, Type = FileSystemEntityType.File }) .ToList(); } private static string GetVolumeName(IMount mountInfo) { if (mountInfo.VolumeLabel.IsNullOrWhiteSpace()) { return mountInfo.Name; } return $"{mountInfo.Name} ({mountInfo.VolumeLabel})"; } private string GetDirectoryPath(string path) { if (path.Last() != Path.DirectorySeparatorChar) { path += Path.DirectorySeparatorChar; } return path; } private static string GetParent(string path) { var di = new DirectoryInfo(path); if (di.Parent != null) { var parent = di.Parent.FullName; if (parent.Last() != Path.DirectorySeparatorChar) { parent += Path.DirectorySeparatorChar; } return parent; } if (!path.Equals("/")) { return string.Empty; } return null; } } }