diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 4707b568..1a8e2432 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -37,9 +37,11 @@ + + diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ConvertToPdf.cs b/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ConvertToPdf.cs new file mode 100644 index 00000000..b7751ba4 --- /dev/null +++ b/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ConvertToPdf.cs @@ -0,0 +1,93 @@ +using iTextSharp.text; +using iTextSharp.text.pdf; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CliWrap; +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace AsbCloudInfrastructure.Services.DrillingProgram.Convert +{ +#nullable enable + sealed internal class ConvertToPdf + { + internal static readonly string[] filesExtensions = { ".xlsx", ".xls", ".ods", ".odt", ".doc", ".docx", ".pdf" }; + + private static void MergeFiles(IEnumerable inputFiles, string outFile) + { + using var stream = new FileStream(outFile, FileMode.Create); + using var doc = new Document(); + using var pdf = new PdfCopy(doc, stream); + doc.Open(); + var inputFilesList = inputFiles.ToList(); + foreach (var file in inputFilesList) + { + var reader = new PdfReader(file); + for (int i = 0; i < reader.NumberOfPages; i++) + { + pdf.AddPage(pdf.GetImportedPage(reader, i + 1)); + } + pdf.FreeReader(reader); + reader.Close(); + }; + } + + private static (string programFile, string programArg) GetOptionsStartupProcess (string inputFileName, string resultFileDir) + { + (string programFile, string programArg) startupOptions; + if (OperatingSystem.IsWindows()) + { + startupOptions.programFile = "C:\\Program Files\\LibreOffice\\program\\soffice.exe"; + startupOptions.programArg = $"-headless -convert-to pdf {inputFileName} --outdir {resultFileDir}"; + return startupOptions; + } + if(OperatingSystem.IsLinux()) + { + startupOptions.programFile = "/usr/bin/soffice"; + startupOptions.programArg = $"--headless --convert-to pdf {inputFileName} --outdir {resultFileDir}"; + return (startupOptions); + } + + throw new NotSupportedException ("Вызов процесса в текущей операционной системе не возможен"); + } + + private static async Task StartConvertProcessAsync(string inputFileName, string resultFileDir, CancellationToken token) + { + var (programFile, programArg) = GetOptionsStartupProcess(inputFileName, resultFileDir); + var command = Cli.Wrap(programFile) + .WithArguments(programArg); + await command.ExecuteAsync(token); + } + + public static async Task GetConverteAndMergedFileAsync(IEnumerable files, string resultPath, string convertedFilesDir, CancellationToken token) + { + var badFiles = files.Where(f => !filesExtensions.Contains(Path.GetExtension(f))); + if (badFiles.Any()) + { + throw new FileFormatException($"Файлы: {string.Join(", ", badFiles)} - неподдерживаемого формата. " + + $"Они не могут быть добавлены в список файлов для конвертации и слияния в общий файл программы бурения."); + } + var listFiles = files + .Distinct() + .Select(f => new + { + inputFile = f, + convertedFile = Path.Combine(convertedFilesDir, "pdf", Path.ChangeExtension(Path.GetFileName(f), ".pdf")) + }) + .ToList(); + foreach (var file in listFiles) + { + var fileExt = Path.GetExtension(file.inputFile).ToLower(); + if (fileExt != ".pdf") + { + await StartConvertProcessAsync(file.inputFile, Path.GetDirectoryName(file.convertedFile)!, token); + } + } + MergeFiles(listFiles.Select(c => c.convertedFile), resultPath); + Directory.Delete(Path.Combine(convertedFilesDir, "pdf"), true); + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ReadMe.md b/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ReadMe.md new file mode 100644 index 00000000..54ab5556 --- /dev/null +++ b/AsbCloudInfrastructure/Services/DrillingProgram/Convert/ReadMe.md @@ -0,0 +1,17 @@ +КЛАСС ПРЕОБРАЗУЮЩИЙ ЧАСТИ ПРОГРАММЫ БУРЕНИЯ В ЕДИНЫЙ ФАЙЛ ПЕЧАТНОГО ФОРМАТА (pdf) + +1. На Linux сервер необходимо установить пакеты LibreOffice: + + sudo apt-get install libreoffice-writer libreoffice-calc + +2. путь до бинарника LibreOffice: +Linux - /usr/bin/soffice +Windows - C:\Program Files\LibreOffice\program\soffice.exe + +3. В массиве fileExtensions содержатся в виде стринг переменных необходимые расширения файлов +изначально обозначенные в задаче. +При необходимости список можно расширить. + + + + diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index 9ef92fd8..00d2c46e 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -4,6 +4,7 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Background; +using AsbCloudInfrastructure.Services.DrillingProgram.Convert; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -28,7 +29,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private readonly IWellService wellService; private readonly IConfiguration configuration; private readonly BackgroundWorker backgroundWorker; - private readonly IEmailService emailService; + private readonly IEmailService emailService; private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgramPartsStart = 1001; @@ -50,6 +51,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private const int idStateReady = 3; private const int idStateError = 4; + private static readonly string[] validFileExtensions = ConvertToPdf.filesExtensions; + public DrillingProgramService( IAsbCloudDbContext context, FileService fileService, @@ -161,12 +164,21 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return state; } - public async Task AddFile(int idWell, int idFileCategory, int idUser, string fileFullName, System.IO.Stream fileStream, CancellationToken token = default) + private static bool IsFileExtensionValid(string file) { + var fileExt = Path.GetExtension(file).ToLower(); + return validFileExtensions.Contains(fileExt); + } + + public async Task AddFile(int idWell, int idFileCategory, int idUser, string fileFullName, Stream fileStream, CancellationToken token = default) + { + if (!IsFileExtensionValid(fileFullName)) + throw new FileFormatException($"Файл {fileFullName} - неподдерживаемого формата. Файл не может быть загружен."); + var part = await context.DrillingProgramParts - .Include(p => p.RelatedUsers) - .ThenInclude(r => r.User) - .FirstOrDefaultAsync(p => p.IdWell == idWell && p.IdFileCategory == idFileCategory, token); + .Include(p => p.RelatedUsers) + .ThenInclude(r => r.User) + .FirstOrDefaultAsync(p => p.IdWell == idWell && p.IdFileCategory == idFileCategory, token); if (part == null) throw new ArgumentInvalidException($"DrillingProgramPart id == {idFileCategory} does not exist", nameof(idFileCategory)); @@ -333,9 +345,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram await NotifyPublisherOnFullAccepAsync(fileMarkDto, token); } } - - - return result; } @@ -472,15 +481,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram if (!backgroundWorker.Contains(workId)) { var well = (await wellService.GetOrDefaultAsync(idWell, token))!; - var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; - var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName); - + var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf"; + var convertedFilesDir = Path.Combine(Path.GetTempPath(), "drillingProgram", $"{well.Cluster}_{well.Caption}"); + var tempResultFilePath = Path.Combine(convertedFilesDir, resultFileName); var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) => { var context = serviceProvider.GetRequiredService(); var fileService = serviceProvider.GetRequiredService(); - var files = state.Parts.Select(p => fileService.GetUrl(p.File)); - DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath, state.Parts, well); + var files = state.Parts.Select(p => fileService.GetUrl(p.File)); + await ConvertToPdf.GetConverteAndMergedFileAsync(files, tempResultFilePath, convertedFilesDir, token); await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); }; diff --git a/AsbCloudWebApi/Controllers/DrillingProgramController.cs b/AsbCloudWebApi/Controllers/DrillingProgramController.cs index 5b7f2c99..fc4eeb11 100644 --- a/AsbCloudWebApi/Controllers/DrillingProgramController.cs +++ b/AsbCloudWebApi/Controllers/DrillingProgramController.cs @@ -125,10 +125,7 @@ namespace AsbCloudWebApi.Controllers return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(files), "at list 1 file should be uploaded")); var fileName = files[0].FileName; - - if (!fileName.EndsWith(".xlsx")) - return BadRequest(ArgumentInvalidException.MakeValidationError("file", "Файл должен быть xlsx")); - + var fileStream = files[0].OpenReadStream(); var result = await drillingProgramService.AddFile(idWell, idFileCategory, (int)idUser, fileName, fileStream, token); diff --git a/AsbCloudWebApi/ProtobufModel.cs b/AsbCloudWebApi/ProtobufModel.cs index d389cd37..ea432162 100644 --- a/AsbCloudWebApi/ProtobufModel.cs +++ b/AsbCloudWebApi/ProtobufModel.cs @@ -10,10 +10,11 @@ namespace AsbCloudWebApi EnshureRegisteredDataSpin(); EnshureRegisteredDataSaub(); EnshureRegisteredWITS(); + EnshureRegisteredWirelineRunOutBaseDto(); EnshureRegisteredWirelineRunOutDto(); } - private static void EnshureRegisteredWirelineRunOutDto() + private static void EnshureRegisteredWirelineRunOutBaseDto() { var type = typeof(TelemetryWirelineRunOutBaseDto); if (RuntimeTypeModel.Default.IsDefined(type)) @@ -26,6 +27,19 @@ namespace AsbCloudWebApi .Add(5, nameof(TelemetryWirelineRunOutBaseDto.ReplaceWarnSp)); } + private static void EnshureRegisteredWirelineRunOutDto() + { + var type = typeof(TelemetryWirelineRunOutDto); + if (RuntimeTypeModel.Default.IsDefined(type)) + return; + RuntimeTypeModel.Default.Add(type, false) + .Add(1, nameof(TelemetryWirelineRunOutDto.DateTime)) + .Add(2, nameof(TelemetryWirelineRunOutDto.Hauling)) + .Add(3, nameof(TelemetryWirelineRunOutDto.HaulingWarnSp)) + .Add(4, nameof(TelemetryWirelineRunOutDto.Replace)) + .Add(5, nameof(TelemetryWirelineRunOutDto.ReplaceWarnSp)); + } + private static void EnshureRegisteredWITS() { EnshureRegisteredRecord1(); diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs index af1ac385..0336fb7e 100644 --- a/ConsoleApp1/Program.cs +++ b/ConsoleApp1/Program.cs @@ -1,70 +1,172 @@ -using AsbCloudApp.Requests; -using AsbCloudDb.Model; -using AsbCloudInfrastructure; -using AsbCloudInfrastructure.Repository; -using Microsoft.Extensions.Caching.Memory; +using iTextSharp.text.pdf; using System; +using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Threading; +using System.Threading.Tasks; +using Document = iTextSharp.text.Document; +using CliWrap; namespace ConsoleApp1 { class Program { - private static AsbCloudDbContext db = ServiceFactory.Context; - - // use ServiceFactory to make services static void Main(/*string[] args*/) { - DependencyInjection.MapsterSetup(); - var sw = System.Diagnostics.Stopwatch.StartNew(); + // string[] fileExtension = { ".xlsx", ".xls", ".ods", ".odt", ".doc", ".docx", ".pdf" }; + // Console.WriteLine($"start convert"); + // var inputFiles = new List(); + // var resultFile = "C:\\Test\\result.pdf"; + // inputFiles.Add("11112222.docx"); + // inputFiles.Add("11117777.pdf"); + // inputFiles.Add("22223333.xls"); + // inputFiles.Add("33334444.xlsx"); + // //inputFiles.Add("33334444.tts"); - var idTelemetry = 5; + // var listOutNames = new List(); + // var filteredFilesNames = inputFiles + // .Distinct() + // .Where(f => fileExtension.Any(fe => f.ToLower().EndsWith(fe))) + // .ToList(); + // FileInfo fileInfo = new FileInfo(resultFile); - var query = db.Set() - .Where(t => t.IdTelemetry == idTelemetry) - .Where(t => t.BlockPosition > 0.0001) - .Where(t => t.WellDepth > 0.0001) - .Where(t => t.WellDepth - t.BitDepth < 0.01) - .GroupBy(t => new { H = t.DateTime.Hour, W = Math.Truncate(t.WellDepth!.Value) }) - .Select(g => new + // //matchesExtensions(inputFiles); + // foreach (var FileName in inputFiles) + // { + // var outputFile = Path.ChangeExtension(FileName, ".pdf"); + // var outFile = StartConvertProcessAsync(FileName, outputFile); + // Console.WriteLine($"convert file - {FileName}"); + // Console.ReadLine(); + // listOutNames.Add(outFile.Result.ToString()); + // } + // Console.WriteLine("merged files"); + // Console.ReadLine(); + // DoMerged(listOutNames, resultFile); + + + + //static void matchesExtensions(List inputFiles) + // { + // string[] fileExtension = { ".xlsx", ".xls", ".ods", ".odt", ".doc", ".docx", ".pdf" }; + // foreach (var file in inputFiles) + // { + // var fileExt = Path.GetExtension(file); + // if (fileExtension.All(fe => fileExt != fe)) + // { + // throw new FileFormatException($"Файл с именем: {file} не может быть добавлен в список файлов для конвертации и слияния в общий файл программы бурения. Не поддерживаемый формат файла"); + // } + + // } + // } + if (OperatingSystem.IsWindows()) + { + Console.WriteLine("win"); + Console.ReadLine(); + } + if (OperatingSystem.IsLinux()) + { + Console.WriteLine("linux"); + Console.ReadLine(); + } + + } + + public static void DoMerged(IEnumerable inputFiles, string outFile) + { + using var stream = new FileStream(outFile, FileMode.Create); + using var doc = new Document(); + using var pdf = new PdfCopy(doc, stream); + doc.Open(); + var inputFilesList = inputFiles.ToList(); + foreach (var file in inputFilesList) + { + var reader = new PdfReader(file); + for (int i = 0; i < reader.NumberOfPages; i++) { - Count = g.Count(), + PdfImportedPage page = pdf.GetImportedPage(reader, i + 1); + pdf.AddPage(page); + } + pdf.FreeReader(reader); + reader.Close(); + }; + } - DateMin = g.Min(t => t.DateTime), - DateMax = g.Max(t => t.DateTime), + private static (string programFile, string programArg) getOptionsStartupProcess(string inputFileName, string resultFileDir) + { + (string programFile, string programArg) startupOptions; + if (OperatingSystem.IsWindows()) + { + startupOptions.programFile = "C:\\Program Files\\LibreOffice\\program\\soffice.exe"; + startupOptions.programArg = $"-headless -convert-to pdf {inputFileName} --outdir {resultFileDir}"; + return startupOptions; + } + if (OperatingSystem.IsLinux()) + { + startupOptions.programFile = "/usr/bin/soffice"; + startupOptions.programArg = $"--headless --convert-to pdf {inputFileName} --outdir {resultFileDir}"; + return (startupOptions); + } - WellDepthMin = g.Min(t => t.WellDepth), - WellDepthMax = g.Max(t => t.WellDepth), + throw new NotSupportedException("Вызов процесса в текущей операционной системе не возможен"); + } - Pressure = g.Average(t => t.Pressure), - PressureSp = g.Average(t => t.PressureSp), - PressureSpRotor = g.Average(t => t.PressureSpRotor), - PressureSpSlide = g.Average(t => t.PressureSpSlide), - PressureIdle = g.Average(t => t.PressureIdle), - PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax), + //public static void StartConvertProcess(string inputFileName, string outFileName) + //{ + // using (Process pdfprocess = new Process()) + // { + // pdfprocess.StartInfo.UseShellExecute = true; + // //pdfprocess.StartInfo.LoadUserProfile = true; + // pdfprocess.StartInfo.FileName = "soffice"; + // pdfprocess.StartInfo.Arguments = $"--headless --convert-to pdf {inputFileName} --outdir {outFileName}"; + // pdfprocess.StartInfo.WorkingDirectory = "/usr/bin"; + // pdfprocess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + // pdfprocess.Start(); + // if (!pdfprocess.WaitForExit(1000 * 60 * 1)) + // { + // pdfprocess.Kill(); + // } + // pdfprocess.Close(); + // } + //} + private static async Task StartConvertProcessAsync(string inputFileName, string outFileName) + { - AxialLoad = g.Average(t => t.AxialLoad), - AxialLoadSp = g.Average(t => t.AxialLoadSp), - AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax), + var progrAndArg = getOptionsStartupProcess(inputFileName, outFileName); - RotorTorque = g.Average(t => t.RotorTorque), - RotorTorqueSp = g.Average(t => t.RotorTorqueSp), - RotorTorqueIdle = g.Average(t => t.RotorTorqueIdle), + //string outPath = "/home/eddie/Test/OutFiles"; + string outPath = "C:\\Test\\OutFiles"; + var result = Cli.Wrap("C:\\Program Files\\LibreOffice\\program\\soffice.exe") + .WithArguments($"-headless -convert-to pdf C:\\Test\\InFiles\\{inputFileName} -outdir {outPath}"); + await result.ExecuteAsync(); + var outFile = $"{outPath}\\{outFileName}"; + return outFile; + } - BlockSpeed = g.Average(t => t.BlockSpeed), - BlockSpeedSp = g.Average(t => t.BlockSpeedSp), - BlockSpeedSpRotor = g.Average(t => t.BlockSpeedSpRotor), - BlockSpeedSpSlide = g.Average(t => t.BlockSpeedSpSlide), - }) - .Where(s => s.WellDepthMin != s.WellDepthMax) - .Where(s => s.Count > 3) - .OrderBy(t => t.DateMin); - var data = query.ToArray(); - sw.Stop(); - Console.WriteLine($"total time: {sw.ElapsedMilliseconds} ms"); - var count = data.Length; + public static async Task GetConverteAndMergedFileAsync(IEnumerable filesNames, string resultPath) + { + string[] fileExtension = { ".xlsx", ".xls", ".ods", ".odt", ".doc", ".docx", ".pdf" }; + //var filteredFilesNames = filesNames.Distinct(); + var filteredFilesNames = filesNames + .Distinct() + .Where(f => fileExtension.Any(fe => f.ToLower().EndsWith(fe))) + .ToList(); + var listFileNames = filteredFilesNames + .ToList() + .Select(o => new { + inputFile = o, + convertedFile = Path.ChangeExtension(o, ".pdf") + }); + foreach (var excelFileName in listFileNames) + { + await StartConvertProcessAsync(excelFileName.inputFile, excelFileName.convertedFile); + Console.WriteLine($"convert file - {excelFileName.inputFile}"); + Console.ReadLine(); + } + + Console.WriteLine("merged files"); Console.ReadLine(); + DoMerged(listFileNames.Select(c => c.convertedFile), resultPath); + } } } diff --git a/ConsoleApp1/Properties/PublishProfiles/FolderProfile.pubxml b/ConsoleApp1/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..1002f833 --- /dev/null +++ b/ConsoleApp1/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,17 @@ + + + + + Release + Any CPU + C:\home\linux_test_test + FileSystem + net6.0 + linux-x64 + true + false + false + + \ No newline at end of file diff --git a/ConsoleApp1/ReadMe.md b/ConsoleApp1/ReadMe.md new file mode 100644 index 00000000..1ddf6367 --- /dev/null +++ b/ConsoleApp1/ReadMe.md @@ -0,0 +1,25 @@ +ТЕСТ РАБОТЫ КЛАССА КОНВЕРТИРУЮЩЕГО ЧАСТИ ПРОГРАММЫ БУРЕНИЯ В ЕДИНЫЙ ФАЙЛ ПЕЧАТНОГО ФОРМАТА (pdf) + +Тест настроен под проверку на винде +для проверки необходимо создать иерархию папок (в корне С:\) +C:\Test\InFiles и C:\Test\OutFiles + +Для простоты тестирования имена файлов подлежащих конвертации + "зашиты" в код + + + +Для теста/работы на линукс машинах + +на Linux сервер необходимо установить пакеты LibreOffice + +sudo apt-get install libreoffice-writer libreoffice-calc + + перед компиляцией необходимо изменить пути к файлам и папкам + +например : +C:\Test\InFiles => /home/{папка юзера}/Test/InFiles + +путь до бинарника LibreOffice: +Linux - /usr/bin/soffice +Windows - C:\Program Files\LibreOffice\program\soffice.exe \ No newline at end of file