chore: save current workspace progress

This commit is contained in:
梁薄云
2026-08-09 22:13:18 +08:00
parent 650c2ab0e3
commit 2f4fd15e52
449 changed files with 76593 additions and 971 deletions
@@ -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; }
}
}