From ad8afedac4684a4fd1dda2fd0a65e5d927ae95fa Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Fri, 7 Aug 2026 17:10:23 +0330 Subject: [PATCH] tajik backend --- RMuseum/Controllers/GanjoorController.cs | 5 +- RMuseum/Services/IGanjoorService.cs | 5 +- .../GanjoorService-Tajik.cs | 87 +++++++++++++------ 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/RMuseum/Controllers/GanjoorController.cs b/RMuseum/Controllers/GanjoorController.cs index 204dfd84..6e8dbb16 100644 --- a/RMuseum/Controllers/GanjoorController.cs +++ b/RMuseum/Controllers/GanjoorController.cs @@ -4529,6 +4529,7 @@ namespace RMuseum.Controllers /// category page, overwriting their stored TajikHtmlText. Needed one-time after changing /// either generator function (e.g. adding poem excerpts to the table of contents), since /// the normal SQLite import skips pages that already exist and never refreshes them. + /// Runs as a background job - check the Admin area's LongRunningJobs page for progress. /// /// [HttpPut] @@ -4536,10 +4537,10 @@ namespace RMuseum.Controllers [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(bool))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] - public async Task RegenerateTajikCatAndPoetHtmlTextAsync() + public IActionResult RegenerateTajikCatAndPoetHtmlTextAsync() { RServiceResult res = - await _ganjoorService.RegenerateTajikCatAndPoetHtmlTextAsync(); + _ganjoorService.RegenerateTajikCatAndPoetHtmlTextAsync(); if (res.Result) return Ok(); return BadRequest(res.ExceptionString); diff --git a/RMuseum/Services/IGanjoorService.cs b/RMuseum/Services/IGanjoorService.cs index 125ffa73..cb02fe54 100644 --- a/RMuseum/Services/IGanjoorService.cs +++ b/RMuseum/Services/IGanjoorService.cs @@ -1585,10 +1585,11 @@ namespace RMuseum.Services /// /// 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 - /// either generator function, since the normal SQLite import skips pages that already exist + /// either generator function, since the normal SQLite import skips pages that already exist. + /// Runs as a background job; returns immediately once the job is queued. /// /// - Task> RegenerateTajikCatAndPoetHtmlTextAsync(); + RServiceResult RegenerateTajikCatAndPoetHtmlTextAsync(); diff --git a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs index 59ed48e8..a9ac2f38 100644 --- a/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs +++ b/RMuseum/Services/Implementation/GanjoorService-Partials/GanjoorService-Tajik.cs @@ -7,6 +7,7 @@ using RMuseum.Models.Ganjoor.ViewModels; using System.Linq; using RSecurityBackend.Services.Implementation; using System.Collections.Generic; +using RMuseum.DbContext; namespace RMuseum.Services.Implementation { @@ -207,43 +208,77 @@ namespace RMuseum.Services.Implementation } /// - /// re-runs PrepareTajikPoetHtmlTextAsync / PrepareTajikCatHtmlTextAsync for every poet and - /// category page that already has a stored TajikPages row, overwriting TajikHtmlText with - /// freshly generated output. Needed one-time after changing either generator function - /// (e.g. adding poem excerpts to the table of contents), because the normal SQLite import - /// (GanjoorService-TajikSQLiteImport.cs) explicitly skips pages that already exist and so - /// never refreshes previously generated HTML on its own. + /// 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 + /// either generator function (e.g. adding poem excerpts to the table of contents), since + /// the normal SQLite import skips pages that already exist and never refreshes them. + /// Runs as a background job (same pattern as TajikImportFromSqlite) since the number of + /// poets/categories/poems involved can be large enough to exceed a normal request timeout; + /// progress can be tracked the same way as other long running jobs (e.g. the Admin area's + /// LongRunningJobs page). /// - public async Task> RegenerateTajikCatAndPoetHtmlTextAsync() + public RServiceResult RegenerateTajikCatAndPoetHtmlTextAsync() { try { - var tajikPoets = await _context.TajikPoets.AsNoTracking().ToListAsync(); - foreach (var tajikPoet in tajikPoets) - { - var poetPage = await _context.GanjoorPages.AsNoTracking().Where(p => p.GanjoorPageType == GanjoorPageType.PoetPage && p.PoetId == tajikPoet.Id).SingleOrDefaultAsync(); - if (poetPage == null) continue; - var tajikPage = await _context.TajikPages.Where(p => p.Id == poetPage.Id).SingleOrDefaultAsync(); - if (tajikPage == null) continue; - tajikPage.TajikHtmlText = await PrepareTajikPoetHtmlTextAsync(_context, tajikPoet); - } - await _context.SaveChangesAsync(); + _backgroundTaskQueue.QueueBackgroundWorkItem + ( + async token => + { + using (RMuseumDbContext context = new RMuseumDbContext(new DbContextOptions())) //this is long running job, so _context might be already been freed/collected by GC + { + LongRunningJobProgressServiceEF jobProgressServiceEF = new LongRunningJobProgressServiceEF(context); + var job = (await jobProgressServiceEF.NewJob("RegenerateTajikCatAndPoetHtmlText", "Query data")).Result; - var tajikCats = await _context.TajikCats.AsNoTracking().ToListAsync(); - foreach (var tajikCat in tajikCats) - { - var tajikPage = await _context.TajikPages.Where(p => p.Id == tajikCat.Id).SingleOrDefaultAsync(); - if (tajikPage == null) continue; - tajikPage.TajikHtmlText = await PrepareTajikCatHtmlTextAsync(_context, tajikCat); - } - await _context.SaveChangesAsync(); + try + { + var tajikPoets = await context.TajikPoets.AsNoTracking().ToListAsync(); + int done = 0; + foreach (var tajikPoet in tajikPoets) + { + await jobProgressServiceEF.UpdateJob(job.Id, done, $"poet: {tajikPoet.Id}"); + var poetPage = await context.GanjoorPages.AsNoTracking().Where(p => p.GanjoorPageType == GanjoorPageType.PoetPage && p.PoetId == tajikPoet.Id).SingleOrDefaultAsync(); + if (poetPage != null) + { + var tajikPage = await context.TajikPages.Where(p => p.Id == poetPage.Id).SingleOrDefaultAsync(); + if (tajikPage != null) + { + tajikPage.TajikHtmlText = await PrepareTajikPoetHtmlTextAsync(context, tajikPoet); + await context.SaveChangesAsync(); + } + } + done++; + } - return new RServiceResult(true); + var tajikCats = await context.TajikCats.AsNoTracking().ToListAsync(); + done = 0; + foreach (var tajikCat in tajikCats) + { + await jobProgressServiceEF.UpdateJob(job.Id, done, $"cat: {tajikCat.Id}"); + var tajikPage = await context.TajikPages.Where(p => p.Id == tajikCat.Id).SingleOrDefaultAsync(); + if (tajikPage != null) + { + tajikPage.TajikHtmlText = await PrepareTajikCatHtmlTextAsync(context, tajikCat); + await context.SaveChangesAsync(); + } + done++; + } + + await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); + } + catch (Exception exp) + { + await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); + } + } + } + ); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } + return new RServiceResult(true); }