diff --git a/RMuseum/Controllers/GanjoorController.cs b/RMuseum/Controllers/GanjoorController.cs index 05467363..57d1ff37 100644 --- a/RMuseum/Controllers/GanjoorController.cs +++ b/RMuseum/Controllers/GanjoorController.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using RMuseum.Models.Auth.Memory; using RMuseum.Models.Auth.ViewModel; using RMuseum.Models.Ganjoor; -using RMuseum.Models.Ganjoor.PublicExport; using RMuseum.Models.Ganjoor.ViewModels; using RMuseum.Models.GanjoorAudio.ViewModels; using RMuseum.Models.GanjoorIntegration; @@ -2424,81 +2423,6 @@ namespace RMuseum.Controllers } } - /// - /// start exporting all published Ganjoor data to the public git-tracked JSON data set - /// - /// - [HttpPost("publicdata/batchexport")] - [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] - [ProducesResponseType((int)HttpStatusCode.Unauthorized)] - public IActionResult StartBatchExportPublicGitData() - { - try - { - var res = _ganjoorService.StartBatchExportPublicGitData(); - if (!string.IsNullOrEmpty(res.ExceptionString)) - return BadRequest(res.ExceptionString); - return Ok(); - } - catch (Exception exp) - { - return BadRequest(exp.ToString()); - } - } - - /// - /// start exporting the Tajik (Cyrillic) overlay data to its own, separate public - /// git-tracked JSON data set — only poets/categories/poems with an actual Tajik - /// translation are included - /// - /// - [HttpPost("tajik/publicdata/batchexport")] - [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] - [ProducesResponseType((int)HttpStatusCode.Unauthorized)] - public IActionResult StartBatchExportTajikPublicGitData() - { - try - { - var res = _ganjoorService.StartBatchExportTajikPublicGitData(); - if (!string.IsNullOrEmpty(res.ExceptionString)) - return BadRequest(res.ExceptionString); - return Ok(); - } - catch (Exception exp) - { - return BadRequest(exp.ToString()); - } - } - - /// - /// (re)build local Ganjoor content from a public data export tree (local folder or HTTP) — - /// intended for local development use, not for production servers - /// - /// - [HttpPost("publicdata/import")] - [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] - [ProducesResponseType((int)HttpStatusCode.Unauthorized)] - public IActionResult StartImportFromPublicDataRepo([FromBody] PublicDataImportRequestDto request) - { - try - { - var res = _ganjoorService.StartImportFromPublicDataRepo(request.UseHttp, request.Location, request.PoetId); - if (!string.IsNullOrEmpty(res.ExceptionString)) - return BadRequest(res.ExceptionString); - return Ok(); - } - catch (Exception exp) - { - return BadRequest(exp.ToString()); - } - } - /// /// Get user public profile /// diff --git a/RMuseum/Models/Ganjoor/ViewModels/GanjoorVerseViewModel.cs b/RMuseum/Models/Ganjoor/ViewModels/GanjoorVerseViewModel.cs index 7f565dcd..457ee8db 100644 --- a/RMuseum/Models/Ganjoor/ViewModels/GanjoorVerseViewModel.cs +++ b/RMuseum/Models/Ganjoor/ViewModels/GanjoorVerseViewModel.cs @@ -1,6 +1,5 @@ namespace RMuseum.Models.Ganjoor.ViewModels -{ - /// +{ /// /// Ganjoor Verse View Model /// public class GanjoorVerseViewModel @@ -60,6 +59,13 @@ /// public string CoupletSummary { get; set; } + /// + /// original (Persian) text - only populated for Tajik responses that need to show + /// the Persian original alongside the Tajik transliteration. This is a pure view + /// model (not an EF entity), so no [NotMapped] or migration concerns here. + /// + public string OriginalText { get; set; } + public override string ToString() { return Text; diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml index 93d37169..bfebbe4c 100644 --- a/RMuseum/RMuseum.xml +++ b/RMuseum/RMuseum.xml @@ -1775,27 +1775,6 @@ - - - start exporting all published Ganjoor data to the public git-tracked JSON data set - - - - - - start exporting the Tajik (Cyrillic) overlay data to its own, separate public - git-tracked JSON data set — only poets/categories/poems with an actual Tajik - translation are included - - - - - - (re)build local Ganjoor content from a public data export tree (local folder or HTTP) — - intended for local development use, not for production servers - - - Get user public profile @@ -13590,6 +13569,13 @@ couplet summary + + + original (Persian) text - only populated for Tajik responses that need to show + the Persian original alongside the Tajik transliteration. This is a pure view + model (not an EF entity), so no [NotMapped] or migration concerns here. + + Ganjoor Poem related music track model @@ -17537,31 +17523,6 @@ - - - start exporting all published data (poets/categories/poems/verses) to the public - git-tracked JSON data set, committing and pushing changes since the last run - - - - - - start exporting the Tajik (Cyrillic) overlay data to its own separate git-tracked JSON - data set — only entities that actually have a Tajik translation are included - - - - - - (re)build local Ganjoor content (poets/categories/poems/verses/sections + their pages) - from a public data export tree, read locally or over HTTP. Safe to re-run — existing - entities (by id) are left untouched, only missing ones are added. - - true: fetch over HTTP (location is a base URL). false: read from a local folder (location is a path). - base URL or local folder path of the exported data tree - 0 imports every poet; a specific id imports only that poet (useful on a slow connection) - - examine site pages for broken links @@ -21657,6 +21618,16 @@ short plain-text excerpt (first non-empty line) for a Tajik poem's next/previous nav link + + + Tajik counterpart of PrepareHtmlText (GanjoorService-SQLiteImport.cs) - same + VersePosition-based structure/CSS classes, but each verse's Persian original + (GanjoorVerse.OriginalText, set just before this is called) is rendered as an + extra <p class="tg-fa" dir="rtl"> line directly under its Tajik line, for + an interlinear/bilingual display. Computed fresh per-request rather than stored, + since it's cheap given the verses are already loaded for the response. + + re-runs the Tajik poet/category HTML generators for every already-imported poet and diff --git a/RMuseum/Services/IGanjoorService.cs b/RMuseum/Services/IGanjoorService.cs index 39024c18..90b011e2 100644 --- a/RMuseum/Services/IGanjoorService.cs +++ b/RMuseum/Services/IGanjoorService.cs @@ -633,31 +633,6 @@ namespace RMuseum.Services /// RServiceResult StartBatchGenerateGDBFiles(); - /// - /// start exporting all published data (poets/categories/poems/verses) to the public - /// git-tracked JSON data set, committing and pushing changes since the last run - /// - /// - RServiceResult StartBatchExportPublicGitData(); - - /// - /// start exporting the Tajik (Cyrillic) overlay data to its own separate git-tracked JSON - /// data set — only entities that actually have a Tajik translation are included - /// - /// - RServiceResult StartBatchExportTajikPublicGitData(); - - /// - /// (re)build local Ganjoor content (poets/categories/poems/verses/sections + their pages) - /// from a public data export tree, read locally or over HTTP. Safe to re-run — existing - /// entities (by id) are left untouched, only missing ones are added. - /// - /// true: fetch over HTTP (location is a base URL). false: read from a local folder (location is a path). - /// base URL or local folder path of the exported data tree - /// 0 imports every poet; a specific id imports only that poet (useful on a slow connection) - /// - RServiceResult StartImportFromPublicDataRepo(bool useHttp, string location, int poetId = 0); - /// /// examine site pages for broken links /// diff --git a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs index 2ef58d00..7621521e 100644 --- a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs +++ b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs @@ -180,8 +180,13 @@ namespace RMuseum.Services.Implementation var tajikVerses = await _context.TajikVerses.AsNoTracking().Where(v => v.PoemId == page.Poem.Id).OrderBy(v => v.VOrder).ToListAsync(); foreach( var verse in page.Poem.Verses) { + verse.OriginalText = verse.Text; verse.Text = tajikVerses.Where(v => v.VOrder == verse.VOrder).Single().TajikText; } + // recompute fresh on every request (verses are already in memory) rather than + // relying on the precomputed TajikHtmlText blob - keeps this in sync with the + // verse data automatically, no regeneration job needed if it ever changes again + page.HtmlText = PrepareBilingualHtmlText(page.Poem.Verses); if (page.Poem.Recitations != null) { @@ -245,6 +250,101 @@ namespace RMuseum.Services.Implementation return firstLine.Length > 80 ? firstLine.Substring(0, 80) + "…" : firstLine; } + /// + /// Tajik counterpart of PrepareHtmlText (GanjoorService-SQLiteImport.cs) - same + /// VersePosition-based structure/CSS classes, but each verse's Persian original + /// (GanjoorVerse.OriginalText, set just before this is called) is rendered as an + /// extra <p class="tg-fa" dir="rtl"> line directly under its Tajik line, for + /// an interlinear/bilingual display. Computed fresh per-request rather than stored, + /// since it's cheap given the verses are already loaded for the response. + /// + private static string PrepareBilingualHtmlText(GanjoorVerseViewModel[] verses) + { + string FaLine(string text) => string.IsNullOrEmpty(text) ? "" : $"

{text}

"; + + string htmlText = ""; + int coupletIndex = 0; + for (int vIndex = 0; vIndex < verses.Length; vIndex++) + { + GanjoorVerseViewModel v = verses[vIndex]; + if (v.VersePosition == VersePosition.CenteredVerse1) + { + coupletIndex++; + if (((vIndex + 1) < verses.Length) && (verses[vIndex + 1].VersePosition == VersePosition.CenteredVerse2)) + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}{Environment.NewLine}"; + } + else + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}"; + } + } + else + if (v.VersePosition == VersePosition.CenteredVerse2) + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}"; + } + else + if (v.VersePosition == VersePosition.Right) + { + coupletIndex++; + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}"; + } + else + if (v.VersePosition == VersePosition.Left) + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}"; + } + else + if (v.VersePosition == VersePosition.Comment) + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}"; + } + else + if (v.VersePosition == VersePosition.Paragraph || v.VersePosition == VersePosition.Single) + { + coupletIndex++; + string[] lines = v.Text.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + string[] faLines = string.IsNullOrEmpty(v.OriginalText) ? new string[0] : v.OriginalText.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + + string cssClass = v.VersePosition == VersePosition.Paragraph ? "n" : "l"; + + if (lines.Length != 0) + { + if (v.Text.Length / lines.Length < 150) + { + htmlText += $"

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

{FaLine(v.OriginalText?.Replace("\r\n", " "))}
{Environment.NewLine}"; + } + else + { + // Persian original might not split into the exact same number of + // lines as its Tajik translation - matched by index on a best-effort + // basis; this branch only fires for long paragraph/note text, not the + // actual poem verses (Right/Left/CenteredVerse), so a rare misalignment + // here is a minor cosmetic issue, not a poem-text-accuracy one + for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++) + { + string faOne = lineIndex < faLines.Length ? faLines[lineIndex] : ""; + htmlText += $"

{lines[lineIndex]}

{FaLine(faOne)}
{Environment.NewLine}"; + } + } + } + else + { + if (string.IsNullOrEmpty(v.Text)) + { + htmlText += $"

 

{Environment.NewLine}";//empty line! + } + else + { + htmlText += $"

{v.Text}

{FaLine(v.OriginalText)}
{Environment.NewLine}";//not brave enough to ignore it! + } + } + } + } + return htmlText.Trim(); + } + /// /// re-runs the Tajik poet/category HTML generators for every already-imported poet and /// category page, overwriting their stored TajikHtmlText. Needed one-time after changing