using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using API.DTOs; using API.Interfaces.Services; using Microsoft.Extensions.Logging; using NetVips; namespace API.Services { public class DirectoryService : IDirectoryService { private readonly ILogger _logger; public DirectoryService(ILogger logger) { _logger = logger; } /// /// Given a set of regex search criteria, get files in the given path. /// /// Directory to search /// Regex version of search pattern (ie \.mp3|\.mp4). Defaults to * meaning all files. /// SearchOption to use, defaults to TopDirectoryOnly /// List of file paths private static IEnumerable GetFilesWithCertainExtensions(string path, string searchPatternExpression = "", SearchOption searchOption = SearchOption.TopDirectoryOnly) { if (!Directory.Exists(path)) return ImmutableList.Empty; var reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase); return Directory.EnumerateFiles(path, "*", searchOption) .Where(file => reSearchPattern.IsMatch(Path.GetExtension(file))); } public string[] GetFiles(string path, string searchPatternExpression = "") { if (searchPatternExpression != string.Empty) { return GetFilesWithCertainExtensions(path, searchPatternExpression).ToArray(); } return !Directory.Exists(path) ? Array.Empty() : Directory.GetFiles(path); } public bool ExistOrCreate(string directoryPath) { var di = new DirectoryInfo(directoryPath); if (di.Exists) return true; try { Directory.CreateDirectory(directoryPath); } catch (Exception ex) { _logger.LogError(ex, "There was an issue creating directory: {Directory}", directoryPath); return false; } return true; } public void ClearAndDeleteDirectory(string directoryPath) { DirectoryInfo di = new DirectoryInfo(directoryPath); foreach (var file in di.EnumerateFiles()) { file.Delete(); } foreach (var dir in di.EnumerateDirectories()) { dir.Delete(true); } di.Delete(true); } public IEnumerable ListDirectory(string rootPath) { if (!Directory.Exists(rootPath)) return ImmutableList.Empty; var di = new DirectoryInfo(rootPath); var dirs = di.GetDirectories() .Where(dir => !(dir.Attributes.HasFlag(FileAttributes.Hidden) || dir.Attributes.HasFlag(FileAttributes.System))) .Select(d => d.Name).ToImmutableList(); return dirs; } public async Task ReadImageAsync(string imagePath) { if (!File.Exists(imagePath)) { _logger.LogError("Image does not exist on disk"); return null; } using var image = Image.NewFromFile(imagePath); return new ImageDto { Content = await File.ReadAllBytesAsync(imagePath), Filename = Path.GetFileNameWithoutExtension(imagePath), FullPath = Path.GetFullPath(imagePath), Width = image.Width, Height = image.Height, Format = image.Format, }; } /// /// Recursively scans files and applies an action on them. This uses as many cores the underlying PC has to speed /// up processing. /// /// Directory to scan /// Action to apply on file path /// Regex pattern to search against /// public static int TraverseTreeParallelForEach(string root, Action action, string searchPattern) { //Count of files traversed and timer for diagnostic output var fileCount = 0; // Determine whether to parallelize file processing on each folder based on processor count. var procCount = Environment.ProcessorCount; // Data structure to hold names of subfolders to be examined for files. var dirs = new Stack(); if (!Directory.Exists(root)) { throw new ArgumentException("The directory doesn't exist"); } dirs.Push(root); while (dirs.Count > 0) { var currentDir = dirs.Pop(); string[] subDirs; string[] files; try { subDirs = Directory.GetDirectories(currentDir); } // Thrown if we do not have discovery permission on the directory. catch (UnauthorizedAccessException e) { Console.WriteLine(e.Message); continue; } // Thrown if another process has deleted the directory after we retrieved its name. catch (DirectoryNotFoundException e) { Console.WriteLine(e.Message); continue; } try { files = GetFilesWithCertainExtensions(currentDir, searchPattern) .ToArray(); } catch (UnauthorizedAccessException e) { Console.WriteLine(e.Message); continue; } catch (DirectoryNotFoundException e) { Console.WriteLine(e.Message); continue; } catch (IOException e) { Console.WriteLine(e.Message); continue; } // Execute in parallel if there are enough files in the directory. // Otherwise, execute sequentially.Files are opened and processed // synchronously but this could be modified to perform async I/O. try { if (files.Length < procCount) { foreach (var file in files) { action(file); fileCount++; } } else { Parallel.ForEach(files, () => 0, (file, _, localCount) => { action(file); return ++localCount; }, (c) => { Interlocked.Add(ref fileCount, c); }); } } catch (AggregateException ae) { ae.Handle((ex) => { if (ex is UnauthorizedAccessException) { // Here we just output a message and go on. Console.WriteLine(ex.Message); return true; } // Handle other exceptions here if necessary... return false; }); } // Push the subdirectories onto the stack for traversal. // This could also be done before handing the files. foreach (string str in subDirs) dirs.Push(str); } return fileCount; } } }