Files
Qinventor/scripts/launcher/Program.cs
T

118 lines
6.8 KiB
C#

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Nodes;
static class Program
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
static int Main(string[] args)
{
// Preserve a drive root (for example F:\\ on a portable USB drive).
var root = Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory);
var rootPrefix = Path.EndsInDirectorySeparator(root) ? root : root + Path.DirectorySeparatorChar;
var log = Path.Combine(root, "logs", "launcher.log");
try
{
Directory.CreateDirectory(Path.GetDirectoryName(log)!);
Directory.CreateDirectory(Path.Combine(root, "data"));
var marker = Path.Combine(root, "data", "runtime-path.txt");
var engine = File.Exists(marker)
? Path.GetFullPath(Path.Combine(root, File.ReadAllText(marker).Trim()))
: Directory.EnumerateFiles(Path.Combine(root, "runtime"), "FreeCAD.exe", SearchOption.AllDirectories)
.First(path => string.Equals(Path.GetFileName(Path.GetDirectoryName(path)), "bin", StringComparison.OrdinalIgnoreCase));
if (!engine.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase) || !File.Exists(engine))
throw new IOException("Не найдены библиотеки CAD. Выполните scripts\\bootstrap.ps1.");
var nativeEngine = RuntimePath.ForEngine(root, engine);
var start = new ProcessStartInfo(nativeEngine) { UseShellExecute = false, WorkingDirectory = root };
start.Environment["QI_ROOT"] = root;
start.Environment.Remove("QI_TEST");
var selfTest = args.Contains("--self-test", StringComparer.Ordinal);
var profile = Path.Combine(root, "data");
if (selfTest)
{
start.Environment["QI_TEST"] = "1";
profile = Path.Combine(root, "artifacts", "test-profiles", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(profile);
var report = Path.Combine(root, "artifacts", "validation.json");
if (File.Exists(report))
File.Move(report, Path.Combine(root, "artifacts", $"validation-previous-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json"));
}
start.ArgumentList.Add("--user-cfg");
start.ArgumentList.Add(Path.Combine(profile, "user.cfg"));
start.ArgumentList.Add("--system-cfg");
start.ArgumentList.Add(Path.Combine(profile, "system.cfg"));
start.ArgumentList.Add("--write-log");
start.ArgumentList.Add("--log-file");
start.ArgumentList.Add(Path.Combine(root, "logs", "cad.log"));
// Apply first-run native view preferences before restored sketches
// exist. Setting them afterwards would mark clean projects dirty.
start.ArgumentList.Add(Path.Combine(root, "scripts", "startup.FCMacro"));
foreach (var arg in args)
{
if (arg != "--self-test") start.ArgumentList.Add(arg);
}
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Starting QInventor {engine}\nNative runtime path: {nativeEngine}\n");
using var process = Process.Start(start) ?? throw new IOException("Не удалось запустить CAD-процесс.");
process.WaitForExit();
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Exit {process.ExitCode}\n");
if (process.ExitCode != 0) throw new IOException($"CAD-процесс завершился с кодом {process.ExitCode}. Подробности: logs\\cad.log.");
if (selfTest)
{
using var report = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "artifacts", "validation.json")));
if (report.RootElement.GetProperty("status").GetString() != "passed")
{
File.AppendAllText(log, $"{DateTimeOffset.Now:O} Проверка приложения завершилась с ошибками; artifacts\\validation.json\n");
return 2;
}
}
return 0;
}
catch (Exception ex)
{
try { File.AppendAllText(log, $"{DateTimeOffset.Now:O} {ex}\n"); } catch { }
if (args.Contains("--self-test", StringComparer.Ordinal))
{
// Native failures can interrupt a suite before Python writes its
// final status. Preserve completed checks and make that failure
// explicit instead of leaving a misleading running report.
try
{
var reportPath = Path.Combine(root, "artifacts", "validation.json");
Directory.CreateDirectory(Path.GetDirectoryName(reportPath)!);
var report = new JsonObject();
if (File.Exists(reportPath))
{
try
{
report = JsonNode.Parse(File.ReadAllText(reportPath)) as JsonObject ?? new JsonObject();
}
catch (JsonException)
{
// A native crash can interrupt Python's live JSON
// write. Preserve its remaining bytes, then still
// produce a readable failure report.
var damaged = Path.Combine(root, "artifacts", $"validation-truncated-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json");
File.Copy(reportPath, damaged);
}
}
report["status"] = "failed";
var previousFailed = report["failed"] is JsonValue count && count.TryGetValue<int>(out var failures) ? failures : 0;
report["failed"] = Math.Max(1, previousFailed);
report["launcher_error"] = ex.Message;
report["finished"] = DateTime.UtcNow.ToString("o");
var staged = reportPath + ".launcher-tmp";
File.WriteAllText(staged, report.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
File.Move(staged, reportPath, overwrite: true);
}
catch { }
return 1;
}
MessageBoxW(IntPtr.Zero, "Не удалось запустить QInventor.\n\nПроверьте, что приложение перенесено вместе с папками runtime, src и scripts. Подробности ошибки сохранены в журнале.\n\nЖурнал: " + log, "QInventor", 0x10);
return 1;
}
}
}