diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml
index cf8acb99..43bb8a1d 100644
--- a/RMuseum/RMuseum.xml
+++ b/RMuseum/RMuseum.xml
@@ -18359,6 +18359,13 @@
PDF Library Services
+
+
+ batch import soha library
+
+ from 0
+
+
start importing local pdf file
diff --git a/RMuseum/Services/Implementation/PDFLibraryService-Partials/PDFLibraryService-eliteraturebook.cs b/RMuseum/Services/Implementation/PDFLibraryService-Partials/PDFLibraryService-eliteraturebook.cs
new file mode 100644
index 00000000..6e58c9f3
--- /dev/null
+++ b/RMuseum/Services/Implementation/PDFLibraryService-Partials/PDFLibraryService-eliteraturebook.cs
@@ -0,0 +1,652 @@
+using DNTPersianUtils.Core;
+using ganjoor;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+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 System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+namespace RMuseum.Services.Implementation
+{
+ public partial class PDFLibraryService
+ {
+ private async Task ImportELiteratureBookLibraryUrlAsync(string srcUrl, RMuseumDbContext context)
+ {
+
+ var oldJobs = await context.ImportJobs.ToArrayAsync();
+ context.RemoveRange(oldJobs);
+ await context.SaveChangesAsync();
+
+ ImportJob job = new ImportJob()
+ {
+ JobType = JobType.Pdf,
+ SrcContent = "",
+ ResourceNumber = "scrapping ..." + srcUrl,
+ FriendlyUrl = "",
+ SrcUrl = "",
+ QueueTime = DateTime.Now,
+ ProgressPercent = 0,
+ Status = ImportJobStatus.NotStarted
+ };
+ await context.ImportJobs.AddAsync
+ (
+ job
+ );
+ await context.SaveChangesAsync();
+
+ try
+ {
+ var pdfSource = await context.PDFSources.Where(s => s.Name == "سها").SingleOrDefaultAsync();
+ if (pdfSource == null)
+ {
+ PDFSource newSource = new PDFSource()
+ {
+ Name = "سها",
+ Url = "https://sohalibrary.com",
+ Description = "سامانهٔ جستجوی یکپارچهٔ نرمافزار سنا"
+ };
+ context.PDFSources.Add(newSource);
+ await context.SaveChangesAsync();
+ pdfSource = await context.PDFSources.Where(s => s.Name == "سها").SingleAsync();
+ }
+ NewPDFBookViewModel model = new NewPDFBookViewModel();
+ model.PDFSourceId = pdfSource.Id;
+ model.OriginalSourceName = pdfSource.Name;
+ model.OriginalSourceUrl = srcUrl;
+ model.BookScriptType = BookScriptType.Printed;
+ model.Language = "فارسی";
+ model.SkipUpload = true;
+
+ string html = "";
+ using (var client = new HttpClient())
+ {
+ using (var result = await client.GetAsync(srcUrl))
+ {
+ if (result.IsSuccessStatusCode)
+ {
+ html = await result.Content.ReadAsStringAsync();
+ }
+ else
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"Http result is not ok ({result.StatusCode}) for {srcUrl}";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+ }
+ }
+
+ if (html.IndexOf("/item/download/") == -1)
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"/item/download/ not found in html source.";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+
+ List meta = new List();
+ int idxStart;
+ int idx = html.IndexOf("branch-link");
+ string firstHandSource = "";
+ if (idx != -1)
+ {
+ idxStart = html.IndexOf(">", idx);
+ if (idxStart != -1)
+ {
+ int idxEnd = html.IndexOf("<", idxStart);
+
+ if (idxEnd != -1)
+ {
+ firstHandSource = html.Substring(idxStart + 1, idxEnd - idxStart - 1);
+ meta.Add
+ (
+ await TagHandler.PrepareAttribute(context, "First Hand Source", firstHandSource, 1)
+ );
+ }
+ }
+ }
+
+ if (firstHandSource != "کتابخانه تخصصی ادبیات")
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"کتابخانه تخصصی ادبیات not found.";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+
+
+
+ idx = html.IndexOf("title-normal-for-book-name");
+ if (idx == -1)
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"title-normal-for-book-name not found in {srcUrl}";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+ idxStart = html.IndexOf(">", idx);
+ if (idxStart != -1)
+ {
+ int idxEnd = html.IndexOf("<", idxStart);
+
+ if (idxEnd != -1)
+ {
+ model.Title = html.Substring(idxStart + 1, idxEnd - idxStart - 1);
+ //we can try to extract volume information from title here
+ model.Title = model.Title.Trim();
+ }
+ }
+
+
+
+ idxStart = html.IndexOf("width-150");
+ while (idxStart != -1)
+ {
+ idxStart = html.IndexOf(">", idxStart);
+ if (idxStart == -1) break;
+ int idxEnd = html.IndexOf("<", idxStart);
+ if (idxEnd == -1) break;
+
+ string tagName = html.Substring(idxStart + 1, idxEnd - idxStart - 1).ToPersianNumbers().ApplyCorrectYeKe().Trim();
+
+ idxStart = html.IndexOf("value-name", idxEnd);
+ if (idxStart == -1) break;
+ idxStart = html.IndexOf(">", idxStart);
+ if (idxStart == -1) break;
+ idxEnd = html.IndexOf("", idxStart);
+ if (idxEnd == -1) break;
+
+ string tagValue = html.Substring(idxStart + 1, idxEnd - idxStart - 1);
+ tagValue = Regex.Replace(tagValue, "<.*?>", string.Empty).Trim();
+
+ string tagValueCleaned = tagValue.ToPersianNumbers().ApplyCorrectYeKe();
+
+ if (tagName == "نویسنده")
+ {
+ model.AuthorsLine = tagValueCleaned;
+ tagName = "Author";
+
+ string[] authors = tagValueCleaned.Split(',', StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < authors.Length; i++)
+ {
+ string authorTrimmed = authors[i].Trim();
+ var existingAuthor = await context.Authors.AsNoTracking().Where(a => a.Name == authorTrimmed).FirstOrDefaultAsync();
+ if (existingAuthor != null)
+ {
+ if (i == 0)
+ {
+ model.WriterId = existingAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Writer2Id = existingAuthor.Id;
+ }
+ if (i == 2)
+ {
+ model.Writer3Id = existingAuthor.Id;
+ }
+ if (i == 3)
+ {
+ model.Writer4Id = existingAuthor.Id;
+ }
+
+ }
+ else
+ {
+ var newAuthor = new Author()
+ {
+ Name = authorTrimmed
+ };
+ context.Authors.Add(newAuthor);
+ await context.SaveChangesAsync();
+ if (i == 0)
+ {
+ model.WriterId = newAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Writer2Id = newAuthor.Id;
+ }
+ if (i == 2)
+ {
+ model.Writer3Id = newAuthor.Id;
+ }
+ if (i == 3)
+ {
+ model.Writer4Id = newAuthor.Id;
+ }
+ }
+ }
+
+
+ }
+ if (tagName == "مترجم")
+ {
+ model.TranslatorsLine = tagValueCleaned;
+ model.IsTranslation = true;
+ tagName = "Translator";
+
+ string[] authors = tagValueCleaned.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < authors.Length; i++)
+ {
+ string authorTrimmed = authors[i].Trim();
+ var existingAuthor = await context.Authors.AsNoTracking().Where(a => a.Name == authorTrimmed).FirstOrDefaultAsync();
+ if (existingAuthor != null)
+ {
+ if (i == 0)
+ {
+ model.TranslatorId = existingAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Translator2Id = existingAuthor.Id;
+ }
+ if (i == 2)
+ {
+ model.Translator3Id = existingAuthor.Id;
+ }
+ if (i == 3)
+ {
+ model.Translator4Id = existingAuthor.Id;
+ }
+ }
+ else
+ {
+ var newAuthor = new Author()
+ {
+ Name = authorTrimmed
+ };
+ context.Authors.Add(newAuthor);
+ await context.SaveChangesAsync();
+
+ if (i == 0)
+ {
+ model.TranslatorId = newAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Translator2Id = newAuthor.Id;
+ }
+ if (i == 2)
+ {
+ model.Translator3Id = newAuthor.Id;
+ }
+ if (i == 3)
+ {
+ model.Translator4Id = newAuthor.Id;
+ }
+ }
+ }
+
+
+ }
+ if (tagName == "مصحح")
+ {
+ tagName = "Collector";
+
+ string[] authors = tagValueCleaned.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < authors.Length; i++)
+ {
+ string authorTrimmed = authors[i].Trim();
+ var existingAuthor = await context.Authors.AsNoTracking().Where(a => a.Name == authorTrimmed).FirstOrDefaultAsync();
+ if (existingAuthor != null)
+ {
+ if (i == 0)
+ {
+ model.CollectorId = existingAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Collector2Id = existingAuthor.Id;
+ }
+
+ }
+ else
+ {
+ var newAuthor = new Author()
+ {
+ Name = authorTrimmed
+ };
+ context.Authors.Add(newAuthor);
+ await context.SaveChangesAsync();
+ if (i == 0)
+ {
+ model.CollectorId = newAuthor.Id;
+ }
+ if (i == 1)
+ {
+ model.Collector2Id = newAuthor.Id;
+ }
+ }
+ }
+
+
+ }
+ if (tagName == "زبان")
+ {
+ model.Language = tagValueCleaned;
+ tagName = "Language";
+
+ if (tagValueCleaned != "فارسی")
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = "Language is not فارسی";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+ }
+ if (tagName == "شماره جلد")
+ {
+ if (int.TryParse(tagValue, out int v))
+ {
+ model.VolumeOrder = v;
+ }
+ tagName = "Volume";
+ }
+ if (tagName == "ناشر")
+ {
+ model.PublisherLine = tagValueCleaned;
+ tagName = "Publisher";
+ }
+ if (tagName == "محل نشر")
+ {
+ model.PublishingLocation = tagValueCleaned;
+ tagName = "Publishing Location";
+ }
+ if (tagName == "تاریخ انتشار")
+ {
+ model.PublishingDate = tagValueCleaned;
+ tagName = "Publishing Date";
+ }
+ if (tagName == "موضوع")
+ {
+ tagName = "Subject";
+ }
+ if (tagName == "تعداد صفحات")
+ {
+ if (tagValue.Contains("ـ"))
+ {
+ tagValue = tagValue.Substring(tagValue.IndexOf("ـ") + 1).Trim();
+
+ }
+
+ if (int.TryParse(tagValue, out int v))
+ {
+ model.ClaimedPageCount = v;
+ }
+
+ tagName = "Page Count";
+ }
+
+ meta.Add
+ (
+ await TagHandler.PrepareAttribute(context, tagName, tagValueCleaned, 1)
+ );
+ idxStart = html.IndexOf("width-150", idxEnd);
+
+ }
+
+ string bookTitle = model.Title;
+ int volumeNumber = 0;
+ if (bookTitle.Contains("ـ ج"))
+ {
+ bookTitle = bookTitle.Substring(0, model.Title.IndexOf("ـ ج") - 1);
+ int.TryParse(model.Title.Substring(model.Title.IndexOf("ـ ج") + "ـ ج".Length).Trim(), out volumeNumber);
+ }
+
+ bookTitle = bookTitle.ToPersianNumbers().ApplyCorrectYeKe().Trim();
+ model.Title = model.Title.ToPersianNumbers().ApplyCorrectYeKe().Trim();
+
+ var book = await context.Books.AsNoTracking().Where(b => b.Name == bookTitle).FirstOrDefaultAsync();
+ if (book != null)
+ {
+ model.BookId = book.Id;
+ }
+ else
+ {
+ Book newBook = new Book()
+ {
+ Name = bookTitle,
+ Description = "",
+ LastModified = DateTime.Now,
+ };
+ context.Books.Add(newBook);
+ await context.SaveChangesAsync();
+ model.BookId = newBook.Id;
+ }
+
+ if (volumeNumber != 0)
+ {
+ MultiVolumePDFCollection collection = await context.MultiVolumePDFCollections.Where(v => v.Name == bookTitle && v.BookId == model.BookId).SingleOrDefaultAsync();
+ if (collection != null)
+ {
+ model.MultiVolumePDFCollectionId = collection.Id;
+
+ collection.VolumeCount += 1;
+ context.Update(collection);
+ await context.SaveChangesAsync();
+
+ }
+ else
+ {
+ MultiVolumePDFCollection newCollection = new MultiVolumePDFCollection()
+ {
+ Name = bookTitle,
+ BookId = model.BookId,
+ Description = "",
+ VolumeCount = 1,
+ };
+ context.MultiVolumePDFCollections.Add(newCollection);
+ await context.SaveChangesAsync();
+ model.MultiVolumePDFCollectionId = newCollection.Id;
+ }
+ }
+
+ idx = html.IndexOf("/item/download/");
+ int idxQuote = html.IndexOf('"', idx);
+ string downloadUrl = html.Substring(idx, idxQuote - idx);
+ downloadUrl = "https://sohalibrary.com" + downloadUrl;
+
+
+ model.OriginalFileUrl = downloadUrl;
+
+ using (var client = new HttpClient())
+ {
+ using (var result = await client.GetAsync(downloadUrl))
+ {
+ if (result.IsSuccessStatusCode)
+ {
+ string fileName = string.Empty;
+ if (result.Content.Headers.ContentDisposition != null)
+ {
+ fileName = System.Net.WebUtility.UrlDecode(result.Content.Headers.ContentDisposition.FileName).Replace("\"", "");
+ }
+
+ if (string.IsNullOrEmpty(fileName) || File.Exists(Path.Combine(_imageFileService.ImageStoragePath, fileName)))
+ {
+ fileName = downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1) + ".pdf";
+ }
+
+ if (fileName.Length > 70)
+ {
+ fileName = Path.GetFileNameWithoutExtension(fileName).Substring(0, 64) + ".pdf";
+ }
+
+ model.LocalImportingPDFFilePath = Path.Combine(_imageFileService.ImageStoragePath, fileName);
+ if (File.Exists(model.LocalImportingPDFFilePath))
+ File.Delete(model.LocalImportingPDFFilePath);
+
+ using (Stream pdfStream = await result.Content.ReadAsStreamAsync())
+ {
+ pdfStream.Seek(0, SeekOrigin.Begin);
+ using (FileStream fs = File.OpenWrite(model.LocalImportingPDFFilePath))
+ {
+ pdfStream.CopyTo(fs);
+ }
+ }
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Succeeded;
+ context.Update(job);
+ await context.SaveChangesAsync();
+
+ string fileChecksum = PoemAudio.ComputeCheckSum(model.LocalImportingPDFFilePath);
+
+ var res = await ImportLocalPDFFileAsync(context, model, fileChecksum);
+ File.Delete(model.LocalImportingPDFFilePath);
+ if (res.Result != null)
+ {
+ var pdf = await context.PDFBooks.Include(p => p.Tags).Where(p => p.Id == res.Result.Id).SingleAsync();
+ if (pdf.Tags.Count > 0)
+ {
+ //not working
+ foreach (var tag in meta)
+ {
+ pdf.Tags.Add(tag);
+ }
+ }
+ else
+ {
+ pdf.Tags = meta;
+ }
+
+ context.Update(pdf);
+ await context.SaveChangesAsync();
+ }
+
+ }
+ else
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"Http result is not ok ({result.StatusCode}) for {downloadUrl}";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+ }
+ }
+
+
+
+ }
+ catch (Exception e)
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = e.ToString();
+ job.EndTime = DateTime.Now;
+ context.Update(job);
+ await context.SaveChangesAsync();
+ }
+
+
+ }
+
+ ///
+ /// batch import soha library
+ ///
+ /// from 0
+ ///
+ public void BatchImportELiteratureBookLibraryAsync(int ajaxPageIndexStart, int ajaxPageIndexEnd)
+ {
+ _backgroundTaskQueue.QueueBackgroundWorkItem
+ (
+ async token =>
+ {
+ using (RMuseumDbContext context = new RMuseumDbContext(new DbContextOptions()))
+ {
+ string ajaxPageUrl = "https://eliteraturebook.com/index";
+ for (int nAjaxPageIndex = ajaxPageIndexStart; nAjaxPageIndex <= ajaxPageIndexEnd; nAjaxPageIndex++)
+ {
+ try
+ {
+ ImportJob job = new ImportJob()
+ {
+ JobType = JobType.Pdf,
+ SrcContent = "",
+ ResourceNumber = $"scrapping ajax page ... {nAjaxPageIndex}",
+ FriendlyUrl = "",
+ SrcUrl = "",
+ QueueTime = DateTime.Now,
+ ProgressPercent = 0,
+ Status = ImportJobStatus.NotStarted
+ };
+ await context.ImportJobs.AddAsync
+ (
+ job
+ );
+ await context.SaveChangesAsync();
+ string html = string.Empty;
+ using (var client = new HttpClient())
+ {
+ using (var result = await client.PostAsync(ajaxPageUrl,
+ new StringContent($"ajaxPage={nAjaxPageIndex}")
+ ))
+ {
+ if (result.IsSuccessStatusCode)
+ {
+ html = await result.Content.ReadAsStringAsync();
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Succeeded;
+ context.Update(job);
+ await context.SaveChangesAsync();
+ }
+ else
+ {
+ job.EndTime = DateTime.Now;
+ job.Status = ImportJobStatus.Failed;
+ job.Exception = $"Http result is not ok ({result.StatusCode}) for ajaxPage={nAjaxPageIndex}";
+ context.Update(job);
+ await context.SaveChangesAsync();
+ return;
+ }
+ }
+ }
+ if(!string.IsNullOrEmpty(html))
+ {
+ int idx = html.IndexOf("https://eliteraturebook.com/books/view/");
+ while(idx != -1)
+ {
+ int endIdx = html.IndexOf("\"", idx);
+ string srcUrl = html.Substring(idx, endIdx - idx);
+ idx = html.IndexOf("https://eliteraturebook.com/books/view/", idx + 1);
+ await ImportELiteratureBookLibraryUrlAsync(srcUrl, context);
+ }
+
+ }
+
+ }
+ catch
+ {
+ //ignore
+ }
+ }
+ }
+ }
+ );
+ }
+
+ }
+}
+