diff --git a/PARR.AIHIT/AIHIT/SyncherInstaller.cs b/PARR.AIHIT/AIHIT/SyncherInstaller.cs index 9ded749e..5921eea4 100644 --- a/PARR.AIHIT/AIHIT/SyncherInstaller.cs +++ b/PARR.AIHIT/AIHIT/SyncherInstaller.cs @@ -9,7 +9,7 @@ namespace PARR.AIHIT { public static class SyncherInstaller { - public static void InstallerSyncerServices(this IServiceCollection services, IConfiguration configuration) + public static void InstallSyncerServices(this IServiceCollection services, IConfiguration configuration) { var syncherSettings = new SyncherSettings(); configuration.GetSection(nameof(SyncherSettings)).Bind(syncherSettings); diff --git a/PARR.API.sln b/PARR.API.sln index 09641c6a..099c900c 100644 --- a/PARR.API.sln +++ b/PARR.API.sln @@ -22,7 +22,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{AB278172-BFB4-4D54-9022-2D2A5B3338D7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.BLL", "PARR.BLL\PARR.BLL.csproj", "{7BAB9170-743D-4542-8881-9B161EE81BB6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PARR.BLL", "PARR.BLL\PARR.BLL.csproj", "{7BAB9170-743D-4542-8881-9B161EE81BB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.EsppTemplateSyncWorker", "PARR.EsppTemplateSyncWorker\PARR.EsppTemplateSyncWorker.csproj", "{7FBD65FE-26FE-4E60-AD1F-20AAC6F88C7A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.EsppTemplateSync", "PARR.EsppTemplateSync\PARR.EsppTemplateSync.csproj", "{39FD9AAD-0792-46B2-A935-42486A993C32}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +61,14 @@ Global {7BAB9170-743D-4542-8881-9B161EE81BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {7BAB9170-743D-4542-8881-9B161EE81BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {7BAB9170-743D-4542-8881-9B161EE81BB6}.Release|Any CPU.Build.0 = Release|Any CPU + {7FBD65FE-26FE-4E60-AD1F-20AAC6F88C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FBD65FE-26FE-4E60-AD1F-20AAC6F88C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FBD65FE-26FE-4E60-AD1F-20AAC6F88C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FBD65FE-26FE-4E60-AD1F-20AAC6F88C7A}.Release|Any CPU.Build.0 = Release|Any CPU + {39FD9AAD-0792-46B2-A935-42486A993C32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FD9AAD-0792-46B2-A935-42486A993C32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FD9AAD-0792-46B2-A935-42486A993C32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FD9AAD-0792-46B2-A935-42486A993C32}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PARR.BLL/Services/Implementations/FileService.cs b/PARR.BLL/Services/Implementations/FileService.cs index ee42578f..439a6daf 100644 --- a/PARR.BLL/Services/Implementations/FileService.cs +++ b/PARR.BLL/Services/Implementations/FileService.cs @@ -99,5 +99,26 @@ namespace PARR.BLL.Services.Implementations //return false; } } + + public bool DeleteFile(string path) + { + logger.LogInformation($"Удаляю файл: {path}"); + + try + { + if (!File.Exists(path)) + return true; + + File.Delete(path); + logger.LogInformation($"Файл удален: {path}"); + + return true; + } + catch (Exception e) + { + logger.LogError(e, $"Ошибка при удалении файла {path}"); + return false; + } + } } } diff --git a/PARR.BLL/Services/Interfaces/IFileService.cs b/PARR.BLL/Services/Interfaces/IFileService.cs index 1ffc8563..3d0a36f1 100644 --- a/PARR.BLL/Services/Interfaces/IFileService.cs +++ b/PARR.BLL/Services/Interfaces/IFileService.cs @@ -22,5 +22,12 @@ namespace PARR.BLL.Services.Interfaces /// /// bool IsExtensionAllowed(IFormFile formFile, string[] allowedExtensions); + + /// + /// Удалить файл + /// + /// Полный путь к файлу + /// + bool DeleteFile(string path); } } diff --git a/PARR.EsppTemplateSync/EsppTemplateSyncInstaller.cs b/PARR.EsppTemplateSync/EsppTemplateSyncInstaller.cs new file mode 100644 index 00000000..6dc8b6f2 --- /dev/null +++ b/PARR.EsppTemplateSync/EsppTemplateSyncInstaller.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using PARR.BLL; +using PARR.DAL; +using PARR.EsppTemplateSync.Services; + +namespace PARR.EsppTemplateSync +{ + public static class EsppTemplateSyncInstaller + { + public static void InstallEsppTemplateSyncServices(this IServiceCollection services, IConfiguration configuration) + { + services.InstallBllServices(configuration); + services.InstallDalServices(configuration); + + services.AddTransient(); + services.AddTransient(); + } + } +} diff --git a/PARR.EsppTemplateSync/ITemplateSyncer.cs b/PARR.EsppTemplateSync/ITemplateSyncer.cs new file mode 100644 index 00000000..3570eb51 --- /dev/null +++ b/PARR.EsppTemplateSync/ITemplateSyncer.cs @@ -0,0 +1,8 @@ +namespace PARR.EsppTemplateSync +{ + public interface ITemplateSyncer + { + void StartWatching(); + void StopWatching(); + } +} diff --git a/PARR.EsppTemplateSync/PARR.EsppTemplateSync.csproj b/PARR.EsppTemplateSync/PARR.EsppTemplateSync.csproj new file mode 100644 index 00000000..21190861 --- /dev/null +++ b/PARR.EsppTemplateSync/PARR.EsppTemplateSync.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/PARR.EsppTemplateSync/Services/IParserService.cs b/PARR.EsppTemplateSync/Services/IParserService.cs new file mode 100644 index 00000000..118b1f32 --- /dev/null +++ b/PARR.EsppTemplateSync/Services/IParserService.cs @@ -0,0 +1,7 @@ +namespace PARR.EsppTemplateSync.Services +{ + internal interface IParserService + { + Task ParseAsync(string path); + } +} diff --git a/PARR.EsppTemplateSync/Services/ParserService.cs b/PARR.EsppTemplateSync/Services/ParserService.cs new file mode 100644 index 00000000..f240954f --- /dev/null +++ b/PARR.EsppTemplateSync/Services/ParserService.cs @@ -0,0 +1,15 @@ +namespace PARR.EsppTemplateSync.Services +{ + internal class ParserService : IParserService + { + public async Task ParseAsync(string path) + { + //TODO: + + bool isSuccess = true; + + + return isSuccess; + } + } +} diff --git a/PARR.EsppTemplateSync/TemplateSyncer.cs b/PARR.EsppTemplateSync/TemplateSyncer.cs new file mode 100644 index 00000000..0844928b --- /dev/null +++ b/PARR.EsppTemplateSync/TemplateSyncer.cs @@ -0,0 +1,113 @@ +using Microsoft.Extensions.Logging; +using PARR.BLL.Services.Interfaces; +using PARR.BLL.Settings; +using PARR.EsppTemplateSync.Services; + +namespace PARR.EsppTemplateSync +{ + internal class TemplateSyncer : ITemplateSyncer + { + private readonly ILogger logger; + private readonly IParserService parserService; + private readonly IFileService fileService; + private readonly string dirPath; + private readonly string filterExtensions; + + private FileSystemWatcher? watcher; + + public TemplateSyncer( + StorageSettings storageSettings, + ILogger logger, + IParserService parserService, + IFileService fileService + ) + { + this.logger = logger; + this.parserService = parserService; + this.fileService = fileService; + + if (storageSettings.EsppTemplates == null) + { + logger.LogError("Нет секции настроек хранилища. StorageSettings, EsppTemplates"); + throw new Exception("Нет секции настроек хранилища. StorageSettings, EsppTemplates"); + } + + string dirPath = Path.Combine(storageSettings.EsppTemplates.TemplatePath); + this.dirPath = Path.Combine(storageSettings.StoragePath, dirPath); + + filterExtensions = string.Join(",", storageSettings.EsppTemplates!.AllowedExtensions).Replace(".", "*."); + } + + public void StartWatching() + { + //смотрим, доступна ли папка + if (!Directory.Exists(dirPath)) + { + logger.LogError($"Не найдена папка: {dirPath}."); + throw new Exception($"Не найдена папка: {dirPath}."); + } + + + // сначала обрабатываем существующие файлы, до тех пор пока все не обработаем + // после обработки всех файлов, запускаем вотчер + GetExistingFilesAsync(); + + watcher = new FileSystemWatcher(dirPath); + watcher.NotifyFilter = NotifyFilters.FileName; + //watcher.Changed += OnChanged; + //todo: тут await??? + watcher.Created += OnChanged; + + watcher.Filter = filterExtensions; + watcher.EnableRaisingEvents = true; + + logger.LogInformation($"--- --- --- FileWatcher запущен --- --- ---"); + } + + public void StopWatching() + { + watcher?.Dispose(); + logger.LogInformation($"=== === === FileWatcher остановлен === === ==="); + } + + private async void OnChanged(object sender, FileSystemEventArgs e) + { + var path = e.FullPath; + await ParseFileAsync(path); + } + + private async void GetExistingFilesAsync() + { + // при первом запуске воркера, обрабатываем существующие файлы + var files = Directory.GetFiles(this.dirPath, filterExtensions); + + logger.LogInformation($"Запуск первоначальной загрузки файлов после старта сервиса. (До старта вотчера). Найдено файлов: {files.Length} шт."); + + foreach (var file in files) + await ParseFileAsync(file); + + //если были файлы, запустим еще раз ручную обработку, вдруг за это время еще загрузили, а вотчер еще не был запущен + //запускаем до тех пор, пока не останется необработанных файлов + if (files.Any()) + GetExistingFilesAsync(); + } + + + private async Task ParseFileAsync(string path) + { + logger.LogInformation($"--- --- Найден файл. Готов для парсинга {path} --- ---"); + var parseResult = await parserService.ParseAsync(path); + + if (parseResult) + { + logger.LogInformation($"Парсинг успешно завершен. Удаляю файл {path}"); + var removeResult = fileService.DeleteFile(path); + } + else + { + logger.LogError($"Парсинг завершен c ошибкой. Файл не удален {path}"); + } + } + + } +} diff --git a/PARR.EsppTemplateSyncWorker/PARR.EsppTemplateSyncWorker.csproj b/PARR.EsppTemplateSyncWorker/PARR.EsppTemplateSyncWorker.csproj new file mode 100644 index 00000000..48556f65 --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/PARR.EsppTemplateSyncWorker.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + enable + dotnet-PARR.EsppTemplateSyncWorker-33871a7f-fda7-4371-a369-a7ce886efbec + + + + + + + + + + + + + + diff --git a/PARR.EsppTemplateSyncWorker/Program.cs b/PARR.EsppTemplateSyncWorker/Program.cs new file mode 100644 index 00000000..d499c455 --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/Program.cs @@ -0,0 +1,20 @@ +using PARR.EsppTemplateSync; +using PARR.EsppTemplateSyncWorker; +using Serilog; + +IHost host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.InstallEsppTemplateSyncServices(hostContext.Configuration); + + services.AddHostedService(); + }) + .UseSerilog((hostContext, services, config) => + { + config + .WriteTo.Console() + .ReadFrom.Configuration(hostContext.Configuration); + }) + .Build(); + +host.Run(); diff --git a/PARR.EsppTemplateSyncWorker/Properties/launchSettings.json b/PARR.EsppTemplateSyncWorker/Properties/launchSettings.json new file mode 100644 index 00000000..96aa315b --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "PARR.EsppTemplateSyncWorker": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/PARR.EsppTemplateSyncWorker/Worker.cs b/PARR.EsppTemplateSyncWorker/Worker.cs new file mode 100644 index 00000000..6bfa8ed4 --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/Worker.cs @@ -0,0 +1,40 @@ +using PARR.EsppTemplateSync; + +namespace PARR.EsppTemplateSyncWorker +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + private readonly ITemplateSyncer templateSyncer; + + public Worker(ILogger logger, ITemplateSyncer templateSyncer) + { + _logger = logger; + this.templateSyncer = templateSyncer; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + //var task = new Task(() => + //{ + // templateSyncer.StartWatching(); + //}); + //task.Start(); + + templateSyncer.StartWatching(); + + //while (!stoppingToken.IsCancellationRequested) + //{ + // _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + // await Task.Delay(1 * 1000, stoppingToken); + //} + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + templateSyncer.StopWatching(); + + return base.StopAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/PARR.EsppTemplateSyncWorker/appsettings.Development.json b/PARR.EsppTemplateSyncWorker/appsettings.Development.json new file mode 100644 index 00000000..58b8d3f6 --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "StorageSettings": { + "StoragePath": "W:\\" + } +} diff --git a/PARR.EsppTemplateSyncWorker/appsettings.json b/PARR.EsppTemplateSyncWorker/appsettings.json new file mode 100644 index 00000000..7fa498f7 --- /dev/null +++ b/PARR.EsppTemplateSyncWorker/appsettings.json @@ -0,0 +1,34 @@ +{ + "DefaultConnection": "Server=10.99.253.184;Database=parr;User Id=app_parr; Password=PosdfkhT&)%sdfligL&%5546;", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Debug" + } + }, + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "log/log-.txt", + "rollingInterval": "Day" + } + } + ] + }, + "StorageSettings": { + "StoragePath": "Data", + "EsppTemplates": { + "AllowedExtensions": [ ".csv" ], + "MaxFileSizeMb": 100 + } + } +} diff --git a/PARR.Worker/PARR.Worker/Program.cs b/PARR.Worker/PARR.Worker/Program.cs index 5a7c906f..c53ae283 100644 --- a/PARR.Worker/PARR.Worker/Program.cs +++ b/PARR.Worker/PARR.Worker/Program.cs @@ -18,16 +18,16 @@ IHost host = Host.CreateDefaultBuilder(args) services.InstallMailServices(hostContext.Configuration); services.InstallDalServices(hostContext.Configuration); - services.InstallerSyncerServices(hostContext.Configuration); + services.InstallSyncerServices(hostContext.Configuration); services.AddHostedService(); }) - .UseSerilog((hostContext, services, config) => - { - config - .WriteTo.Console() - .ReadFrom.Configuration(hostContext.Configuration); - }) + .UseSerilog((hostContext, services, config) => + { + config + .WriteTo.Console() + .ReadFrom.Configuration(hostContext.Configuration); + }) .Build(); host.Run();