feat: restore product-specific legacy project selection
This commit is contained in:
@@ -15,8 +15,18 @@ public partial class MainWindow : System.Windows.Window
|
|||||||
private readonly LegacyConfigurationAdapter adapter=new();
|
private readonly LegacyConfigurationAdapter adapter=new();
|
||||||
private LauncherProduct? selected;
|
private LauncherProduct? selected;
|
||||||
private string? projectPath;
|
private string? projectPath;
|
||||||
public MainWindow() { InitializeComponent(); Initialize(); }
|
private string? manualRosterPath;
|
||||||
internal MainWindow(LocalParityLaunchProfile profile) { localProfile=profile; InitializeComponent(); Initialize(); }
|
public MainWindow() { InitializeComponent(); WireSourceVerifiedSelection(); Initialize(); }
|
||||||
|
internal MainWindow(LocalParityLaunchProfile profile) { localProfile=profile; InitializeComponent(); WireSourceVerifiedSelection(); Initialize(); }
|
||||||
|
private void WireSourceVerifiedSelection()
|
||||||
|
{
|
||||||
|
SelectProjectButton.Click -= SelectProject_Click;
|
||||||
|
SelectProjectButton.Click += SelectProjectFile_Click;
|
||||||
|
AutomaticMode.Checked -= LoadMode_Changed;
|
||||||
|
ListMode.Checked -= LoadMode_Changed;
|
||||||
|
AutomaticMode.Checked += LoadModeChanged;
|
||||||
|
ListMode.Checked += LoadModeChanged;
|
||||||
|
}
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
var plant=new PlantProductDiscovery(new WindowsRegistryReader()).DiscoverPlant3D(2026);
|
var plant=new PlantProductDiscovery(new WindowsRegistryReader()).DiscoverPlant3D(2026);
|
||||||
@@ -34,7 +44,62 @@ public partial class MainWindow : System.Windows.Window
|
|||||||
var path=Path.GetFullPath(dialog.FolderName); if(!Directory.Exists(path)){Status.Text="Выбранная папка недоступна.";return;} if(selected.Id is "plant3d" or "pnid" && !File.Exists(Path.Combine(path,"Project.xml"))){Status.Text="Выбранная папка не является проектом Plant 3D.";return;}
|
var path=Path.GetFullPath(dialog.FolderName); if(!Directory.Exists(path)){Status.Text="Выбранная папка недоступна.";return;} if(selected.Id is "plant3d" or "pnid" && !File.Exists(Path.Combine(path,"Project.xml"))){Status.Text="Выбранная папка не является проектом Plant 3D.";return;}
|
||||||
projectPath=path; ProjectPath.Text=path; ResolvePlugins();
|
projectPath=path; ProjectPath.Text=path; ResolvePlugins();
|
||||||
}
|
}
|
||||||
|
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 = "Select project", Filter = product.Filter, CheckFileExists = true, Multiselect = false };
|
||||||
|
if (dialog.ShowDialog() != true) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
projectPath = LegacyProductSelections.ValidateSelectedFile(product, dialog.FileName);
|
||||||
|
manualRosterPath = null;
|
||||||
|
ProjectPath.Text = Path.GetFileName(projectPath);
|
||||||
|
ResolvePluginsSourceVerified();
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Status.Text = UserMessage(ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadModeChanged(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 = "Select 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;
|
||||||
|
ResolvePluginsSourceVerified();
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadMode_Changed(object sender,System.Windows.RoutedEventArgs e) { if(projectPath is not null)ResolvePlugins(); }
|
private void LoadMode_Changed(object sender,System.Windows.RoutedEventArgs e) { if(projectPath is not null)ResolvePlugins(); }
|
||||||
|
private void ResolvePluginsSourceVerified()
|
||||||
|
{
|
||||||
|
if (selected is null || projectPath is null) return;
|
||||||
|
if (localProfile is not null && selected.Id == "plant3d") { ResolvePlugins(); return; }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var product = LegacyProductSelections.For(selected.Id);
|
||||||
|
var plan = LegacyProductSelections.ReadRoster(product, projectPath, manualRosterPath, adapter);
|
||||||
|
Plugins.ItemsSource = plan.OrderedIds;
|
||||||
|
Plugins.IsEnabled = false;
|
||||||
|
PluginStatus.Text = $"List source: {plan.Source}.";
|
||||||
|
StartButton.IsEnabled = false;
|
||||||
|
Status.Text = plan.UnmappedIds.Count == 0
|
||||||
|
? "Packages for the selected plugins are not mapped to 2026 packages."
|
||||||
|
: $"Packages for {product.DisplayName} are not yet available for 2026: {string.Join(", ", plan.UnmappedIds)}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Plugins.ItemsSource = null;
|
||||||
|
Plugins.IsEnabled = false;
|
||||||
|
StartButton.IsEnabled = false;
|
||||||
|
Status.Text = ex.Message == "LEGACY_DOWNLOAD_LIST_NOT_FOUND"
|
||||||
|
? "Для выбранного проекта не найден список плагинов"
|
||||||
|
: UserMessage(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
private void ResolvePlugins()
|
private void ResolvePlugins()
|
||||||
{
|
{
|
||||||
if(selected is null||projectPath is null)return;
|
if(selected is null||projectPath is null)return;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
namespace Launcher.Next.Application;
|
||||||
|
|
||||||
|
public sealed record LegacyProductSelection(string Id, string DisplayName, string Filter, string RequiredFileName, string Pattern, string[] SourceVerifiedLegacyIds, string[] UnmappedLegacyIds, bool SupportsPlantBootstrap);
|
||||||
|
public sealed record LegacyPluginPlan(string RosterPath, string Source, IReadOnlyList<string> OrderedIds, IReadOnlyList<string> UnmappedIds);
|
||||||
|
|
||||||
|
public static class LegacyProductSelections
|
||||||
|
{
|
||||||
|
public static readonly LegacyProductSelection Plant3D = new("plant3d", "Plant 3D 2026", "Project files |Project.xml|All files (*.*)|*.*", "Project.xml", "Plant 3D_2019", [], [], true);
|
||||||
|
public static readonly LegacyProductSelection Pnid = new("pnid", "Plant P&ID 2026", "Project files |Project.xml|All files (*.*)|*.*", "Project.xml", "Plant PnID_2019", ["c1b999d6c16029e9ac5b1e1a8afe1500", "5b80379c3ec715e7da0ee414c53aa7bc", "1111"], ["c1b999d6c16029e9ac5b1e1a8afe1500", "5b80379c3ec715e7da0ee414c53aa7bc", "1111"], true);
|
||||||
|
public static readonly LegacyProductSelection Navisworks = new("navisworks", "Navisworks Manage 2026", "nwd files (*.nwd)|*.nwd|All files (*.*)|*.*", ".nwd", "Navisworks_Manage_2018", ["7dd981169fa14d21aaef3fcdb0e7e970"], ["7dd981169fa14d21aaef3fcdb0e7e970"], false);
|
||||||
|
|
||||||
|
public static LegacyProductSelection For(string id) => id switch { "plant3d" => Plant3D, "pnid" => Pnid, "navisworks" => Navisworks, _ => throw new InvalidOperationException("LEGACY_PRODUCT_UNKNOWN") };
|
||||||
|
|
||||||
|
public static string ValidateSelectedFile(LegacyProductSelection product, string selected)
|
||||||
|
{
|
||||||
|
var file = Path.GetFullPath(selected);
|
||||||
|
if (!File.Exists(file) || File.GetAttributes(file).HasFlag(FileAttributes.ReparsePoint)) throw new InvalidOperationException("LEGACY_PROJECT_FILE_INVALID");
|
||||||
|
var correctName = product.RequiredFileName.StartsWith('.') ? file.EndsWith(product.RequiredFileName, StringComparison.OrdinalIgnoreCase) : string.Equals(Path.GetFileName(file), product.RequiredFileName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (!correctName) throw new InvalidOperationException("LEGACY_PROJECT_FILE_PRODUCT_MISMATCH");
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AutomaticRoster(LegacyProductSelection product, string selectedFile)
|
||||||
|
{
|
||||||
|
var projectDirectory = Path.GetDirectoryName(ValidateSelectedFile(product, selectedFile))!;
|
||||||
|
return Path.GetFullPath(Path.Combine(projectDirectory, "..", "_System", product.Pattern, "Applications", "download_list.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LegacyPluginPlan ReadRoster(LegacyProductSelection product, string selectedFile, string? manualXml, LegacyConfigurationAdapter parser)
|
||||||
|
{
|
||||||
|
var roster = manualXml is null ? AutomaticRoster(product, selectedFile) : Path.GetFullPath(manualXml);
|
||||||
|
if (!File.Exists(roster) || File.GetAttributes(roster).HasFlag(FileAttributes.ReparsePoint) || !string.Equals(Path.GetFileName(roster), "download_list.xml", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LEGACY_DOWNLOAD_LIST_NOT_FOUND");
|
||||||
|
var ids = parser.ParseDownloadList(File.ReadAllText(roster)).PluginIds;
|
||||||
|
if (product.SourceVerifiedLegacyIds.Length > 0 && ids.Any(id => !product.SourceVerifiedLegacyIds.Contains(id, StringComparer.OrdinalIgnoreCase))) throw new InvalidOperationException("LEGACY_PLUGIN_PRODUCT_MISMATCH");
|
||||||
|
return new(roster, manualXml is null ? "Automatic" : "Manual", ids, product.UnmappedLegacyIds.Intersect(ids, StringComparer.OrdinalIgnoreCase).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using Launcher.Next.Application;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Launcher.Next.Tests;
|
||||||
|
|
||||||
|
public sealed class LegacyProductSelectionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SourceVerifiedProducts_ExposeExactOpenFileFilters()
|
||||||
|
{
|
||||||
|
Assert.Equal("Project files |Project.xml|All files (*.*)|*.*", LegacyProductSelections.Pnid.Filter);
|
||||||
|
Assert.Equal("nwd files (*.nwd)|*.nwd|All files (*.*)|*.*", LegacyProductSelections.Navisworks.Filter);
|
||||||
|
Assert.Equal("Project.xml", LegacyProductSelections.Pnid.RequiredFileName);
|
||||||
|
Assert.Equal(".nwd", LegacyProductSelections.Navisworks.RequiredFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AutomaticRoster_UsesOnlyExactLegacyRelativeLocation()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var project = fixture.ProjectFile("Project.xml");
|
||||||
|
var actual = LegacyProductSelections.AutomaticRoster(LegacyProductSelections.Pnid, project);
|
||||||
|
Assert.Equal(Path.Combine(fixture.Root, "_System", "Plant PnID_2019", "Applications", "download_list.xml"), actual);
|
||||||
|
Assert.DoesNotContain("scan", actual, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SelectedProjectFile_IsProductSpecificAndRejectsWrongExtension()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var nwd = fixture.ProjectFile("model.nwd");
|
||||||
|
Assert.Equal(nwd, LegacyProductSelections.ValidateSelectedFile(LegacyProductSelections.Navisworks, nwd));
|
||||||
|
Assert.Throws<InvalidOperationException>(() => LegacyProductSelections.ValidateSelectedFile(LegacyProductSelections.Pnid, nwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AutomaticPnidRoster_PreservesOrderAndReportsExactUnmappedIds()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var project = fixture.ProjectFile("Project.xml");
|
||||||
|
fixture.WriteAutomatic("Plant PnID_2019", "<root><id>c1b999d6c16029e9ac5b1e1a8afe1500</id><id>5b80379c3ec715e7da0ee414c53aa7bc</id><id>1111</id></root>");
|
||||||
|
var plan = LegacyProductSelections.ReadRoster(LegacyProductSelections.Pnid, project, null, new LegacyConfigurationAdapter());
|
||||||
|
Assert.Equal("Automatic", plan.Source);
|
||||||
|
Assert.Equal(["c1b999d6c16029e9ac5b1e1a8afe1500", "5b80379c3ec715e7da0ee414c53aa7bc", "1111"], plan.OrderedIds);
|
||||||
|
Assert.Equal(plan.OrderedIds, plan.UnmappedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ManualRoster_DoesNotChangeProjectAndUsesExplicitDownloadList()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var project = fixture.ProjectFile("model.nwd");
|
||||||
|
var manual = Path.Combine(fixture.Root, "download_list.xml");
|
||||||
|
File.WriteAllText(manual, "<root><id>7dd981169fa14d21aaef3fcdb0e7e970</id></root>");
|
||||||
|
var plan = LegacyProductSelections.ReadRoster(LegacyProductSelections.Navisworks, project, manual, new LegacyConfigurationAdapter());
|
||||||
|
Assert.Equal("Manual", plan.Source);
|
||||||
|
Assert.Equal(manual, plan.RosterPath);
|
||||||
|
Assert.Equal(["7dd981169fa14d21aaef3fcdb0e7e970"], plan.UnmappedIds);
|
||||||
|
Assert.Equal(project, LegacyProductSelections.ValidateSelectedFile(LegacyProductSelections.Navisworks, project));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MissingAutomaticRoster_FailsClosedWithoutSearchingOtherDirectories()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var project = fixture.ProjectFile("model.nwd");
|
||||||
|
File.WriteAllText(Path.Combine(fixture.Root, "download_list.xml"), "<root><id>7dd981169fa14d21aaef3fcdb0e7e970</id></root>");
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => LegacyProductSelections.ReadRoster(LegacyProductSelections.Navisworks, project, null, new LegacyConfigurationAdapter()));
|
||||||
|
Assert.Equal("LEGACY_DOWNLOAD_LIST_NOT_FOUND", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProductIncompatibleAndDuplicateIds_AreBlocked()
|
||||||
|
{
|
||||||
|
using var fixture = new SelectionFixture();
|
||||||
|
var project = fixture.ProjectFile("Project.xml");
|
||||||
|
fixture.WriteAutomatic("Plant PnID_2019", "<root><id>unexpected</id></root>");
|
||||||
|
Assert.Equal("LEGACY_PLUGIN_PRODUCT_MISMATCH", Assert.Throws<InvalidOperationException>(() => LegacyProductSelections.ReadRoster(LegacyProductSelections.Pnid, project, null, new LegacyConfigurationAdapter())).Message);
|
||||||
|
fixture.WriteAutomatic("Plant PnID_2019", "<root><id>1111</id><id>1111</id></root>");
|
||||||
|
Assert.Equal("LEGACY_DOWNLOAD_SELECTION_INVALID", Assert.Throws<InvalidOperationException>(() => LegacyProductSelections.ReadRoster(LegacyProductSelections.Pnid, project, null, new LegacyConfigurationAdapter())).Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Navisworks_DoesNotAdvertisePlantBootstrap()
|
||||||
|
{
|
||||||
|
Assert.False(LegacyProductSelections.Navisworks.SupportsPlantBootstrap);
|
||||||
|
Assert.True(LegacyProductSelections.Pnid.SupportsPlantBootstrap);
|
||||||
|
Assert.Equal(["7dd981169fa14d21aaef3fcdb0e7e970"], LegacyProductSelections.Navisworks.UnmappedLegacyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SelectionFixture : IDisposable
|
||||||
|
{
|
||||||
|
public SelectionFixture() { Root = Path.Combine(Path.GetTempPath(), "launcher-next-selection-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(Path.Combine(Root, "Project")); }
|
||||||
|
public string Root { get; }
|
||||||
|
public string ProjectFile(string name) { var path = Path.Combine(Root, "Project", name); File.WriteAllText(path, "fixture"); return path; }
|
||||||
|
public void WriteAutomatic(string pattern, string xml) { var directory = Path.Combine(Root, "_System", pattern, "Applications"); Directory.CreateDirectory(directory); File.WriteAllText(Path.Combine(directory, "download_list.xml"), xml); }
|
||||||
|
public void Dispose() { if (Directory.Exists(Root)) Directory.Delete(Root, true); }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user