diff --git a/backend/LexBoxApi/Controllers/ProjectController.cs b/backend/LexBoxApi/Controllers/ProjectController.cs index 838c61847..5176c46e3 100644 --- a/backend/LexBoxApi/Controllers/ProjectController.cs +++ b/backend/LexBoxApi/Controllers/ProjectController.cs @@ -232,6 +232,17 @@ private async Task StreamHttpResponse(HttpContent hgResult) await hgResult.CopyToAsync(writer.AsStream()); } + [HttpGet("getLdmlZip")] // TODO: Discuss endpoint name, and whether it should be GET or POST, at next opportunity + [AdminRequired] // TODO: Decide on permissions, because we don't want everyone triggering this + public async Task GetLdmlZip(CancellationToken token) + { + var path = await projectService.PrepareLdmlZip(token); + Response.Headers.ContentDisposition = "attachment;filename=\"ldml.zip\""; // TODO: Put timestamp in filename, or use the filename that PrepareLdmlZip returns once it has a timestamp in it + Response.ContentType = "application/zip"; + Response.StatusCode = 200; + await Response.SendFileAsync(path, token); + } + [HttpPost("updateMissingLanguageList")] public async Task> UpdateMissingLanguageList(int limit = 10) { diff --git a/backend/LexBoxApi/Jobs/DelayedLexJob.cs b/backend/LexBoxApi/Jobs/DelayedLexJob.cs index 25060d61b..59079cd9a 100644 --- a/backend/LexBoxApi/Jobs/DelayedLexJob.cs +++ b/backend/LexBoxApi/Jobs/DelayedLexJob.cs @@ -17,7 +17,8 @@ protected static async Task QueueJob(ISchedulerFactory schedulerFactory, data[nameof(JobTriggerTraceId)] = Activity.Current?.Context.TraceId.ToHexString() ?? string.Empty; data[nameof(JobTriggerSpanParentId)] = Activity.Current?.Context.SpanId.ToHexString() ?? string.Empty; var trigger = TriggerBuilder.Create() - .WithIdentity(key.Name + "_Trigger", key.Group) + // TODO: Is there a simpler way of telling Quartz "Hey, enqueue this job after X delay"? Picking a unique trigger name each time seems unnecessarily complicated. + .WithIdentity(key.Name + "_Trigger_" + now.Ticks.ToString(), key.Group) .StartAt(now.Add(delay)) .ForJob(key.Name, key.Group) .UsingJobData(data) diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index deb983c89..4a0a82f2b 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -283,17 +283,22 @@ public async Task ResetLexEntryCount(string projectCode) return dirInfo; } - public async Task PrepareLdmlZip(Stream outStream, CancellationToken token = default) + public async Task PrepareLdmlZip(CancellationToken token = default) { var path = System.IO.Path.Join(System.IO.Path.GetTempPath(), "ldml-zip"); // TODO: pick random name, rather than predictable one if (Directory.Exists(path)) Directory.Delete(path, true); Directory.CreateDirectory(path); await DeleteTempDirectoryJob.Queue(schedulerFactory, path, TimeSpan.FromHours(4)); + var zipRoot = System.IO.Path.Join(path, "zipRoot"); + Directory.CreateDirectory(zipRoot); await foreach (var project in dbContext.Projects.Where(p => p.Type == ProjectType.FLEx).AsAsyncEnumerable()) { - await ExtractLdmlZip(project, path, token); + await ExtractLdmlZip(project, zipRoot, token); } - ZipFile.CreateFromDirectory(path, outStream, CompressionLevel.Fastest, includeBaseDirectory: false); + var zipFilePath = System.IO.Path.Join(path, "ldml.zip"); // TODO: Put timestamp in there + if (File.Exists(zipFilePath)) File.Delete(zipFilePath); + ZipFile.CreateFromDirectory(zipRoot, zipFilePath, CompressionLevel.Fastest, includeBaseDirectory: false); + return zipFilePath; } public async Task UpdateLastCommit(string projectCode) diff --git a/hgweb/command-runner.sh b/hgweb/command-runner.sh index 4d6e6a6ee..9eeb805b3 100644 --- a/hgweb/command-runner.sh +++ b/hgweb/command-runner.sh @@ -40,7 +40,8 @@ fi if [[ $command_name == "ldmlzip" ]]; then # Preflight check: ldml zip access is only allowed if LexiconSettings.plsx contains addToSldr="true" - if (chg -R /var/hg/repos/$first_char/$project_code cat -r tip CachedSettings/SharedSettings/LexiconSettings.plsx | grep '/dev/null); then CONTENT_TYPE="application/zip" else echo "Content-type: text/plain"