chore: save current workspace progress
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace MultiWheelC.TrajectoryPlanning.PathSmoothing.Visualization;
|
||||
|
||||
/// <summary>以同级临时文件生成六张 SVG、六张 PNG 和 CSV 后成组发布。</summary>
|
||||
public sealed class SmoothingReportExporter
|
||||
{
|
||||
private readonly SmoothingFontResolver _fontResolver = new SmoothingFontResolver();
|
||||
private readonly SmoothingFigureSetBuilder _figureSetBuilder = new SmoothingFigureSetBuilder();
|
||||
private readonly SmoothingSvgRenderer _svgRenderer = new SmoothingSvgRenderer();
|
||||
private readonly SmoothingPngRenderer _pngRenderer = new SmoothingPngRenderer();
|
||||
private readonly SmoothingCsvWriter _csvWriter = new SmoothingCsvWriter();
|
||||
|
||||
public SmoothingReportExportResult Export(SmoothingReportExportRequest request)
|
||||
{
|
||||
if (request == null || request.Model == null || string.IsNullOrWhiteSpace(request.OutputDirectory) || !IsFileStem(request.FileStem))
|
||||
return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.InvalidInput, "报告模型、输出目录或文件名无效。");
|
||||
if (!_fontResolver.TryResolve(request.ChineseFontFamilyName, request.LatinFontFamilyName, out SmoothingFontResolution availableFonts, out string fontReason))
|
||||
return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.FontUnavailable, fontReason);
|
||||
availableFonts.Dispose();
|
||||
|
||||
var pending = new List<PendingFile>();
|
||||
var svgPaths = new List<string>();
|
||||
var pngPaths = new List<string>();
|
||||
string csvPath = Path.Combine(request.OutputDirectory, request.FileStem + ".csv");
|
||||
try
|
||||
{
|
||||
SmoothingFigureSet figures = _figureSetBuilder.Build(request.Model);
|
||||
for (int index = 0; index < figures.Figures.Count; index++)
|
||||
{
|
||||
SmoothingFigureDefinition figure = figures.Figures[index];
|
||||
string svgPath = Path.Combine(request.OutputDirectory, figure.FileStem + ".svg");
|
||||
string pngPath = Path.Combine(request.OutputDirectory, figure.FileStem + ".png");
|
||||
svgPaths.Add(svgPath);
|
||||
pngPaths.Add(pngPath);
|
||||
pending.Add(new PendingFile(svgPath, Encoding.UTF8.GetBytes(_svgRenderer.Render(figure))));
|
||||
if (!_fontResolver.TryResolve(request.ChineseFontFamilyName, request.LatinFontFamilyName, out SmoothingFontResolution renderFonts, out fontReason))
|
||||
throw new InvalidOperationException(fontReason);
|
||||
byte[] png;
|
||||
using (renderFonts) png = _pngRenderer.Render(figure, renderFonts);
|
||||
pending.Add(new PendingFile(pngPath, png));
|
||||
}
|
||||
pending.Add(new PendingFile(csvPath, _csvWriter.Write(request.Model)));
|
||||
Directory.CreateDirectory(request.OutputDirectory);
|
||||
for (int index = 0; index < pending.Count; index++) File.WriteAllBytes(pending[index].TemporaryPath, pending[index].Content);
|
||||
PublishAll(pending);
|
||||
DeleteIfExists(Path.Combine(request.OutputDirectory, "comparison.svg"));
|
||||
DeleteIfExists(Path.Combine(request.OutputDirectory, "comparison.png"));
|
||||
return SmoothingReportExportResult.Success(svgPaths, pngPaths, csvPath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
RestorePublishedFiles(pending);
|
||||
return SmoothingReportExportResult.Failure(SmoothingReportExportStatus.Failed, exception.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (int index = 0; index < pending.Count; index++)
|
||||
{
|
||||
DeleteIfExists(pending[index].TemporaryPath);
|
||||
DeleteIfExists(pending[index].BackupPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PublishAll(IReadOnlyList<PendingFile> files)
|
||||
{
|
||||
string transaction = Guid.NewGuid().ToString("N");
|
||||
for (int index = 0; index < files.Count; index++)
|
||||
{
|
||||
PendingFile file = files[index];
|
||||
file.ExistedBeforePublish = File.Exists(file.FinalPath);
|
||||
file.BackupPath = file.ExistedBeforePublish ? file.FinalPath + ".backup-" + transaction : string.Empty;
|
||||
if (file.ExistedBeforePublish) File.Replace(file.TemporaryPath, file.FinalPath, file.BackupPath);
|
||||
else File.Move(file.TemporaryPath, file.FinalPath);
|
||||
file.Published = true;
|
||||
}
|
||||
for (int index = 0; index < files.Count; index++) DeleteIfExists(files[index].BackupPath);
|
||||
}
|
||||
|
||||
private static void RestorePublishedFiles(IReadOnlyList<PendingFile> files)
|
||||
{
|
||||
for (int index = files.Count - 1; index >= 0; index--)
|
||||
{
|
||||
PendingFile file = files[index];
|
||||
if (!file.Published) continue;
|
||||
try
|
||||
{
|
||||
if (file.ExistedBeforePublish && File.Exists(file.BackupPath))
|
||||
{
|
||||
if (File.Exists(file.FinalPath)) File.Replace(file.BackupPath, file.FinalPath, null);
|
||||
else File.Move(file.BackupPath, file.FinalPath);
|
||||
}
|
||||
else if (!file.ExistedBeforePublish) DeleteIfExists(file.FinalPath);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFileStem(string value)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value) && value.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 && value.IndexOf(Path.DirectorySeparatorChar) < 0 && value.IndexOf(Path.AltDirectorySeparatorChar) < 0;
|
||||
}
|
||||
|
||||
private static void DeleteIfExists(string path)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
|
||||
private sealed class PendingFile
|
||||
{
|
||||
public PendingFile(string finalPath, byte[] content)
|
||||
{
|
||||
FinalPath = finalPath;
|
||||
Content = content;
|
||||
TemporaryPath = finalPath + ".tmp";
|
||||
BackupPath = string.Empty;
|
||||
}
|
||||
|
||||
public string FinalPath { get; }
|
||||
public byte[] Content { get; }
|
||||
public string TemporaryPath { get; }
|
||||
public string BackupPath { get; set; }
|
||||
public bool ExistedBeforePublish { get; set; }
|
||||
public bool Published { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user