From d0247dbb19b8ad3e9462d37dc12a80d2a030f1f6 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 6 Sep 2026 22:26:17 +0200 Subject: [PATCH] fix(vt): C separate exports/meta/ and exports/nav/ .af directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item C (slice-1 fix round). MossTankMetaProfileStore and MossTankRouteProfileStore both wrote their .af exports into the same flat "exports/" directory keyed only by profile name — a Meta profile and a route (Navigation) profile sharing a name (e.g. both named "Same") would silently clobber each other's .af file on save, with no error and no warning. - MossTankMetaProfileStore.WriteLegacyExport now writes to "exports/meta/{name}.af". - MossTankRouteProfileStore.WriteLegacyExport now writes to "exports/nav/{name}.af". - Both class docs updated to name the collision this avoids and point at the sibling store's subdirectory. - MossTankPanelTests: existing NavCommandsImportAndExportExactVtankNavFiles and MetaCommandsImportAndExportExactVtankMetFiles updated to the new paths. New MetaAndRouteExportsWithTheSameNameDoNotCollide saves a Meta and a route profile both named "Same" and asserts both .af files exist with their own correct content — verified failing before the fix (asserted false on the meta file's existence once both paths were reverted to the flat "exports/" root, confirming the collision is real and this test catches it). Full MossTank suite: 569/569 passing (568 -> 569, one new test). Co-Authored-By: Claude Fable 5.1 --- .../MossTankMetaProfileStore.cs | 9 ++++- .../MossTankRouteProfileStore.cs | 9 ++++- .../MossTankPanelTests.cs | 38 +++++++++++++++++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs index 676aa1396..8ba153b50 100644 --- a/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs +++ b/src/AcDream.Plugins.MossTank/MossTankMetaProfileStore.cs @@ -195,7 +195,12 @@ internal sealed class MossTankMetaProfileStore /// .af is the only VTank-compatible export format now (Campaign /// VT slice 1 Part A — MossTank no longer authors the binary /// .met format at all, matching 's - /// demotion to a one-shot import). + /// demotion to a one-shot import). Exports live under + /// exports/meta/ — a distinct subdirectory from + /// 's exports/nav/ — so a + /// Meta profile and a route (Navigation) profile sharing the same name + /// cannot silently overwrite each other's .af file (both stores + /// used to write into the same flat exports/ root). /// private void WriteLegacyExport(string name, MetaProfile profile) { @@ -204,7 +209,7 @@ internal sealed class MossTankMetaProfileStore try { _host.Storage.WriteText( - $"exports/{LegacyFileName(name)}.af", + $"exports/meta/{LegacyFileName(name)}.af", MetafSerializer.SaveMeta(profile)); } catch (Exception error) diff --git a/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs b/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs index d6af35d8a..49c4ae7af 100644 --- a/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs +++ b/src/AcDream.Plugins.MossTank/MossTankRouteProfileStore.cs @@ -264,7 +264,12 @@ internal sealed class MossTankRouteProfileStore /// .af is the only VTank-compatible export format now (Campaign /// VT slice 1 Part A — MossTank no longer authors the binary /// .nav format at all, matching 's - /// demotion to a one-shot import). + /// demotion to a one-shot import). Exports live under + /// exports/nav/ — a distinct subdirectory from + /// 's exports/meta/ — so a + /// route (Navigation) profile and a Meta profile sharing the same name + /// cannot silently overwrite each other's .af file (both stores + /// used to write into the same flat exports/ root). /// private void WriteLegacyExport(string name, NavigationSettings settings) { @@ -273,7 +278,7 @@ internal sealed class MossTankRouteProfileStore try { _host.Storage.WriteText( - $"exports/{LegacyFileName(name)}.af", + $"exports/nav/{LegacyFileName(name)}.af", MetafSerializer.SaveNav(settings)); } catch (Exception error) diff --git a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs index 2fab57bd8..8a506f9b5 100644 --- a/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs +++ b/tests/AcDream.Plugins.MossTank.Tests/MossTankPanelTests.cs @@ -678,7 +678,39 @@ public sealed class MossTankPanelTests Command(panel, "nav save Exported.nav"); Assert.StartsWith( "NAV: ", - storage.Text["exports/Exported.af"], + storage.Text["exports/nav/Exported.af"], + StringComparison.Ordinal); + } + + // Item C (Campaign VT slice-1 fix round): a Meta profile and a route + // (Navigation) profile named identically used to write into the SAME + // flat "exports/" directory — "Same.af" from one silently clobbered + // "Same.af" from the other. exports/meta/ and exports/nav/ are + // separate subdirectories so both coexist. + [Fact] + public void MetaAndRouteExportsWithTheSameNameDoNotCollide() + { + var storage = new MemoryStorage(); + storage.Text["imports/Same.met"] = + "1\r\nCondAct\r\n5\r\nCType\r\nAType\r\nCData\r\nAData\r\nState\r\n" + + "n\r\nn\r\nn\r\nn\r\nn\r\n1\r\n" + + "i\r\n1\r\ni\r\n2\r\ni\r\n0\r\ns\r\n/say imported\r\n" + + "s\r\nDefault\r\n"; + var panel = new MossTankPanel(new FakeHost(new FakeAutomation(), storage)); + + Command(panel, "meta load Same.met"); + Command(panel, "meta save Same.met"); + Command(panel, "nav save Same.nav"); + + Assert.True(storage.Text.ContainsKey("exports/meta/Same.af")); + Assert.True(storage.Text.ContainsKey("exports/nav/Same.af")); + Assert.StartsWith( + "STATE: ", + storage.Text["exports/meta/Same.af"], + StringComparison.Ordinal); + Assert.StartsWith( + "NAV: ", + storage.Text["exports/nav/Same.af"], StringComparison.Ordinal); } @@ -712,10 +744,10 @@ public sealed class MossTankPanelTests Command(panel, "meta save Exported.met"); Assert.StartsWith( "STATE: ", - storage.Text["exports/Exported.af"], + storage.Text["exports/meta/Exported.af"], StringComparison.Ordinal); Assert.True(MetafSerializer.TryLoadMeta( - storage.Text["exports/Exported.af"], + storage.Text["exports/meta/Exported.af"], NoOpSpellCatalogForExport.Instance, out MetaProfile exported, out string error), error);