Skip to content

Commit c480ccd

Browse files
authored
Don't re-insert files that change during ingestion (#2919)
* Don't re-insert files that change during ingestion, reuse the old record, fixes issue-2908 * Include changed verification file
1 parent a3e6bc5 commit c480ccd

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

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

+34-5
Original file line numberDiff line numberDiff line change
@@ -789,21 +789,50 @@ public record struct AddedEntry
789789
public required LoadoutFile.New LoadoutFileEntry { get; init; }
790790
}
791791

792-
private bool ActionIngestFromDisk(Dictionary<GamePath, SyncNode> syncTree, Loadout.ReadOnly loadout, ITransaction tx, ref EntityId? overridesGroup)
792+
private bool ActionIngestFromDisk(Dictionary<GamePath, SyncNode> syncTree, Loadout.ReadOnly loadout, ITransaction tx, ref EntityId? overridesGroupId)
793793
{
794-
overridesGroup ??= GetOrCreateOverridesGroup(tx, loadout);
794+
overridesGroupId ??= GetOrCreateOverridesGroup(tx, loadout);
795+
var newGroup = true;
796+
LoadoutItemGroup.ReadOnly? overridesGroup = null;
797+
if (!overridesGroupId.Value.InPartition(PartitionId.Temp))
798+
{
799+
newGroup = false;
800+
overridesGroup = LoadoutItemGroup.Load(loadout.Db, overridesGroupId.Value);
801+
}
802+
795803
var ingestedFiles = false;
796804

797805
foreach (var (path, node) in syncTree)
798806
{
799807
if (!node.Actions.HasFlag(Actions.IngestFromDisk))
800808
continue;
801-
802-
// Entry was added or modified
809+
810+
// If the overrides group is not new, we need to check if the file is already in the overrides group
811+
if (!newGroup)
812+
{
813+
var existingRecord = overridesGroup!.Value.Children
814+
.OfTypeLoadoutItemWithTargetPath()
815+
.FirstOrOptional(c => c.TargetPath == path);
816+
817+
if (existingRecord.HasValue)
818+
{
819+
// Update the disk entry
820+
tx.Add(node.Disk.EntityId, DiskStateEntry.LastModified, new DateTimeOffset(node.Disk.LastModifiedTicks, TimeSpan.Zero));
821+
822+
// Update the file entry
823+
tx.Add(existingRecord.Value.Id, LoadoutFile.Hash, node.Disk.Hash);
824+
tx.Add(existingRecord.Value.Id, LoadoutFile.Size, node.Disk.Size);
825+
826+
// Skip the rest of this process
827+
continue;
828+
}
829+
}
830+
831+
// Entry was added
803832
var id = tx.TempId();
804833
var loadoutItem = new LoadoutItem.New(tx, id)
805834
{
806-
ParentId = overridesGroup.Value,
835+
ParentId = overridesGroupId.Value,
807836
LoadoutId = loadout.Id,
808837
Name = path.FileName,
809838
};

tests/NexusMods.DataModel.Synchronizer.Tests/ExternalChangesTests.cs

+36
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using NexusMods.Abstractions.GameLocators;
55
using NexusMods.Abstractions.Loadouts;
66
using NexusMods.Games.TestFramework;
7+
using NexusMods.Hashing.xxHash3;
78
using NexusMods.Paths;
89
using Xunit.Abstractions;
910

@@ -87,6 +88,41 @@ public async Task ExistingFilesEndUpInOverrides()
8788

8889
}
8990

91+
/// <summary>
92+
/// (issue-2908) Changes to external files should be reflected in the external files after synchronizing
93+
/// </summary>
94+
[Fact]
95+
public async Task ChangingExternalFileUpdatesExternalFiles()
96+
{
97+
await Synchronizer.RescanFiles(GameInstallation);
98+
var loadoutA = await CreateLoadout();
99+
var externalFile = loadoutA.InstallationInstance.LocationsRegister[LocationId.Game] / "config.json";
100+
var gameFile = new GamePath(LocationId.Game, "config.json");
101+
102+
await externalFile.WriteAllTextAsync("version1");
103+
104+
var loadout = await Synchronizer.Synchronize(loadoutA);
105+
106+
var externalFileRecord = loadout.Items.OfTypeLoadoutItemWithTargetPath().Single(f => f.TargetPath == gameFile);
107+
if (!externalFileRecord.TryGetAsLoadoutFile(out var loadoutFile))
108+
Assert.Fail("The file should be in the loadout");
109+
110+
loadoutFile.Hash.Should().Be("version1".xxHash3AsUtf8());
111+
112+
await externalFile.WriteAllTextAsync("version2");
113+
114+
loadout = await Synchronizer.Synchronize(loadout);
115+
116+
var refreshedRecord = loadout.Items.OfTypeLoadoutItemWithTargetPath().Single(f => f.TargetPath == gameFile);
117+
refreshedRecord.Id.Should().Be(externalFileRecord.Id, "the file should be the same id");
118+
119+
if (!refreshedRecord.TryGetAsLoadoutFile(out var refreshedFile))
120+
Assert.Fail("The file should be in the loadout");
121+
122+
refreshedFile.Hash.Should().Be("version2".xxHash3AsUtf8());
123+
124+
}
125+
90126
private async Task ExtractV2ToGameFolder(AbsolutePath gameFolder)
91127
{
92128
// Extract the game files into the folder so that we trigger a game version update

tests/NexusMods.DataModel.Synchronizer.Tests/GeneralFileManagementTests.SynchronizerFileManagementTest.verified.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,9 @@ Updated the new file and synced it.
7272
| {Game, Data/image.dds} | 0x9829BF9CBC5991D2 | 85.484 KB |
7373
| {Game, README.txt} | 0x284B31336E242FFA | 26 B |
7474
| {Game, StubbedGame.exe} | 0xAD76A8A9233B7238 | 209 KB |
75-
### Loadout A - (2)
75+
### Loadout A - (1)
7676
| Path | Hash | Size | Disabled | Deleted |
7777
| --- | --- | --- | --- | --- |
78-
| {Game, bin/newFile.txt} | 0x673E3C493921A2D5 | 12 B | | |
7978
| {Game, bin/newFile.txt} | 0xB4E7270703F5F646 | 21 B | | |
8079

8180

0 commit comments

Comments
 (0)