diff --git a/API/API.csproj b/API/API.csproj index 52664396f..bdfb017a6 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 4800a8cbb..5aee3e0bc 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -99,5 +99,16 @@ namespace API.Controllers return BadRequest("Not Implemented"); } + + [Authorize(Policy = "RequireAdminRole")] + [HttpGet("scan")] + public async Task ScanLibrary(int libraryId) + { + var library = await _libraryRepository.GetLibraryForIdAsync(libraryId); + + _directoryService.ScanLibrary(library); + + return Ok(); + } } } \ No newline at end of file diff --git a/API/Data/LibraryRepository.cs b/API/Data/LibraryRepository.cs index 68e6371bd..b7bd978a1 100644 --- a/API/Data/LibraryRepository.cs +++ b/API/Data/LibraryRepository.cs @@ -37,6 +37,15 @@ namespace API.Data .Include(f => f.Folders) .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); } + + public async Task GetLibraryForIdAsync(int libraryId) + { + return await _context.Library + .Where(x => x.Id == libraryId) + .Include(f => f.Folders) + .ProjectTo(_mapper.ConfigurationProvider).SingleAsync(); + } + public async Task LibraryExists(string libraryName) { diff --git a/API/Interfaces/IDirectoryService.cs b/API/Interfaces/IDirectoryService.cs index 87a2b9f98..3c7151794 100644 --- a/API/Interfaces/IDirectoryService.cs +++ b/API/Interfaces/IDirectoryService.cs @@ -1,10 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; +using API.DTOs; +using API.Entities; namespace API.Interfaces { public interface IDirectoryService { IEnumerable ListDirectory(string rootPath); + + void ScanLibrary(LibraryDto library); } } \ No newline at end of file diff --git a/API/Interfaces/ILibraryRepository.cs b/API/Interfaces/ILibraryRepository.cs index 3f929efda..ae2cf88d8 100644 --- a/API/Interfaces/ILibraryRepository.cs +++ b/API/Interfaces/ILibraryRepository.cs @@ -17,6 +17,6 @@ namespace API.Interfaces /// Task LibraryExists(string libraryName); - Task> GetLibrariesForUserAsync(AppUser user); + public Task GetLibraryForIdAsync(int libraryId); } } \ No newline at end of file diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 49de3db48..1109f2305 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -1,16 +1,28 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Security; +using System.Threading; using System.Threading.Tasks; +using API.DTOs; using API.Interfaces; +using Microsoft.Extensions.Logging; namespace API.Services { public class DirectoryService : IDirectoryService { - /// + private readonly ILogger _logger; + + public DirectoryService(ILogger logger) + { + _logger = logger; + } + + /// /// Lists out top-level folders for a given directory. Filters out System and Hidden folders. /// /// Absolute path @@ -27,5 +39,132 @@ namespace API.Services return dirs; } + + public void ScanLibrary(LibraryDto library) + { + foreach (var folderPath in library.Folders) + { + try { + TraverseTreeParallelForEach(folderPath, (f) => + { + // Exceptions are no-ops. + try { + // Do nothing with the data except read it. + //byte[] data = File.ReadAllBytes(f); + ProcessManga(f); + } + catch (FileNotFoundException) {} + catch (IOException) {} + catch (UnauthorizedAccessException) {} + catch (SecurityException) {} + // Display the filename. + Console.WriteLine(f); + }); + } + catch (ArgumentException) { + Console.WriteLine(@"The directory 'C:\Program Files' does not exist."); + } + } + } + + private static void ProcessManga(string filename) + { + Console.WriteLine($"Found {filename}"); + } + + public static void TraverseTreeParallelForEach(string root, Action action) + { + //Count of files traversed and timer for diagnostic output + int fileCount = 0; + var sw = Stopwatch.StartNew(); + + // Determine whether to parallelize file processing on each folder based on processor count. + int procCount = System.Environment.ProcessorCount; + + // Data structure to hold names of subfolders to be examined for files. + Stack dirs = new Stack(); + + if (!Directory.Exists(root)) { + throw new ArgumentException(); + } + dirs.Push(root); + + while (dirs.Count > 0) { + string 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 = Directory.GetFiles(currentDir); + } + 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, loopState, localCount) => + { action(file); + return (int) ++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); + } + + // For diagnostic purposes. + Console.WriteLine("Processed {0} files in {1} milliseconds", fileCount, sw.ElapsedMilliseconds); + } } } \ No newline at end of file