From 9e89f87d47fd75ef8415e1b77e4dd0ed4e88d05b Mon Sep 17 00:00:00 2001 From: KharchenkoVV Date: Thu, 23 Sep 2021 11:37:57 +0500 Subject: [PATCH] Changed Drilling program creation to ClosedXML lib --- .../Services/IDrillingProgramApacheService.cs | 11 ++ .../AsbCloudInfrastructure.csproj | 1 + AsbCloudInfrastructure/DependencyInjection.cs | 1 + .../Services/DrillingProgramApacheService.cs | 92 ++++++++++++++ .../Services/DrillingProgramService.cs | 120 +++++++++++++++--- .../Services/FileService.cs | 19 ++- 6 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 AsbCloudApp/Services/IDrillingProgramApacheService.cs create mode 100644 AsbCloudInfrastructure/Services/DrillingProgramApacheService.cs diff --git a/AsbCloudApp/Services/IDrillingProgramApacheService.cs b/AsbCloudApp/Services/IDrillingProgramApacheService.cs new file mode 100644 index 00000000..4f42dfbd --- /dev/null +++ b/AsbCloudApp/Services/IDrillingProgramApacheService.cs @@ -0,0 +1,11 @@ +using AsbCloudApp.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudApp.Services +{ + public interface IDrillingProgramApacheService + { + Task GetAsync(int idWell, CancellationToken token = default); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 56ac820c..375a1a5e 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -9,6 +9,7 @@ + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index ea36bfc2..d95d72a1 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -40,6 +40,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // admin crud services: services.AddTransient, CrudServiceBase>(); diff --git a/AsbCloudInfrastructure/Services/DrillingProgramApacheService.cs b/AsbCloudInfrastructure/Services/DrillingProgramApacheService.cs new file mode 100644 index 00000000..2a267bab --- /dev/null +++ b/AsbCloudInfrastructure/Services/DrillingProgramApacheService.cs @@ -0,0 +1,92 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Services; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services +{ + public class DrillingProgramApacheService : IDrillingProgramApacheService + { + private readonly IFileService fileService; + private readonly IWellService wellService; + private const int idFileCategoryDrillingProgramItems = 13; + private const int idFileCategoryDrillingProgram = 14; + + public DrillingProgramApacheService(IFileService fileService, IWellService wellService) + { + this.fileService = fileService; + this.wellService = wellService; + } + + public async Task GetAsync(int idWell, CancellationToken token = default) + { + var filesInfos = await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgramItems, token) + .ConfigureAwait(false); + + var well = await wellService.GetAsync(idWell, token) + .ConfigureAwait(false); + + var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; + + var matchFiles = await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgram, token) + .ConfigureAwait(false); + + if (matchFiles is not null && matchFiles.Any()) + { + matchFiles = matchFiles.OrderByDescending(f => f.UploadDate); + var matchFilesIterator = matchFiles.GetEnumerator(); + matchFilesIterator.MoveNext(); + var matchFile = matchFilesIterator.Current; + while (matchFilesIterator.MoveNext()) + await fileService.DeletedAsync(matchFilesIterator.Current.Id, token) + .ConfigureAwait(false); + + if (filesInfos.All(f => f.UploadDate <= matchFile.UploadDate)) + return matchFile; + else + await fileService.DeletedAsync(matchFile.Id, token) + .ConfigureAwait(false); + } + + var fileNames = filesInfos + .Where(f => f.Name != resultFileName) + .Select(f => fileService.GetFileName(f)); + + var stream = new MemoryStream(1024 * 1024); + UniteExcelFiles(fileNames, stream); + var buffer = stream.ToArray(); + var fileStream = new MemoryStream(buffer); + return await fileService.SaveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, fileStream, token) + .ConfigureAwait(false); + } + + private static void UniteExcelFiles(IEnumerable excelFilesNames, Stream stream) + { + IWorkbook product = new XSSFWorkbook(); + + foreach (var excelFileName in excelFilesNames) + { + IWorkbook book = new XSSFWorkbook(new FileStream(excelFileName, FileMode.Open)); + + for (int i = 0; i < book.NumberOfSheets; i++) + { + ISheet sheet = book.GetSheetAt(i); + try + { + sheet.CopyTo(product, sheet.SheetName, true, true); + } + catch + { + //what can't be done - can't be done. ignore it. + } + } + } + product.Write(stream); + } + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgramService.cs index 7197c4e9..44947674 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgramService.cs @@ -1,7 +1,7 @@ using AsbCloudApp.Data; using AsbCloudApp.Services; -using NPOI.SS.UserModel; -using NPOI.XSSF.UserModel; +using ClosedXML.Excel; +using ClosedXML.Excel.Drawings; using System.Collections.Generic; using System.IO; using System.Linq; @@ -31,8 +31,6 @@ namespace AsbCloudInfrastructure.Services var well = await wellService.GetAsync(idWell, token) .ConfigureAwait(false); - var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; - var matchFiles = await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgram, token) .ConfigureAwait(false); @@ -53,40 +51,124 @@ namespace AsbCloudInfrastructure.Services .ConfigureAwait(false); } + var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; + var fileNames = filesInfos .Where(f => f.Name != resultFileName) .Select(f => fileService.GetFileName(f)); - var stream = new MemoryStream(1024 * 1024); - UniteExcelFiles(fileNames, stream); - var buffer = stream.ToArray(); - var fileStream = new MemoryStream(buffer); - return await fileService.SaveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, fileStream, token) + var resultExcelPath = Path.Combine(fileService.RootPath, + $"{idWell}", $"{idFileCategoryDrillingProgram}", + resultFileName); + + UniteExcelFiles(fileNames, resultExcelPath); + + return await fileService.SaveAsync(idWell, null, idFileCategoryDrillingProgram, + resultExcelPath, null, token) .ConfigureAwait(false); } - private static void UniteExcelFiles(IEnumerable excelFilesNames, Stream stream) + private static void UniteExcelFiles(IEnumerable excelFilesNames, string resultExcelPath) { - IWorkbook product = new XSSFWorkbook(); + var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled); + + const int maxAllowedColumns = 256; foreach (var excelFileName in excelFilesNames) { - IWorkbook book = new XSSFWorkbook(new FileStream(excelFileName, FileMode.Open)); + using var sourceExcelFile = new XLWorkbook(excelFileName, XLEventTracking.Disabled); - for (int i = 0; i < book.NumberOfSheets; i++) + foreach (var sheet in sourceExcelFile.Worksheets) { - ISheet sheet = book.GetSheetAt(i); - try + var imagesInfos = sheet.Pictures.Select(p => new ImageInfo { - sheet.CopyTo(product, sheet.SheetName, true, true); + Id = p.Id, + Data = p.ImageStream.GetBuffer(), + Height = p.Height, + Width = p.Width, + TopLeftCellAddress = p.TopLeftCell.Address, + Left = p.Left, + Top = p.Top + }).ToList(); + + if (sheet.Columns().Count() > maxAllowedColumns) + { + var resultSheet = resultExcelFile.Worksheets.Add(sheet.Name); + + var rngData = GetCellsRange(sheet); + + rngData.CopyTo(resultSheet.Cell(1, 1)); + + var lastRowWithData = rngData.LastRowUsed().RangeAddress + .LastAddress.RowNumber; + + for (int i = 1; i < lastRowWithData; i++) + { + resultSheet.Row(i).Height = sheet.Row(i).Height; + resultSheet.Column(i).Width = sheet.Column(i).Width; + } + + CopyImagesToAnotherSheet(imagesInfos, resultSheet); } - catch + else { - //what can't be done - can't be done. ignore it. + RemovePicturesFromSheet(sheet); + + var resultSheet = sheet.CopyTo(resultExcelFile, sheet.Name); + + CopyImagesToAnotherSheet(imagesInfos, resultSheet); } } } - product.Write(stream); + + resultExcelFile.SaveAs(resultExcelPath, + new SaveOptions { EvaluateFormulasBeforeSaving = true}); + } + + private static IXLWorksheet CopyImagesToAnotherSheet(IEnumerable imagesInfos, + IXLWorksheet resultSheet) + { + foreach (var image in imagesInfos) + { + var stream = new MemoryStream(); + stream.Write(image.Data, 0, image.Data.Length); + + resultSheet.AddPicture(stream) + .WithPlacement(XLPicturePlacement.Move) + .WithSize(image.Width, image.Height) + .MoveTo(resultSheet.Cell(image.TopLeftCellAddress), + image.Left, image.Top); + } + + return resultSheet; + } + + private static void RemovePicturesFromSheet(IXLWorksheet sheet) + { + var filteredPics = sheet.Pictures.Select(p => p.Name).Distinct().ToList(); + + foreach (var n in filteredPics) + sheet.Pictures.Delete(n); + } + + private static IXLRange GetCellsRange(IXLWorksheet sheet) + { + var firstTableCell = sheet.FirstCellUsed(); + var lastTableCell = sheet.LastCellUsed(); + var rngData = sheet.Range(firstTableCell.Address, lastTableCell.Address); + + return rngData; } } + + class ImageInfo + { + public int Id { get; set; } + public byte[] Data { get; set; } + public int Height { get; set; } + public int Width { get; set; } + public IXLAddress TopLeftCellAddress { get; set; } + public int Left { get; set; } + public int Top { get; set; } + } } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/FileService.cs b/AsbCloudInfrastructure/Services/FileService.cs index 615fcc08..b214daca 100644 --- a/AsbCloudInfrastructure/Services/FileService.cs +++ b/AsbCloudInfrastructure/Services/FileService.cs @@ -29,7 +29,9 @@ namespace AsbCloudInfrastructure.Services .ThenInclude(c => c.CompanyType); } - public async Task SaveAsync(int idWell, int? idUser, int idCategory, string fileFullName, Stream fileStream, CancellationToken token = default) + public async Task SaveAsync(int idWell, int? idUser, + int idCategory, string fileFullName, Stream fileStream, + CancellationToken token = default) { //save info to db var fileInfo = new AsbCloudDb.Model.FileInfo() @@ -40,21 +42,24 @@ namespace AsbCloudInfrastructure.Services Name = Path.GetFileName(fileFullName), UploadDate = DateTime.Now, IsDeleted = false, - Size = fileStream.Length, + Size = fileStream?.Length ?? 0 }; var entry = db.Files.Add(fileInfo); db.SaveChanges(); var fileId = entry.Entity.Id; //save stream to disk - var relativePath = Path.Combine(RootPath, $"{idWell}", + if(fileStream is not null) + { + var relativePath = Path.Combine(RootPath, $"{idWell}", $"{idCategory}", $"{fileId}" + $"{Path.GetExtension(fileFullName)}"); - Directory.CreateDirectory(Path.GetDirectoryName(relativePath)); + Directory.CreateDirectory(Path.GetDirectoryName(relativePath)); - using (var newfileStream = new FileStream(relativePath, FileMode.Create)) - { - await fileStream.CopyToAsync(newfileStream); + using (var newfileStream = new FileStream(relativePath, FileMode.Create)) + { + await fileStream.CopyToAsync(newfileStream, token); + } } var dto = entry.Entity.Adapt();