using Dapper; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using RMuseum.DbContext; using RMuseum.Models.Ganjoor; using RMuseum.Models.Ganjoor.ViewModels; using RMuseum.Models.GanjoorAudio; using RMuseum.Models.GanjoorAudio.ViewModels; using RMuseum.Models.GanjoorIntegration.ViewModels; using RSecurityBackend.Models.Generic; using System; using System.Data; using System.Data.Common; using System.Linq; using System.Threading.Tasks; namespace RMuseum.Services.Implementation { /// /// IGanjoorService implementation /// public class GanjoorService : IGanjoorService { /// /// get poem by id /// /// /// public async Task> GetPoemById(int id) { try { var poem = await _context.GanjoorPoems.Include(p => p.Cat).Where(p => p.Id == id).SingleOrDefaultAsync(); if (poem == null) return new RServiceResult(null); var cat = poem.Cat; while(cat != null) { cat.Parent = await _context.GanjoorCategories.Where(c => c.Id == cat.ParentId).SingleOrDefaultAsync(); cat = cat.Parent; } return new RServiceResult(poem); } catch(Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poem recitations (PlainText/HtmlText are intentionally empty) /// /// /// public async Task> GetPoemRecitations(int id) { try { var source = from audio in _context.Recitations join poem in _context.GanjoorPoems on audio.GanjoorPostId equals poem.Id where audio.ReviewStatus == AudioReviewStatus.Approved && poem.Id == id orderby audio.AudioOrder select new PublicRecitationViewModel() { Id = audio.Id, PoemId = audio.GanjoorPostId, PoemFullTitle = poem.FullTitle, PoemFullUrl = poem.FullUrl, AudioTitle = audio.AudioTitle, AudioArtist = audio.AudioArtist, AudioArtistUrl = audio.AudioArtistUrl, AudioSrc = audio.AudioSrc, AudioSrcUrl = audio.AudioSrcUrl, LegacyAudioGuid = audio.LegacyAudioGuid, Mp3FileCheckSum = audio.Mp3FileCheckSum, Mp3SizeInBytes = audio.Mp3SizeInBytes, PublishDate = audio.ReviewDate, FileLastUpdated = audio.FileLastUpdated, Mp3Url = $"https://ganjgah.ir/api/audio/file/{audio.Id}.mp3", XmlText = $"https://ganjgah.ir/api/audio/xml/{audio.Id}", PlainText = "", //poem.PlainText HtmlText = "",//poem.HtmlText }; return new RServiceResult(await source.ToArrayAsync()); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poem images by id (some fields are intentionally field with blank or null), /// EntityImageId : the most important data field, image url is https://ganjgah.ir/api/images/thumb/{EntityImageId}.jpg or https://ganjgah.ir/api/images/norm/{EntityImageId}.jpg /// /// /// public async Task> GetPoemImages(int id) { try { var source = from link in _context.GanjoorLinks.Include(l => l.Artifact).Include(l => l.Item).ThenInclude(i => i.Images) join poem in _context.GanjoorPoems on link.GanjoorPostId equals poem.Id where link.ReviewResult == Models.GanjoorIntegration.ReviewResult.Approved && poem.Id == id orderby link.ReviewDate select new GanjoorLinkViewModel() { Id = link.Id, GanjoorPostId = link.GanjoorPostId, GanjoorUrl = $"https://ganjoor.net{poem.FullUrl}", GanjoorTitle = poem.FullTitle, EntityName = $"{link.Artifact.Name} » {link.Item.Name}", EntityFriendlyUrl = $"https://museum.ganjoor.net/items/{link.Artifact.FriendlyUrl}/{link.Item.FriendlyUrl}", EntityImageId = link.Item.Images.First().Id,//the most important data field, image url is https://ganjgah.ir/api/images/thumb/{EntityImageId}.jpg ReviewResult = link.ReviewResult, Synchronized = link.Synchronized, SuggestedBy = null //intentional (this is going to be used in an anonymous method) }; return new RServiceResult(await source.ToArrayAsync()); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } private int _GetRandomPoemId() { //this is magic number based method! int startPoemId = 2130; int endPoemId = 2634 + 1; //one is added for مژده ای دل که مسیحا نفسی می‌آید Random r = new Random(DateTime.Now.Millisecond); int poemId = r.Next(startPoemId, endPoemId); if (poemId == endPoemId) { poemId = 33179;//مژده ای دل که مسیحا نفسی می‌آید } return poemId; } /// /// get a random poem from hafez /// /// public async Task> Faal() { try { int poemId = _GetRandomPoemId(); var poem = (await GetPoemById(poemId)).Result; PublicRecitationViewModel[] recitations = poem == null ? new PublicRecitationViewModel[] { } : (await GetPoemRecitations(poemId)).Result; int loopPreventer = 0; while (poem == null || recitations.Length == 0) { poem = (await GetPoemById(poemId)).Result; recitations = poem == null ? new PublicRecitationViewModel[] { } : (await GetPoemRecitations(poemId)).Result; loopPreventer++; if (loopPreventer > 5) break; } return new RServiceResult ( new GanjoorPoemCompleteViewModel() { Poem = poem, Recitations = recitations, Images = null //no usage for now, so do not waste resources } ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } #region Date import / modifications /// /// imports unimported poem data from a locally accessible ganjoor SqlLite database /// /// public async Task> ImportLocalSQLiteDb() { try { SqliteConnectionStringBuilder connectionStringBuilder = new SqliteConnectionStringBuilder(); connectionStringBuilder.DataSource = Configuration.GetSection("LocalSqliteImport")["FilePath"]; using (SqliteConnection sqliteConnection = new SqliteConnection(connectionStringBuilder.ToString())) { await sqliteConnection.OpenAsync(); IDbConnection dapper = sqliteConnection; using (var sqlConnection = _context.Database.GetDbConnection()) { await sqlConnection.OpenAsync(); foreach (var poet in await dapper.QueryAsync("SELECT * FROM poet ORDER BY id")) { int poetId = (int)poet.id; if ((await _context.GanjoorPoets.Where(p => p.Id == poetId).FirstOrDefaultAsync()) != null) { continue; } DbTransaction transaction = await sqlConnection.BeginTransactionAsync(); try { using (var command = sqlConnection.CreateCommand()) { command.Transaction = transaction; command.CommandText = $"INSERT INTO GanjoorPoets (Id, Name, Description) VALUES (${poet.id}, N'{poet.name}', N'{poet.description}')"; await command.ExecuteNonQueryAsync(); await _ImportSQLiteCatChildren(command, dapper, poetId, 0, "", ""); await transaction.CommitAsync(); } } catch (Exception exp2) { await transaction.RollbackAsync(); return new RServiceResult(false, exp2.ToString()); } } } } } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } return new RServiceResult(true); } private async Task _ImportSQLiteCatChildren(DbCommand command, IDbConnection dapper, int poetId, int catId, string itemFullTitle, string itemFullSlug) { foreach(var cat in await dapper.QueryAsync($"SELECT * FROM cat WHERE poet_id = {poetId} AND parent_id = {catId} ORDER BY id")) { if (catId == 0) { command.CommandText = $"INSERT INTO GanjoorCategories (Id, PoetId, Title, UrlSlug, FullUrl) VALUES (${cat.id}, {poetId}, N'{cat.text}', '{ _ExtractUrlSlug(cat.url)}', '{itemFullSlug}/{ _ExtractUrlSlug(cat.url)}')"; } else { command.CommandText = $"INSERT INTO GanjoorCategories (Id, PoetId, Title, ParentId, UrlSlug, FullUrl) VALUES (${cat.id}, {poetId}, N'{cat.text}', {catId}, '{ _ExtractUrlSlug(cat.url)}', '{itemFullSlug}/{ _ExtractUrlSlug(cat.url)}')"; } await command.ExecuteNonQueryAsync(); foreach (var poem in await dapper.QueryAsync($"SELECT * FROM poem WHERE cat_id = {cat.id} ORDER BY id")) { command.CommandText = $"INSERT INTO GanjoorPoems (Id, CatId, Title, UrlSlug, FullTitle, FullUrl) VALUES (${poem.id}, {cat.id}, N'{poem.title}', '{ _ExtractUrlSlug(poem.url)}', N'{$"{itemFullTitle}{cat.text} » {poem.title}"}', '{itemFullSlug}/{ _ExtractUrlSlug(cat.url)}/{_ExtractUrlSlug(poem.url)}')"; await command.ExecuteNonQueryAsync(); foreach (var verse in await dapper.QueryAsync($"SELECT * FROM verse WHERE poem_id = {poem.id} ORDER BY vorder")) { command.CommandText = $"INSERT INTO GanjoorVerses (PoemId, VOrder, VersePosition, Text) VALUES (${poem.id}, {verse.vorder}, {verse.position}, N'{verse.text}')"; await command.ExecuteNonQueryAsync(); } } await _ImportSQLiteCatChildren(command, dapper, poetId, (int)cat.id, $"{itemFullTitle}{cat.text} » ", $"{itemFullSlug}/{ _ExtractUrlSlug(cat.url)}"); } } private static string _ExtractUrlSlug(string slug) { if (!string.IsNullOrEmpty(slug)) { //sample: https://ganjoor.net/hafez if (slug.LastIndexOf('/') != -1) { // => result = hafez slug = slug.Substring(slug.LastIndexOf('/') + 1); } } return slug; } /// /// updates poems text /// /// public RServiceResult UpdatePoemsText() { try { _backgroundTaskQueue.QueueBackgroundWorkItem ( async token => { using (RMuseumDbContext context = new RMuseumDbContext(Configuration)) //this is long running job, so _context might be already been freed/collected by GC { var poems = await context.GanjoorPoems.Where(p => p.PlainText == null).ToListAsync(); int n = 0; foreach (GanjoorPoem poem in poems) { var verses = await context.GanjoorVerses.Where(v => v.PoemId == poem.Id).OrderBy(v => v.VOrder).ToListAsync(); string plainText = ""; string htmlText = ""; for (int vIndex = 0; vIndex < verses.Count; vIndex++) { GanjoorVerse v = verses[vIndex]; if (string.IsNullOrEmpty(plainText)) plainText = v.Text; else { plainText += $" {v.Text}"; } switch (v.VersePosition) { case VersePosition.CenteredVerse1: if (((vIndex + 1) < verses.Count) && (verses[vIndex + 1].VersePosition == VersePosition.CenteredVerse2)) { htmlText += $"

{v.Text.Replace("ـ", "").Trim()}

{Environment.NewLine}"; //div is not closed } else { htmlText += $"

{v.Text.Replace("ـ", "").Trim()}

{Environment.NewLine}"; } break; case VersePosition.CenteredVerse2: htmlText += $"

{v.Text.Replace("ـ", "").Trim()}

{Environment.NewLine}"; break; case VersePosition.Right: htmlText += $"

{v.Text.Replace("ـ", "").Trim()}

{Environment.NewLine}"; break; case VersePosition.Left: htmlText += $"

{v.Text.Replace("ـ", "").Trim()}

{Environment.NewLine}"; break; case VersePosition.Paragraph: case VersePosition.Single: { string[] lines = v.Text.Replace("ـ", "").Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length != 0) { if (v.Text.Replace("ـ", "").Length / lines.Length < 150) { htmlText += $"

{v.Text.Replace("ـ", "").Replace("\r\n", " ")}

{Environment.NewLine}"; } else { foreach (string line in lines) htmlText += $"

{line}

{Environment.NewLine}"; } } } break; } } poem.PlainText = plainText; poem.HtmlText = htmlText; context.Update(poem); n++; if (n >= 1000) { await context.SaveChangesAsync(); n = 0; } } if(n > 0) await context.SaveChangesAsync(); } }); return new RServiceResult(true); } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } } #endregion /// /// Database Contetxt /// protected readonly RMuseumDbContext _context; /// /// Configuration /// protected IConfiguration Configuration { get; } /// /// Background Task Queue Instance /// protected readonly IBackgroundTaskQueue _backgroundTaskQueue; /// /// constructor /// /// /// /// public GanjoorService(RMuseumDbContext context, IConfiguration configuration, IBackgroundTaskQueue backgroundTaskQueue) { _context = context; _backgroundTaskQueue = backgroundTaskQueue; Configuration = configuration; } } }