using AsbCloudApp.Data; using AsbCloudApp.Services; using ClosedXML.Excel; using ClosedXML.Excel.Drawings; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { public class DrillingProgramService : IDrillingProgramService { private readonly IFileService fileService; private readonly IWellService wellService; private const int idFileCategoryDrillingProgramItems = 13; private const int idFileCategoryDrillingProgram = 14; private const int maxAllowedColumns = 256; public DrillingProgramService(IFileService fileService, IWellService wellService) { this.fileService = fileService; this.wellService = wellService; } public async Task GetOrCreateSharedUrlAsync(int idWell, int idUser, IFileShareService fileShareService, CancellationToken token = default) { var fileInfo = await GetOrCreateAsync(idWell, idUser, token) .ConfigureAwait(false); if (fileInfo is null) return null; var sharedUrl = await fileService.GetSharedUrlAsync(fileInfo, idUser, fileShareService, token) .ConfigureAwait(false); return sharedUrl; } public async Task GetOrCreateAsync(int idWell, int idUser, CancellationToken token = default) { var programParts = (await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgramItems, token) .ConfigureAwait(false)) .Where(f => f.FileMarks?.Any(m => m.IdMarkType == 1 && !m.IsDeleted)??false); var well = await wellService.GetAsync(idWell, token) .ConfigureAwait(false); var programs = await fileService.GetInfosByCategoryAsync(idWell, idFileCategoryDrillingProgram, token) .ConfigureAwait(false); if (programs is not null && programs.Any() && programParts.Any()) { programs = programs.OrderByDescending(f => f.UploadDate); var matchFilesIterator = programs.GetEnumerator(); matchFilesIterator.MoveNext(); var matchFile = matchFilesIterator.Current; if (programParts.All(pp => matchFile.UploadDate > pp.UploadDate) && File.Exists(fileService.GetUrl(matchFile))) return matchFile; else await fileService.DeleteAsync(matchFile.Id, token) .ConfigureAwait(false); while (matchFilesIterator.MoveNext()) await fileService.DeleteAsync(matchFilesIterator.Current.Id, token) .ConfigureAwait(false); } if (!programParts.Any()) throw new FileNotFoundException("Нет частей для формирования программы бурения"); var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; var filteredFilePaths = programParts .Select(file => fileService.GetUrl(file)); var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName); UniteExcelFiles(filteredFilePaths, tempResultFilePath); var fileInfo = await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token).ConfigureAwait(false); return fileInfo; } public async Task CreateFileMarkAsync(FileMarkDto fileMarkDto, int idUser, CancellationToken token) { var fileInfo = await fileService.GetInfoAsync(fileMarkDto.IdFile, token) .ConfigureAwait(false); if (fileInfo.IdCategory != idFileCategoryDrillingProgramItems) throw new ArgumentException($"Этот метод допустим только для файлов-частей программы бурения idCategory=={idFileCategoryDrillingProgramItems}.", nameof(fileMarkDto)); var result = await fileService.CreateFileMarkAsync(fileMarkDto, idUser, token) .ConfigureAwait(false); var drillingPrograms = await fileService.GetInfosByCategoryAsync(fileInfo.IdWell, idFileCategoryDrillingProgram, token) .ConfigureAwait(false); foreach (var drillingProgram in drillingPrograms) await fileService.DeleteAsync(drillingProgram.Id, token) .ConfigureAwait(false); return result; } public async Task MarkFileMarkAsDeletedAsync(int idMark, CancellationToken token) { var fileInfo = await fileService.GetByMarkId(idMark, token) .ConfigureAwait(false); if (fileInfo.IdCategory != idFileCategoryDrillingProgramItems) throw new ArgumentException($"Этот метод допустим только для файлов-частей программы бурения idCategory=={idFileCategoryDrillingProgramItems}.", nameof(idMark)); var result = await fileService.MarkFileMarkAsDeletedAsync(idMark, token) .ConfigureAwait(false); var drillingPrograms = await fileService.GetInfosByCategoryAsync(fileInfo.IdWell, idFileCategoryDrillingProgram, token) .ConfigureAwait(false); foreach (var drillingProgram in drillingPrograms) await fileService.DeleteAsync(drillingProgram.Id, token) .ConfigureAwait(false); return result; } private static void UniteExcelFiles(IEnumerable excelFilesNames, string resultExcelPath) { var resultExcelFile = new XLWorkbook(XLEventTracking.Disabled); var filteredFileNames = excelFilesNames.Distinct(); foreach (var excelFileName in filteredFileNames) { using var workbookSrc = new XLWorkbook(excelFileName, XLEventTracking.Disabled); foreach (var sheet in workbookSrc.Worksheets) { if(sheet.Visibility == XLWorksheetVisibility.Visible) CopySheet(resultExcelFile, sheet); } } resultExcelFile.SaveAs(resultExcelPath, new SaveOptions { EvaluateFormulasBeforeSaving = true}); } private static void CopySheet(XLWorkbook workbookDst, IXLWorksheet sheetSrc) { var newSheetName = sheetSrc.Name; var suffix = ""; int index = 1; while (workbookDst.Worksheets.Contains(newSheetName)) { newSheetName = sheetSrc.Name; suffix = $"_{index++}"; if (newSheetName.Length + suffix.Length >= 31) newSheetName = newSheetName.Substring(0, (31 - suffix.Length)); newSheetName += suffix; } var imagesInfos = sheetSrc.Pictures.Select(p => new ImageInfo { Id = p.Id, Data = p.ImageStream.GetBuffer(), Height = p.Height, Width = p.Width, TopLeftCellAddress = p.TopLeftCell.Address, Left = p.Left, Top = p.Top }).ToList(); IXLWorksheet resultSheet; if (sheetSrc.Columns().Count() > maxAllowedColumns) { resultSheet = workbookDst.Worksheets.Add(newSheetName); var rngData = GetCellsRange(sheetSrc); rngData.CopyTo(resultSheet.Cell(1, 1)); var lastRowWithData = rngData.LastRowUsed().RangeAddress .LastAddress.RowNumber; for (int i = 1; i < lastRowWithData; i++) { resultSheet.Row(i).Height = sheetSrc.Row(i).Height; resultSheet.Column(i).Width = sheetSrc.Column(i).Width; } } else { RemovePicturesFromSheet(sheetSrc); resultSheet = sheetSrc.CopyTo(workbookDst, newSheetName); } CopyImagesToAnotherSheet(imagesInfos, resultSheet); } 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; } } }