using Microsoft.Extensions.Logging; using Roadie.Library; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Data.Context; using Roadie.Library.Encoding; using Roadie.Library.Engines; using Roadie.Library.Extensions; using Roadie.Library.FilePlugins; using Roadie.Library.Identity; using Roadie.Library.MetaData.Audio; using Roadie.Library.Processors; using Roadie.Library.Utility; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; using data = Roadie.Library.Data; namespace Roadie.Api.Services { public class FileDirectoryProcessorService : ServiceBase, IFileDirectoryProcessorService { private List _addedArtistIds = new List(); private List _addedReleaseIds = new List(); private List _addedTrackIds = new List(); public IEnumerable AddedArtistIds => _addedArtistIds.Distinct(); public IEnumerable AddedReleaseIds => _addedReleaseIds.Distinct(); public IEnumerable AddedTrackIds => _addedTrackIds.Distinct(); public int? ProcessLimit { get; set; } private IArtistLookupEngine ArtistLookupEngine { get; } private IAudioMetaDataHelper AudioMetaDataHelper { get; } private IFileProcessor FileProcessor { get; } private IReleaseLookupEngine ReleaseLookupEngine { get; } private IReleaseService ReleaseService { get; } public FileDirectoryProcessorService(IRoadieSettings configuration, IHttpEncoder httpEncoder, IHttpContext httpContext, IRoadieDbContext context, ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine, IFileProcessor fileProcessor, IReleaseLookupEngine releaseLookupEngine, IAudioMetaDataHelper audioMetaDataHelper, IReleaseService releaseService) : base(configuration, httpEncoder, context, cacheManager, logger, httpContext) { ArtistLookupEngine = artistLookupEngine; AudioMetaDataHelper = audioMetaDataHelper; ReleaseLookupEngine = releaseLookupEngine; ReleaseService = releaseService; FileProcessor = fileProcessor; } public static OperationResult DeleteEmptyFolders(DirectoryInfo processingFolder, ILogger logger) { var result = new OperationResult(); try { result.IsSuccess = FolderPathHelper.DeleteEmptyFolders(processingFolder); } catch (Exception ex) { logger.LogError(ex, string.Format("Error Deleting Empty Folder [{0}] Error [{1}]", processingFolder.FullName, ex.Serialize())); } return result; } public async Task> Process(ApplicationUser user, DirectoryInfo folder, bool doJustInfo, int? submissionId = null, bool doDeleteFiles = true) { var sw = new Stopwatch(); sw.Start(); await PreProcessFolder(folder, doJustInfo); var processedFiles = 0; var pluginResultInfos = new List(); var errors = new List(); _addedArtistIds.Clear(); _addedReleaseIds.Clear(); _addedTrackIds.Clear(); FileProcessor.SubmissionId = submissionId; foreach (var file in Directory.EnumerateFiles(folder.FullName, "*.*", SearchOption.AllDirectories) .ToArray()) { var operation = await FileProcessor.Process(file, doJustInfo); if (operation != null && operation.AdditionalData != null && operation.AdditionalData.ContainsKey(PluginResultInfo.AdditionalDataKeyPluginResultInfo)) { pluginResultInfos.Add(operation.AdditionalData[PluginResultInfo.AdditionalDataKeyPluginResultInfo] as PluginResultInfo); processedFiles++; } if (ProcessLimit.HasValue && processedFiles > ProcessLimit.Value) break; } await PostProcessFolder(user, folder, pluginResultInfos, doJustInfo, doDeleteFiles); sw.Stop(); _addedArtistIds.AddRange(ArtistLookupEngine.AddedArtistIds); _addedReleaseIds.AddRange(ReleaseLookupEngine.AddedReleaseIds); _addedTrackIds.AddRange(ReleaseLookupEngine.AddedTrackIds); Logger.LogInformation("Completed! Processed Folder [{0}]: Processed Files [{1}] : Elapsed Time [{2}]", folder.FullName, processedFiles, sw.Elapsed); return new OperationResult { IsSuccess = !errors.Any(), AdditionalData = new Dictionary { { "ProcessedFiles", processedFiles } }, OperationTime = sw.ElapsedMilliseconds }; } /// /// Perform any operations to the given folder and the plugin results after processing /// private async Task PostProcessFolder(ApplicationUser user, DirectoryInfo inboundFolder, IEnumerable pluginResults, bool doJustInfo, bool doDeleteFiles = true) { SimpleContract.Requires(inboundFolder != null, "Invalid InboundFolder"); if (pluginResults != null) { foreach (var releasesInfo in pluginResults.GroupBy(x => x.ReleaseId).Select(x => x.First())) { await ReleaseService.ScanReleaseFolder(user, releasesInfo.ReleaseId, doJustInfo); _addedTrackIds.AddRange(ReleaseService.AddedTrackIds); } } if (!doJustInfo && doDeleteFiles) { var fileExtensionsToDelete = Configuration.FileExtensionsToDelete ?? new string[0]; if (fileExtensionsToDelete.Any()) foreach (var fileInFolder in inboundFolder.GetFiles("*.*", SearchOption.AllDirectories)) { if (fileExtensionsToDelete.Any(x => x.Equals(fileInFolder.Extension, StringComparison.OrdinalIgnoreCase))) { if (!doJustInfo) { fileInFolder.Delete(); Logger.LogTrace("Deleted File [{0}], Was found in in FileExtensionsToDelete", fileInFolder.Name); } } } DeleteEmptyFolders(inboundFolder, Logger); } return true; } /// /// Perform any operations to the given folder before processing /// private Task PreProcessFolder(DirectoryInfo inboundFolder, bool doJustInfo = false) { return Task.FromResult(true); } } }