Skip to content

Commit 596d997

Browse files
authored
Ingest required error (#974)
* WIP, this will take more work * I think I got it working, didn't need the cleaning code after all.
1 parent d28dce6 commit 596d997

File tree

6 files changed

+105
-24
lines changed

6 files changed

+105
-24
lines changed

NexusMods.App.sln.DotSettings

+4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=XL/@EntryIndexedValue">XL</s:String>
4747
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=XS/@EntryIndexedValue">XS</s:String>
4848
<s:String x:Key="/Default/CustomTools/CustomToolsData/@EntryValue"></s:String>
49+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
50+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
51+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
52+
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
4953
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
5054
<s:Boolean x:Key="/Default/UserDictionary/Words/=FOMOD/@EntryIndexedValue">True</s:Boolean>
5155
<s:Boolean x:Key="/Default/UserDictionary/Words/=Loadout/@EntryIndexedValue">True</s:Boolean>

src/Abstractions/NexusMods.Abstractions.DiskState/DiskStateTree.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Text.Json.Serialization;
33
using NexusMods.Abstractions.GameLocators;
44
using NexusMods.Abstractions.GameLocators.Trees;
5+
using NexusMods.Abstractions.Loadouts;
6+
using NexusMods.Abstractions.Serialization.DataModel.Ids;
57
using NexusMods.Hashing.xxHash64;
68
using NexusMods.Paths;
79
using NexusMods.Paths.Trees.Traits;
@@ -16,6 +18,11 @@ public class DiskStateTree : AGamePathNodeTree<DiskStateEntry>
1618
{
1719
private DiskStateTree(IEnumerable<KeyValuePair<GamePath, DiskStateEntry>> tree) : base(tree) { }
1820

21+
/// <summary>
22+
/// The associated loadout id.
23+
/// </summary>
24+
public IId LoadoutRevision { get; set; } = IdEmpty.Empty;
25+
1926
/// <summary>
2027
/// Creates a disk state from a list of files.
2128
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using NexusMods.Abstractions.GameLocators;
12
using NexusMods.Abstractions.Loadouts;
23

34
namespace NexusMods.Abstractions.DiskState;
@@ -8,17 +9,14 @@ namespace NexusMods.Abstractions.DiskState;
89
public interface IDiskStateRegistry
910
{
1011
/// <summary>
11-
/// Saves a disk state to the data store
12+
/// Saves a disk state to the data store for the given game installation
1213
/// </summary>
13-
/// <param name="loadoutId"></param>
14-
/// <param name="diskState"></param>
1514
/// <returns></returns>
16-
void SaveState(LoadoutId loadoutId, DiskStateTree diskState);
15+
void SaveState(GameInstallation installation, DiskStateTree diskState);
1716

1817
/// <summary>
19-
/// Gets the disk state associated with a specific version of a loadout (if any)
18+
/// Gets the disk state associated with a specific game installation, returns false if no state is found
2019
/// </summary>
21-
/// <param name="loadoutId"></param>
2220
/// <returns></returns>
23-
DiskStateTree? GetState(LoadoutId loadoutId);
21+
DiskStateTree? GetState(GameInstallation gameInstallation);
2422
}

src/Abstractions/NexusMods.Abstractions.Loadouts.Synchronizers/ALoadoutSynchronizer.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,10 @@ public virtual async Task<DiskStateTree> Apply(Loadout loadout)
491491
{
492492
var flattened = await LoadoutToFlattenedLoadout(loadout);
493493
var fileTree = await FlattenedLoadoutToFileTree(flattened, loadout);
494-
var prevState = _diskStateRegistry.GetState(loadout.LoadoutId)!;
494+
var prevState = _diskStateRegistry.GetState(loadout.Installation)!;
495495
var diskState = await FileTreeToDisk(fileTree, loadout, flattened, prevState, loadout.Installation);
496-
_diskStateRegistry.SaveState(loadout.LoadoutId, diskState);
496+
diskState.LoadoutRevision = loadout.DataStoreId;
497+
_diskStateRegistry.SaveState(loadout.Installation, diskState);
497498
return diskState;
498499
}
499500

@@ -503,7 +504,7 @@ public virtual async Task<Loadout> Ingest(Loadout loadout)
503504
// Reconstruct the previous file tree
504505
var prevFlattenedLoadout = await LoadoutToFlattenedLoadout(loadout);
505506
var prevFileTree = await FlattenedLoadoutToFileTree(prevFlattenedLoadout, loadout);
506-
var prevDiskState = _diskStateRegistry.GetState(loadout.LoadoutId)!;
507+
var prevDiskState = _diskStateRegistry.GetState(loadout.Installation)!;
507508

508509
// Get the new disk state
509510
var diskState = await GetDiskState(loadout.Installation);
@@ -512,7 +513,9 @@ public virtual async Task<Loadout> Ingest(Loadout loadout)
512513
var newLoadout = await FlattenedLoadoutToLoadout(flattenedLoadout, loadout, prevFlattenedLoadout);
513514

514515
await BackupNewFiles(loadout, fileTree);
515-
_diskStateRegistry.SaveState(loadout.LoadoutId, diskState);
516+
newLoadout.EnsurePersisted(_store);
517+
diskState.LoadoutRevision = newLoadout.DataStoreId;
518+
_diskStateRegistry.SaveState(loadout.Installation, diskState);
516519

517520
return newLoadout;
518521
}
@@ -591,8 +594,9 @@ public virtual async Task<Loadout> Manage(GameInstallation installation)
591594
Installation = installation,
592595
Mods = loadout.Mods.With(gameFiles.Id, gameFiles)
593596
});
594-
595-
_diskStateRegistry.SaveState(loadout.LoadoutId, initialState);
597+
598+
initialState.LoadoutRevision = loadout.DataStoreId;
599+
_diskStateRegistry.SaveState(loadout.Installation, initialState);
596600

597601
return loadout;
598602
}

src/NexusMods.DataModel/Loadouts/DiskStateRegistry.cs

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
using System.Diagnostics;
12
using System.IO.Compression;
3+
using System.Text;
24
using System.Text.Json;
35
using Microsoft.Extensions.Logging;
46
using NexusMods.Abstractions.DiskState;
7+
using NexusMods.Abstractions.GameLocators;
58
using NexusMods.Abstractions.Loadouts;
69
using NexusMods.Abstractions.Serialization;
710
using NexusMods.Abstractions.Serialization.DataModel;
11+
using NexusMods.Abstractions.Serialization.DataModel.Ids;
812
using Reloaded.Memory.Extensions;
913

1014
namespace NexusMods.DataModel.Loadouts;
@@ -31,12 +35,11 @@ public DiskStateRegistry(ILogger<DiskStateRegistry> logger, IDataStore dataStore
3135
/// <summary>
3236
/// Saves a disk state to the data store
3337
/// </summary>
34-
/// <param name="loadoutId"></param>
35-
/// <param name="diskState"></param>
3638
/// <returns></returns>
37-
public void SaveState(LoadoutId loadoutId, DiskStateTree diskState)
39+
public void SaveState(GameInstallation installation, DiskStateTree diskState)
3840
{
39-
var iid = loadoutId.ToEntityId(EntityCategory.DiskState);
41+
Debug.Assert(!diskState.LoadoutRevision.Equals(IdEmpty.Empty), "diskState.LoadoutRevision must be set");
42+
var iid = MakeId(installation);
4043
using var ms = new MemoryStream();
4144
{
4245
using var compressed = new GZipStream(ms, CompressionMode.Compress, leaveOpen: true);
@@ -45,14 +48,21 @@ public void SaveState(LoadoutId loadoutId, DiskStateTree diskState)
4548
_dataStore.PutRaw(iid, ms.GetBuffer().AsSpan().SliceFast(0, (int)ms.Length));
4649
}
4750

51+
private IId MakeId(GameInstallation installation)
52+
{
53+
var str = $"{installation.Game.Name}|{installation.Version}|{installation.Store.Value}";
54+
var bytes = Encoding.UTF8.GetBytes(str);
55+
return IId.FromSpan(EntityCategory.DiskState, bytes);
56+
}
57+
4858
/// <summary>
4959
/// Gets the disk state associated with a specific version of a loadout (if any)
5060
/// </summary>
51-
/// <param name="loadoutId"></param>
61+
/// <param name="gameInstallation"></param>
5262
/// <returns></returns>
53-
public DiskStateTree? GetState(LoadoutId loadoutId)
63+
public DiskStateTree? GetState(GameInstallation gameInstallation)
5464
{
55-
var iid = loadoutId.ToEntityId(EntityCategory.DiskState);
65+
var iid = MakeId(gameInstallation);
5666
var data = _dataStore.GetRaw(iid);
5767
if (data == null) return null;
5868
using var ms = new MemoryStream(data);

tests/NexusMods.DataModel.Tests/ALoadoutSynchronizerTests.cs

+62-4
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public async Task CanWriteDiskTreeToDisk()
205205
{
206206
var flattened = await _synchronizer.LoadoutToFlattenedLoadout(BaseList.Value);
207207
var fileTree = await _synchronizer.FlattenedLoadoutToFileTree(flattened, BaseList.Value);
208-
var prevState = DiskStateRegistry.GetState(BaseList.Id)!;
208+
var prevState = DiskStateRegistry.GetState(BaseList.Value.Installation)!;
209209
var diskState = await _synchronizer.FileTreeToDisk(fileTree, BaseList.Value, flattened, prevState, Install);
210210

211211
diskState.GetAllDescendentFiles()
@@ -323,7 +323,7 @@ public async Task CanIngestFileTree()
323323
// Reconstruct the previous file tree
324324
var prevFlattenedLoadout = await _synchronizer.LoadoutToFlattenedLoadout(BaseList.Value);
325325
var prevFileTree = await _synchronizer.FlattenedLoadoutToFileTree(prevFlattenedLoadout, BaseList.Value);
326-
var prevDiskState = DiskStateRegistry.GetState(BaseList.Id);
326+
var prevDiskState = DiskStateRegistry.GetState(BaseList.Value.Installation);
327327

328328
var fileTree = await _synchronizer.DiskToFileTree(diskState, BaseList.Value, prevFileTree, prevDiskState);
329329

@@ -383,7 +383,7 @@ public async Task CanIngestFlattenedList()
383383
// Reconstruct the previous file tree
384384
var prevFlattenedLoadout = await _synchronizer.LoadoutToFlattenedLoadout(BaseList.Value);
385385
var prevFileTree = await _synchronizer.FlattenedLoadoutToFileTree(prevFlattenedLoadout, BaseList.Value);
386-
var prevDiskState = DiskStateRegistry.GetState(BaseList.Id)!;
386+
var prevDiskState = DiskStateRegistry.GetState(BaseList.Value.Installation)!;
387387

388388
var fileTree = await _synchronizer.DiskToFileTree(diskState, BaseList.Value, prevFileTree, prevDiskState);
389389
var flattenedLoadout = await _synchronizer.FileTreeToFlattenedLoadout(fileTree, BaseList.Value, prevFlattenedLoadout);
@@ -452,7 +452,7 @@ public async Task CanIngestLoadout()
452452
// Reconstruct the previous file tree
453453
var prevFlattenedLoadout = await _synchronizer.LoadoutToFlattenedLoadout(BaseList.Value);
454454
var prevFileTree = await _synchronizer.FlattenedLoadoutToFileTree(prevFlattenedLoadout, BaseList.Value);
455-
var prevDiskState = DiskStateRegistry.GetState(BaseList.Id)!;
455+
var prevDiskState = DiskStateRegistry.GetState(BaseList.Value.Installation)!;
456456

457457
var fileTree = await _synchronizer.DiskToFileTree(diskState, BaseList.Value, prevFileTree, prevDiskState);
458458
var flattenedLoadout = await _synchronizer.FileTreeToFlattenedLoadout(fileTree, BaseList.Value, prevFlattenedLoadout);
@@ -652,4 +652,62 @@ public async Task CanWriteGeneratedFiles()
652652

653653
}
654654

655+
[Fact]
656+
public async Task CanSwitchBetweenLoadouts()
657+
{
658+
var listA = BaseList;
659+
// Apply the old state
660+
var baseState = await _synchronizer.Apply(listA.Value);
661+
662+
var newId = LoadoutId.NewId();
663+
LoadoutRegistry.Alter(newId, "Clone List",
664+
_ => listA.Value with {
665+
LoadoutId = newId,
666+
Name = "List B",
667+
});
668+
var listB = LoadoutRegistry.GetMarker(newId);
669+
670+
// We have two lists, so we can now swap between them and that's fine because they are the same so far
671+
await _synchronizer.Apply(listB.Value);
672+
await _synchronizer.Apply(listA.Value);
673+
await _synchronizer.Apply(listB.Value);
674+
675+
GamePath testFilePath = new(LocationId.Game, "textures/test_file_switcher.dds");
676+
677+
// Now let's add a mod to listA
678+
await AddMod("Other Files",
679+
// Each mod overrides the same files for these three files
680+
(testFilePath.Path, $"test-file-switcher")
681+
);
682+
683+
listA.Value.Mods.Count.Should().Be(listB.Value.Mods.Count + 1, "the mod should have been added");
684+
685+
var absPath = Install.LocationsRegister.GetResolvedPath(testFilePath);
686+
// And apply it
687+
await _synchronizer.Apply(listA.Value);
688+
absPath.FileExists.Should().BeTrue("the file should have been written to disk");
689+
(await absPath.ReadAllTextAsync()).Should().Be("test-file-switcher", "the file should contain the new data");
690+
691+
692+
// And switch back to listB
693+
await _synchronizer.Apply(listB.Value);
694+
695+
absPath.FileExists.Should().BeFalse("the file should have been removed from disk");
696+
697+
698+
var listCValue = await _synchronizer.Manage(Install);
699+
700+
var listC = LoadoutRegistry.GetMarker(listCValue.LoadoutId);
701+
await _synchronizer.Ingest(listC.Value);
702+
703+
await _synchronizer.Apply(listC.Value);
704+
705+
await _synchronizer.Apply(listA.Value);
706+
await _synchronizer.Apply(listB.Value);
707+
await _synchronizer.Apply(listC.Value);
708+
709+
710+
711+
712+
}
655713
}

0 commit comments

Comments
 (0)