Kavita/API/Services/Tasks/Scanner/FileScanner.cs

146 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs.Internal.Scanner;
using API.Entities.Enums;
using API.Extensions;
using Kavita.Common.Helpers;
namespace API.Services.Tasks.Scanner;
public interface IFileScanner
{
// TODO: Move this to the scanner service
//Task ScanLibrary(int libraryId, bool forceScan = false);
List<ScannedDirectory> ScanFiles(ScannerOption options);
}
public class FileScanner : IFileScanner
{
private readonly IDirectoryService _directoryService;
private readonly IUnitOfWork _unitOfWork;
public FileScanner(IDirectoryService directoryService, IUnitOfWork unitOfWork)
{
_directoryService = directoryService;
_unitOfWork = unitOfWork;
}
// public async Task ScanLibrary(int libraryId, bool forceScan = false)
// {
// var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId,
// LibraryIncludes.Folders | LibraryIncludes.ExcludePatterns | LibraryIncludes.FileTypes);
//
// if (library == null)
// {
// return;
// }
//
// // Create a ScannerOption
// var options = new ScannerOption()
// {
// FileTypePattern = library.LibraryFileTypes.Select(s => s.FileTypeGroup).ToList(),
// ForceScan = forceScan,
// ExcludePatterns = [.. library.LibraryExcludePatterns.Select(s => s.Pattern)],
// FolderPaths = [.. library.Folders.Select(f => Parser.Parser.NormalizePath(f.Path))]
// };
//
//
// // Find all the information about the directories and their files
// var files = ScanFiles(options);
//
// // Parse said information
//
//
// return;
// }
public List<ScannedDirectory> ScanFiles(ScannerOption options)
{
// Validate input options
if (options == null || options.FolderPaths.Count == 0 || options.FileTypePattern.Count == 0)
{
return [];
}
// Build the file extensions regex from the file type patterns
var fileExtensions = string.Join("|", options.FileTypePattern.Select(l => l.GetRegex()));
if (string.IsNullOrWhiteSpace(fileExtensions))
{
return [];
}
var matcher = BuildMatcher(options.ExcludePatterns);
var scannedDirectories = new List<ScannedDirectory>();
foreach (var folderPath in options.FolderPaths)
{
var normalizedFolderPath = Parser.Parser.NormalizePath(folderPath);
var allDirectories = _directoryService.GetAllDirectories(normalizedFolderPath, matcher)
.Select(Parser.Parser.NormalizePath)
.OrderByDescending(d => d.Length)
.ToList();
// TODO: Optimization: If allDirectories is large, split into Parallel tasks
foreach (var directory in allDirectories)
{
var files = _directoryService.ScanFiles(directory, fileExtensions, matcher)
.Select(filePath =>
{
// Gather metadata for each file
var lastModifiedUtc = _directoryService.GetLastWriteTime(filePath).ToUniversalTime();
var format = Parser.Parser.ParseFormat(filePath);
return new ScannedFile
{
FilePath = filePath,
LastModifiedUtc = lastModifiedUtc,
Format = format
};
})
.ToList();
// Skip directories with no valid files
if (files.Count == 0)
{
continue;
}
// Get directory's metadata (TODO: Replace with _directoryService.GetLastWriteTime(folder).Truncate(TimeSpan.TicksPerSecond);)
//var directoryLastModifiedUtc = files.Max(f => f.LastModifiedUtc);
var directoryLastModifiedUtc = _directoryService.GetLastWriteTime(normalizedFolderPath).Truncate(TimeSpan.TicksPerSecond);
// Add the directory and its files to the result
scannedDirectories.Add(new ScannedDirectory
{
FolderRoot = folderPath,
DirectoryPath = directory,
LastModifiedUtc = directoryLastModifiedUtc,
Files = files
});
}
}
return scannedDirectories;
}
private static GlobMatcher BuildMatcher(List<string> excludePatterns)
{
var matcher = new GlobMatcher();
foreach (var pattern in excludePatterns.Where(p => !string.IsNullOrEmpty(p)))
{
matcher.AddExclude(pattern);
}
return matcher;
}
}