Files
launcher-next/Launcher.Next.App/MainWindow.xaml.cs
T

222 lines
13 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Launcher.Next.Application;
using Launcher.Next.Autodesk;
namespace Launcher.Next.App;
public partial class MainWindow : System.Windows.Window
{
private sealed record LauncherProduct(string Id, string Name, string? Executable, string Diagnostic);
private readonly LocalParityLaunchProfile? localProfile;
private readonly LocalParityLaunchService localService = new();
private readonly LegacyConfigurationAdapter adapter = new();
private LauncherProduct? selected;
private string? projectPath;
private string? manualRosterPath;
private string? selectedRosterSnapshotPath;
private string stage = "ProductSelection";
private string code = "PRODUCT_NOT_SELECTED";
private string detail = "";
private string pattern = "";
private string expectedRoster = "";
private string orderedIds = "";
private string mappingState = "NotEvaluated";
private string preparationState = "NotStarted";
private string activationState = "NotStarted";
private string bootstrapState = "NotStarted";
private string processState = "NotStarted";
private int? processId;
private string packageLoadMarker = "NotStarted";
public MainWindow() { InitializeComponent(); Initialize(); }
internal MainWindow(LocalParityLaunchProfile profile) { localProfile = profile; InitializeComponent(); Initialize(); }
private void Initialize()
{
var plant = new PlantProductDiscovery(new WindowsRegistryReader()).DiscoverPlant3D(2026);
var navisworks = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Autodesk", "Navisworks Manage 2026", "Roamer.exe");
Products.ItemsSource = new[]
{
new LauncherProduct("navisworks", "Navisworks Manage 2026", File.Exists(navisworks) ? navisworks : null, File.Exists(navisworks) ? "Найден" : "Navisworks Manage 2026 не найден"),
new LauncherProduct("pnid", "Plant P&ID 2026", plant?.ExecutablePath, plant is null ? "Plant P&ID 2026 не найден" : "Используется установленный Plant runtime"),
new LauncherProduct("plant3d", "Plant 3D 2026", plant?.ExecutablePath, plant is null ? "Plant 3D 2026 не найден" : "Найден")
};
Version.Text = "Версия: " + typeof(MainWindow).Assembly.GetName().Version;
Transition("ProductSelection", "PRODUCT_NOT_SELECTED", "Выберите программное обеспечение");
if (localProfile is not null) Products.SelectedIndex = 2;
}
private void Products_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
selected = Products.SelectedItem as LauncherProduct;
projectPath = null;
manualRosterPath = null;
ClearOperationState();
ProjectPath.Text = selected is null ? "Выберите программное обеспечение" : "Проект не выбран";
SelectProjectButton.IsEnabled = selected?.Executable is not null;
ProductStatus.Text = selected?.Diagnostic ?? "";
Transition(selected?.Executable is null ? "ProductSelectionFailed" : "ProjectSelection", selected?.Executable is null ? "PRODUCT_NOT_FOUND" : "PROJECT_NOT_SELECTED", selected?.Executable is null ? selected?.Diagnostic ?? "Продукт не найден" : "Выберите проект");
}
private void SelectProject_Click(object sender, System.Windows.RoutedEventArgs e) => SelectProjectFile_Click(sender, e);
private void SelectProjectFile_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (selected is null) return;
var product = LegacyProductSelections.For(selected.Id);
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "Выберите проект", Filter = product.Filter, CheckFileExists = true, Multiselect = false };
if (dialog.ShowDialog() != true) return;
ClearOperationState();
try
{
Transition("ProjectSelected", "PROJECT_SELECTED", "Проверка проекта");
projectPath = LegacyProductSelections.ValidateSelectedFile(product, dialog.FileName);
manualRosterPath = null;
ProjectPath.Text = Path.GetFileName(projectPath);
Transition("ProjectValidated", "PROJECT_VALID", "Поиск списка плагинов");
ResolvePlugins();
}
catch (Exception ex) { ReportFailure(ex, "ProjectValidationFailed"); }
}
private void LoadMode_Changed(object sender, System.Windows.RoutedEventArgs e)
{
if (projectPath is null || selected is null) return;
if (ListMode.IsChecked == true)
{
var dialog = new Microsoft.Win32.OpenFileDialog { Title = "Выберите download_list.xml", Filter = "download_list.xml (*.xml)|*.xml|All files (*.*)|*.*", CheckFileExists = true, Multiselect = false };
if (dialog.ShowDialog() != true) { AutomaticMode.IsChecked = true; return; }
manualRosterPath = dialog.FileName;
}
else manualRosterPath = null;
ClearOperationState();
ResolvePlugins();
}
private void ResolvePlugins()
{
if (selected is null || projectPath is null) return;
try
{
var product = LegacyProductSelections.For(selected.Id);
pattern = localProfile is not null && selected.Id == "plant3d" ? localProfile.PatternName : product.Pattern;
expectedRoster = manualRosterPath ?? LegacyProductSelections.AutomaticRoster(product, projectPath);
Transition("AutomaticRosterPathResolved", "AUTOMATIC_ROSTER_PATH_RESOLVED", "Поиск списка плагинов", expectedRoster);
var plan = LegacyProductSelections.ReadRoster(product, projectPath, manualRosterPath, adapter, localProfile?.Root);
orderedIds = string.Join(", ", plan.OrderedIds);
selectedRosterSnapshotPath = plan.SnapshotPath ?? plan.RosterPath;
if (plan.SnapshotSha256 is not null) detail = $"Roster snapshot SHA-256: {plan.SnapshotSha256}";
Plugins.ItemsSource = plan.OrderedIds;
Plugins.IsEnabled = false;
PluginStatus.Text = "Источник списка: " + (manualRosterPath is null ? "Автоматически" : "Выбран вручную");
Transition("OrderedPluginIdsResolved", "PLUGIN_PLAN_RESOLVED", "Плагины определены");
mappingState = plan.UnmappedIds.Count == 0 ? "Mapped" : "Unmapped: " + string.Join(", ", plan.UnmappedIds);
if (plan.UnmappedIds.Count > 0)
{
StartButton.IsEnabled = false;
Transition("PackageMappingFailed", "PACKAGE_MAPPING_NOT_FOUND", $"Для {product.DisplayName} отсутствуют пакеты 2026: {string.Join(", ", plan.UnmappedIds)}");
return;
}
if (localProfile is null || selected.Id != "plant3d")
{
StartButton.IsEnabled = false;
Transition("PackageMappingFailed", "PACKAGE_MAPPING_NOT_FOUND", "Для выбранных плагинов отсутствуют пакеты 2026");
return;
}
var unmapped = EvaluateLocalProfileDefinitions(plan);
if (unmapped.Count > 0)
{
mappingState = "Known legacy plugin, package 2026 absent: " + string.Join(", ", unmapped);
StartButton.IsEnabled = false;
Transition("PackageMappingFailed", "PACKAGE_MAPPING_NOT_FOUND", "Для части плагинов проекта пока отсутствуют пакеты версии 2026");
return;
}
mappingState = "Mapped";
StartButton.IsEnabled = true;
Transition("Ready", "READY", "Готово к запуску");
}
catch (Exception ex) { ReportFailure(ex, "RosterResolutionFailed"); }
}
private IReadOnlyList<string> EvaluateLocalProfileDefinitions(LegacyPluginPlan plan)
{
var settings = adapter.ParseSettings(File.ReadAllText(localProfile!.SettingsPath));
var selectedPattern = settings.Patterns.SingleOrDefault(x => string.Equals(x.Name, localProfile.PatternName, StringComparison.Ordinal));
if (selectedPattern is null) throw new InvalidOperationException("LOCAL_PARITY_PATTERN_NOT_FOUND");
var overlay = LegacyCompatibilityOverlayParser.Parse(File.ReadAllText(localProfile.OverlayPath));
var missing = new List<string>();
foreach (var id in plan.OrderedIds.Distinct(StringComparer.OrdinalIgnoreCase))
{
var definitions = selectedPattern.Plugins.Where(x => string.Equals(x.Id, id, StringComparison.OrdinalIgnoreCase)).ToArray();
if (definitions.Length == 0) throw new InvalidOperationException("LEGACY_PLUGIN_ID_NOT_DEFINED");
if (definitions.Length > 1) throw new InvalidOperationException("AMBIGUOUS_LEGACY_PLUGIN_DEFINITION");
if (!definitions[0].IsAllowedOn(Environment.MachineName)) { missing.Add(id + " (machine restricted)"); continue; }
if (!overlay.Plugins.Any(x => string.Equals(x.LegacyId, id, StringComparison.OrdinalIgnoreCase))) missing.Add(id);
}
return missing;
}
private async void Start_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (localProfile is null || selected?.Id != "plant3d" || projectPath is null || !StartButton.IsEnabled) return;
StartButton.IsEnabled = false;
try
{
preparationState = "InProgress";
Transition("PackagePreparation", "PACKAGE_PREPARING", "Подготовка плагинов");
var profile = localProfile with { DownloadListPath = selectedRosterSnapshotPath ?? localProfile.DownloadListPath };
var prepared = await localService.PrepareAsync(profile, CancellationToken.None);
preparationState = "Succeeded";
activationState = string.Join(", ", prepared.Packages.Select(x => x.Activation.Outcome));
bootstrapState = "Created";
Transition("ProcessStart", "PROCESS_STARTING", "Запуск Plant 3D");
var result = await localService.LaunchAsync(prepared, CancellationToken.None);
processId = result.ProcessId;
processState = result.StartupVerified ? "Started" : "ExitedEarly";
packageLoadMarker = result.PackageLoadConfirmed ? "AssemblyInitializeCompleted" : "Missing";
if (result.TerminalState == "Succeeded") Transition("Ready", "PACKAGE_LOAD_CONFIRMED", $"Plant 3D готов к работе (PID {result.ProcessId})");
else Transition("PackageLoadFailed", "PACKAGE_LOAD_NOT_CONFIRMED", "Plant 3D запущен, но автоматическая загрузка плагинов не подтверждена");
}
catch (Exception ex) { ReportFailure(ex, "PackagePreparationFailed"); }
finally { StartButton.IsEnabled = stage == "Ready"; }
}
private void ClearOperationState()
{
Plugins.ItemsSource = null;
Plugins.IsEnabled = false;
StartButton.IsEnabled = false;
pattern = expectedRoster = orderedIds = detail = "";
selectedRosterSnapshotPath = null;
mappingState = preparationState = activationState = bootstrapState = processState = "NotStarted";
processId = null;
packageLoadMarker = "NotStarted";
}
private void Transition(string nextStage, string nextCode, string message, string? technicalDetail = null)
{
stage = nextStage;
code = nextCode;
detail = technicalDetail ?? "";
Status.Text = message;
}
private void ReportFailure(Exception exception, string fallbackStage)
{
var failure = LauncherOperationFailures.Classify(exception.Message, fallbackStage, fallbackStage == "ProcessStartFailed");
Transition(failure.Stage, failure.Code, failure.UserMessage, exception.ToString());
}
private void Help_Click(object sender, System.Windows.RoutedEventArgs e) => System.Windows.MessageBox.Show("Выберите ПО и project file. В автоматическом режиме Launcher использует список плагинов, определённый проектом. Режим \"По списку\" позволяет явно выбрать download_list.xml.", "Справка");
private void Diagnostics_Click(object sender, System.Windows.RoutedEventArgs e)
{
var report = $"Launcher version: {typeof(MainWindow).Assembly.GetName().Version}\nSelected product: {selected?.Name}\nDiscovery: {selected?.Diagnostic}\nSelected project: {projectPath}\nProject validation: {(projectPath is null ? "NotStarted" : "Passed")}\nSelected mode: {(AutomaticMode.IsChecked == true ? "Automatic" : "Manual")}\nPattern: {pattern}\nExpected download list: {expectedRoster}\nOrdered legacy IDs: {orderedIds}\nDefinition join: {mappingState}\nPreparation: {preparationState}\nActivation: {activationState}\nBootstrap: {bootstrapState}\nProcess start: {processState}\nPID: {processId}\nPackage-load marker: {packageLoadMarker}\nLast error stage: {stage}\nLast error code: {code}\nTechnical detail: {detail}";
System.Windows.MessageBox.Show(report, "Диагностика Launcher Next");
}
}