using FluentFTP; using FluentFTP.Client.BaseClient; using Microsoft.EntityFrameworkCore; using RMuseum.DbContext; using RMuseum.Models.Artifact; using RMuseum.Models.ImportJob; using RMuseum.Models.PDFLibrary; using RMuseum.Models.PDFLibrary.ViewModels; using RSecurityBackend.Models.Generic; using RSecurityBackend.Models.Image; using RSecurityBackend.Services.Implementation; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading.Tasks; namespace RMuseum.Services.Implementation { public partial class PDFLibraryService { private async Task> ImportLocalPDFFileAsync(RMuseumDbContext context, NewPDFBookViewModel model, string fileChecksum) { try { var pdfRes = await ImportLocalPDFFileAsync(context, model.BookId, model.MultiVolumePDFCollectionId, model.VolumeOrder, model.LocalImportingPDFFilePath, model.OriginalSourceUrl, model.SkipUpload, fileChecksum); if (pdfRes.Result != null) { var pdfBook = pdfRes.Result; pdfBook.Title = model.Title; pdfBook.SubTitle = model.SubTitle; pdfBook.AuthorsLine = model.AuthorsLine; pdfBook.ISBN = model.ISBN; pdfBook.Description = model.Description; pdfBook.IsTranslation = model.IsTranslation; pdfBook.TranslatorsLine = model.TranslatorsLine; pdfBook.TitleInOriginalLanguage = model.TitleInOriginalLanguage; pdfBook.PublisherLine = model.PublisherLine; pdfBook.PublishingDate = model.PublishingDate; pdfBook.PublishingLocation = model.PublishingLocation; pdfBook.PublishingNumber = model.PublishingNumber == 0 ? null : model.PublishingNumber; pdfBook.ClaimedPageCount = model.ClaimedPageCount == 0 ? null : model.ClaimedPageCount; pdfBook.OriginalSourceName = model.OriginalSourceName; pdfBook.OriginalFileUrl = model.OriginalFileUrl; pdfBook.PDFSourceId = model.PDFSourceId; pdfBook.BookScriptType = model.BookScriptType; List roles = new List(); if (model.WriterId != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.WriterId).SingleAsync(), Role = "نویسنده", }); } if (model.Writer2Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Writer2Id).SingleAsync(), Role = "نویسنده", }); } if (model.Writer3Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Writer3Id).SingleAsync(), Role = "نویسنده", }); } if (model.Writer4Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Writer4Id).SingleAsync(), Role = "نویسنده", }); } if (model.TranslatorId != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.TranslatorId).SingleAsync(), Role = "مترجم", }); } if (model.Translator2Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Translator2Id).SingleAsync(), Role = "مترجم", }); } if (model.Translator3Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Translator3Id).SingleAsync(), Role = "مترجم", }); } if (model.Translator4Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Translator4Id).SingleAsync(), Role = "مترجم", }); } if (model.CollectorId != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.CollectorId).SingleAsync(), Role = "مصحح", }); } if (model.Collector2Id != null) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.Collector2Id).SingleAsync(), Role = "مصحح", }); } if (model.OtherContributerId != null && !string.IsNullOrEmpty(model.OtherContributerRole)) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.OtherContributerId).SingleAsync(), Role = model.OtherContributerRole, }); } if (model.OtherContributer2Id != null && !string.IsNullOrEmpty(model.OtherContributer2Role)) { roles.Add(new AuthorRole() { Author = await context.Authors.Where(a => a.Id == model.OtherContributer2Id).SingleAsync(), Role = model.OtherContributer2Role, }); } if (roles.Count > 0) { pdfBook.Contributers = roles; } context.Update(pdfBook); await context.SaveChangesAsync(); return new RServiceResult(pdfBook); } return pdfRes; } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } private async Task> ImportLocalPDFFileAsync(RMuseumDbContext context, int bookId, int? volumeId, int volumeOrder, string filePath, string srcUrl, bool skipUpload, string fileChecksum) { try { ImportJob job = new ImportJob() { JobType = JobType.Pdf, SrcContent = fileChecksum, ResourceNumber = filePath, FriendlyUrl = "", SrcUrl = srcUrl, QueueTime = DateTime.Now, ProgressPercent = 0, Status = ImportJobStatus.NotStarted }; await context.ImportJobs.AddAsync ( job ); await context.SaveChangesAsync(); if (volumeId == 0) volumeId = null; if (!string.IsNullOrEmpty(srcUrl)) { if ( (await context.PDFBooks.Where(a => a.OriginalSourceUrl == srcUrl).SingleOrDefaultAsync()) != null ) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = $"duplicated srcUrl '{srcUrl}'"; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } } if ( (await context.PDFBooks.Where(a => a.FileMD5CheckSum == fileChecksum).SingleOrDefaultAsync()) != null ) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = $"duplicated pdf with checksum '{fileChecksum}'"; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } try { var folderNumber = 1 + await context.PDFBooks.CountAsync(); while (Directory.Exists(Path.Combine(_imageFileService.ImageStoragePath, $"pdfbook-{folderNumber}"))) { folderNumber++; } Directory.CreateDirectory(Path.Combine(_imageFileService.ImageStoragePath, $"pdfbook-{folderNumber}")); PDFBook pdfBook = new PDFBook() { Status = PublishStatus.Draft, DateTime = DateTime.Now, LastModified = DateTime.Now, FileMD5CheckSum = fileChecksum, OriginalSourceUrl = srcUrl, OriginalFileName = Path.GetFileName(filePath), StorageFolderName = $"pdfbook-{folderNumber}", BookId = bookId, VolumeOrder = volumeOrder, MultiVolumePDFCollectionId = volumeId, }; job.FriendlyUrl = pdfBook.StorageFolderName; List meta = new List(); job.StartTime = DateTime.Now; job.Status = ImportJobStatus.Running; job.SrcContent = ""; context.Update(job); await context.SaveChangesAsync(); List pages = await _ImportAndReturnPDFJobImages(pdfBook, job, 0); pdfBook.Tags = meta.ToArray(); pdfBook.Pages = pages.ToArray(); pdfBook.PageCount = pages.Count; if (pages.Count == 0) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = "Pages.Count == 0"; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } using (FileStream fs = new FileStream(filePath, FileMode.Open)) { var pdfStorageResult = await _imageFileService.Add(null, fs, pdfBook.OriginalFileName, pdfBook.StorageFolderName, false, "application/pdf"); if (!string.IsNullOrEmpty(pdfStorageResult.ExceptionString)) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = $"pdfStorageResult.ExceptionString: {pdfStorageResult.ExceptionString}"; job.EndTime = DateTime.Now; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } pdfBook.PDFFile = pdfStorageResult.Result; } await context.PDFBooks.AddAsync(pdfBook); await context.SaveChangesAsync(); var book = await context.Books.Where(b => b.Id == bookId).SingleAsync(); if (book.CoverImageId == null) { book.CoverImage = RImage.DuplicateExcludingId(pdfBook.CoverImage); context.Update(book); await context.SaveChangesAsync(); } var resFTPUpload = await _UploadPDFBookToExternalServer(pdfBook, context, skipUpload); if (!string.IsNullOrEmpty(resFTPUpload.ExceptionString)) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = $"UploadArtifactToExternalServer: {resFTPUpload.ExceptionString}"; job.EndTime = DateTime.Now; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } job.ProgressPercent = 100; job.Status = ImportJobStatus.Succeeded; job.EndTime = DateTime.Now; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(pdfBook); } catch (Exception exp) { job.EndTime = DateTime.Now; job.Status = ImportJobStatus.Failed; job.Exception = exp.ToString(); job.EndTime = DateTime.Now; context.Update(job); await context.SaveChangesAsync(); return new RServiceResult(null, job.Exception); } } catch(Exception e) { return new RServiceResult(null, e.ToString()); } } private async Task> _ImportAndReturnPDFJobImages(PDFBook pdfBook, ImportJob job, int order) { List pages = new List(); string pdfFilePath = job.ResourceNumber; string intermediateFolder = Path.Combine(Path.GetDirectoryName(pdfFilePath), Path.GetFileNameWithoutExtension(pdfFilePath)); Directory.CreateDirectory(intermediateFolder); List fileNames = new List(); int imageOrder = 1; using (FileStream fs = File.OpenRead(pdfFilePath)) { var skBitmaps = PDFtoImage.Conversion.ToImages(fs); foreach (var skBitmap in skBitmaps) { string outFileName = Path.Combine(intermediateFolder, $"{imageOrder}".PadLeft(4, '0') + ".jpg"); using (FileStream fsOut = File.OpenWrite(outFileName)) { skBitmap.Encode(fsOut, SkiaSharp.SKEncodedImageFormat.Jpeg, 90); } fileNames.Add(outFileName); imageOrder++; } } foreach (string fileName in fileNames) { using (RMuseumDbContext importJobUpdaterDb = new RMuseumDbContext(new DbContextOptions())) { job.ProgressPercent = order * 100 / (decimal)fileNames.Count; importJobUpdaterDb.Update(job); await importJobUpdaterDb.SaveChangesAsync(); } order++; PDFPage page = new PDFPage() { PageNumber = order, Description = "", Tags = new RTagValue[] { }, LastModified = DateTime.Now }; string thumbnailImageName = $"{page.PageNumber}".PadLeft(5, '0') + ".jpg"; string thumbnailPath = Path.Combine(Path.Combine(_imageFileService.ImageStoragePath, pdfBook.StorageFolderName), thumbnailImageName); if (File.Exists(thumbnailPath)) { File.Delete(thumbnailPath); } using (Image img = Image.FromFile(fileName)) { int imageWidth = img.Width; int imageHeight = img.Height; int thumbnailImageWidth = ThumbnailImageWidth; int thumbnailImageHeight = ThumbnailImageWidth * imageHeight / imageWidth; if (thumbnailImageHeight > ThumbnailImageMaxHeight) { thumbnailImageHeight = ThumbnailImageMaxHeight; thumbnailImageWidth = thumbnailImageHeight * imageWidth / imageHeight; } //روی خود img تأثیر می گذارد // به احتمال قوی داریم روی تصویر resize کار می‌کنیم Image thumbnail = new Bitmap(thumbnailImageWidth, thumbnailImageHeight); using (Graphics gThumbnail = Graphics.FromImage(thumbnail)) { gThumbnail.DrawImage(img, 0, 0, thumbnailImageWidth, thumbnailImageHeight); } using (MemoryStream msThumbnail = new MemoryStream()) { thumbnail.Save(msThumbnail, ImageFormat.Jpeg); msThumbnail.Seek(0, SeekOrigin.Begin); RServiceResult picture = await _imageFileService.Add(null, msThumbnail, thumbnailImageName, pdfBook.StorageFolderName); if (picture.Result == null) { throw new Exception($"_imageFileService.Add : {picture.ExceptionString}"); } page.ThumbnailImage = picture.Result; if (page.PageNumber == 1) { pdfBook.CoverImage = RImage.DuplicateExcludingId(picture.Result); } } } pages.Add(page); } foreach (var fileName in fileNames) { File.Delete(fileName); } Directory.Delete(intermediateFolder, true); return pages; } private async Task> _UploadPDFBookToExternalServer(PDFBook book, RMuseumDbContext context, bool skipUpload) { LongRunningJobProgressServiceEF jobProgressServiceEF = new LongRunningJobProgressServiceEF(context); var job = (await jobProgressServiceEF.NewJob("_UploadArtifactToExternalServer", $"Uploading {book.StorageFolderName}")).Result; try { if (bool.Parse(Configuration.GetSection("ExternalFTPServer")["UploadEnabled"])) { var ftpClient = new AsyncFtpClient ( Configuration.GetSection("ExternalFTPServer")["Host"], Configuration.GetSection("ExternalFTPServer")["Username"], Configuration.GetSection("ExternalFTPServer")["Password"] ); if (!skipUpload) { ftpClient.ValidateCertificate += FtpClient_ValidateCertificate; await ftpClient.AutoConnect(); ftpClient.Config.RetryAttempts = 3; } var localPDFFilePath = _imageFileService.GetImagePath(book.PDFFile).Result; var remotePDFFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/{book.StorageFolderName}/{Path.GetFileName(localPDFFilePath)}"; if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localPDFFilePath); await ftpClient.UploadFile(localPDFFilePath, remotePDFFilePath, createRemoteDir: true); } var localCoverImageFilePath = _imageFileService.GetImagePath(book.CoverImage).Result; var remoteCoverImageFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/{book.StorageFolderName}/{Path.GetFileName(localCoverImageFilePath)}"; if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localCoverImageFilePath); await ftpClient.UploadFile(localCoverImageFilePath, remoteCoverImageFilePath, createRemoteDir: true); } book.ExternalPDFFileUrl = remotePDFFilePath; book.ExtenalCoverImageUrl = remoteCoverImageFilePath; context.Update(book); foreach (var item in book.Pages) { var localFilePath = _imageFileService.GetImagePath(item.ThumbnailImage).Result; item.ExtenalThumbnailImageUrl = $"{Configuration.GetSection("ExternalFTPServer")["RootUrl"]}/{book.StorageFolderName}/{Path.GetFileName(localFilePath)}"; context.Update(item); if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localFilePath); var remoteFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/{book.CoverImage.FolderName}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath, createRemoteDir: true); } } if (!skipUpload) { await ftpClient.Disconnect(); } await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); } return new RServiceResult(true); } catch (Exception exp) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); return new RServiceResult(false, exp.ToString()); } } private void FtpClient_ValidateCertificate(BaseFtpClient control, FtpSslValidationEventArgs e) { e.Accept = true; } /// /// عرض تصویر بندانگشتی /// protected int ThumbnailImageWidth { get { return int.Parse($"{Configuration.GetSection("PictureFileService")["ThumbnailImageWidth"]}"); } } /// /// طول تصویر بندانگشتی /// protected int ThumbnailImageMaxHeight { get { return int.Parse($"{Configuration.GetSection("PictureFileService")["ThumbnailMaxHeight"]}"); } } } }