Skip to content

Commit db510f8

Browse files
authored
Merge pull request #3197 from Nexus-Mods/fix-missing-apply-button
Fix Applying indicator and jobMonitor notifications
2 parents e2ed13a + 86715ac commit db510f8

File tree

8 files changed

+75
-34
lines changed

8 files changed

+75
-34
lines changed

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class ALoadoutSynchronizer : ILoadoutSynchronizer
4747
private readonly IOSInformation _os;
4848
private readonly ISorter _sorter;
4949
private readonly IGarbageCollectorRunner _garbageCollectorRunner;
50+
private readonly ISynchronizerService _synchronizerService;
5051
private readonly IServiceProvider _serviceProvider;
5152

5253
/// <summary>
@@ -71,7 +72,9 @@ protected ALoadoutSynchronizer(
7172
IGarbageCollectorRunner garbageCollectorRunner)
7273
{
7374
_serviceProvider = serviceProvider;
75+
_synchronizerService = serviceProvider.GetRequiredService<ISynchronizerService>();
7476
_jobMonitor = serviceProvider.GetRequiredService<IJobMonitor>();
77+
7578
_fileHashService = fileHashService;
7679

7780
Logger = logger;
@@ -353,7 +356,7 @@ private static bool FileIsEnabled(LoadoutItem.ReadOnly arg)
353356
/// <inheritdoc />
354357
public async Task<Dictionary<GamePath, SyncNode>> BuildSyncTree(Loadout.ReadOnly loadout)
355358
{
356-
var metadata = await ReindexState(loadout.InstallationInstance, false, Connection);
359+
var metadata = await ReindexState(loadout.InstallationInstance, ignoreModifiedDates: false, Connection);
357360
var previouslyApplied = loadout.Installation.GetLastAppliedDiskState();
358361
return BuildSyncTree(DiskStateToPathPartPair(metadata.DiskStateEntries), DiskStateToPathPartPair(previouslyApplied), loadout);
359362
}
@@ -530,7 +533,7 @@ where versionFiles.Contains(path)
530533
// Delete all the matching override files
531534
foreach (var file in toDelete)
532535
{
533-
tx.Delete(file, false);
536+
tx.Delete(file, recursive: false);
534537

535538
// The backed up file is being 'promoted' to a game file, which needs
536539
// to be rooted explicitly in case the user uses a feature like 'undo'
@@ -689,7 +692,7 @@ private void ActionAddReifiedDelete(Dictionary<GamePath, SyncNode> groupings, Lo
689692
continue;
690693

691694
// If we found a match, we need to remove the entity itself
692-
tx.Delete(match, false);
695+
tx.Delete(match, recursive: false);
693696
continue;
694697
}
695698
}
@@ -1440,15 +1443,17 @@ private async ValueTask<Hash> MaybeHashFile(IDb hashDb, GamePath gamePath, Absol
14401443
// If there is no currently synced loadout, then we can ingest the game folder
14411444
if (!GameInstallMetadata.LastSyncedLoadout.TryGetValue(remappedLoadout.Installation, out var lastSyncedLoadoutId))
14421445
{
1443-
remappedLoadout = await Synchronize(remappedLoadout);
1446+
await _synchronizerService.Synchronize(remappedLoadout.LoadoutId);
1447+
remappedLoadout = remappedLoadout.Rebase();
14441448
}
14451449
else
14461450
{
14471451
// check if the last synced loadout is valid (can apparently happen if the user unmanaged the game and manages it again)
14481452
var lastSyncedLoadout = Loadout.Load(remappedLoadout.Db, lastSyncedLoadoutId);
14491453
if (!lastSyncedLoadout.IsValid())
14501454
{
1451-
remappedLoadout = await Synchronize(lastSyncedLoadout);
1455+
await _synchronizerService.Synchronize(remappedLoadout.LoadoutId);
1456+
remappedLoadout = remappedLoadout.Rebase();
14521457
}
14531458
}
14541459
return remappedLoadout;
@@ -1491,7 +1496,7 @@ public Optional<LoadoutId> GetCurrentlyActiveLoadout(GameInstallation installati
14911496
public async Task ActivateLoadout(LoadoutId loadoutId)
14921497
{
14931498
var loadout = Loadout.Load(Connection.Db, loadoutId);
1494-
var reindexed = await ReindexState(loadout.InstallationInstance, false, Connection);
1499+
var reindexed = await ReindexState(loadout.InstallationInstance, ignoreModifiedDates: false, Connection);
14951500

14961501
var tree = BuildSyncTree(DiskStateToPathPartPair(reindexed.DiskStateEntries), DiskStateToPathPartPair(reindexed.DiskStateEntries), loadout);
14971502
ProcessSyncTree(tree);
@@ -1551,7 +1556,7 @@ await _jobMonitor.Begin(new UnmanageGameJob(installation), async ctx =>
15511556
foreach (var file in GameBackedUpFile.All(Connection.Db))
15521557
{
15531558
if (file.GameInstallId.Value == installation.GameMetadataId)
1554-
tx.Delete(file, false);
1559+
tx.Delete(file, recursive: false);
15551560
}
15561561

15571562
await tx.Commit();
@@ -1622,7 +1627,7 @@ await _jobMonitor.Begin(new UnmanageGameJob(installation), async ctx =>
16221627
datom.ValueSpan.CopyTo(buffer.Span);
16231628

16241629
// Create the new datom and reference the copied value
1625-
var prefix = new KeyPrefix(newId, datom.A, TxId.Tmp, false, datom.Prefix.ValueTag);
1630+
var prefix = new KeyPrefix(newId, datom.A, TxId.Tmp, isRetract: false, datom.Prefix.ValueTag);
16261631
var newDatom = new Datom(prefix, buffer[..datom.ValueSpan.Length]);
16271632

16281633
// Remap any entity ids in the value
@@ -1681,10 +1686,10 @@ public async Task DeleteLoadout(LoadoutId loadoutId, GarbageCollectorRunMode gcR
16811686
}
16821687

16831688
using var tx = Connection.BeginTransaction();
1684-
tx.Delete(loadoutId, false);
1689+
tx.Delete(loadoutId, recursive: false);
16851690
foreach (var item in loadout.Items)
16861691
{
1687-
tx.Delete(item.Id, false);
1692+
tx.Delete(item.Id, recursive: false);
16881693
}
16891694
await tx.Commit();
16901695

@@ -1695,7 +1700,7 @@ public async Task DeleteLoadout(LoadoutId loadoutId, GarbageCollectorRunMode gcR
16951700
public async Task ResetToOriginalGameState(GameInstallation installation, LocatorId[] locatorIds)
16961701
{
16971702
var gameState = _fileHashService.GetGameFiles((installation.Store, locatorIds));
1698-
var metaData = await ReindexState(installation, false, Connection);
1703+
var metaData = await ReindexState(installation, ignoreModifiedDates: false, Connection);
16991704

17001705
List<PathPartPair> diskState = [];
17011706

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public interface ILoadoutSynchronizer
4949
/// Rescan the files in the folders this game requires. This is used to bring the local cache up to date with the
5050
/// whatever is on disk.
5151
/// </summary>
52+
/// <param name="gameInstallation">The game installation to rescan.</param>
53+
/// <param name="ignoreModifiedDate">
54+
/// If false, files that have unchanged modified date since the last scan will be skipped.
55+
/// If true, all files will be rehashed.
56+
/// </param>
5257
Task<GameInstallMetadata.ReadOnly> RescanFiles(GameInstallation gameInstallation, bool ignoreModifiedDate = false);
5358

5459
#endregion
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using NexusMods.Abstractions.Jobs;
2+
using R3;
3+
4+
namespace NexusMods.Abstractions.Loadouts.Synchronizers;
5+
6+
/// <summary>
7+
/// Synchronize the loadout with the game folder,
8+
/// any changes in the game folder will be added to the loadout,
9+
/// and any new changes in the loadout will be applied to the game folder.
10+
/// </summary>
11+
/// <param name="LoadoutId"></param>
12+
public record SynchronizeLoadoutJob(LoadoutId LoadoutId) : IJobDefinition<Unit>;
13+

src/NexusMods.App.UI/LeftMenu/Items/ApplyControl/ApplyControlView.axaml.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public ApplyControlView()
2626

2727
this.OneWayBind(ViewModel, vm => vm.IsProcessing, v => v.ProcessingChangesStackPanel.IsVisible)
2828
.DisposeWith(disposables);
29+
30+
this.OneWayBind(ViewModel, vm => vm.IsApplying, v => v.ProgressBarControl.IsVisible)
31+
.DisposeWith(disposables);
2932

3033
this.WhenAnyObservable(view => view.ViewModel!.ApplyCommand.CanExecute)
3134
.OnUI()
@@ -37,11 +40,6 @@ public ApplyControlView()
3740
)
3841
.DisposeWith(disposables);
3942

40-
this.WhenAnyObservable(view => view.ViewModel!.ApplyCommand.IsExecuting)
41-
.OnUI()
42-
.Subscribe(isApplying => { ProgressBarControl.IsVisible = isApplying; })
43-
.DisposeWith(disposables);
44-
4543
this.OneWayBind(ViewModel, vm => vm.ApplyButtonText, v => v.ApplyButtonTextBlock.Text)
4644
.DisposeWith(disposables);
4745
}

src/NexusMods.App.UI/LeftMenu/Items/ApplyControl/ApplyControlViewModel.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Reactive.Linq;
44
using DynamicData;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Logging;
67
using NexusMods.Abstractions.Jobs;
78
using NexusMods.Abstractions.Loadouts;
89
using NexusMods.Abstractions.UI;
@@ -31,6 +32,7 @@ public class ApplyControlViewModel : AViewModel<IApplyControlViewModel>, IApplyC
3132
private readonly IServiceProvider _serviceProvider;
3233
private readonly GameInstallMetadataId _gameMetadataId;
3334
[Reactive] private bool CanApply { get; set; } = true;
35+
[Reactive] public bool IsApplying { get; private set; }
3436

3537
public ReactiveCommand<Unit, Unit> ApplyCommand { get; }
3638
public ReactiveCommand<NavigationInformation, Unit> ShowApplyDiffCommand { get; }
@@ -97,7 +99,8 @@ public ApplyControlViewModel(LoadoutId loadoutId, IServiceProvider serviceProvid
9799
// - This is done in 'Synchronize' method.
98100
// - They're running a tool from within the App.
99101
// - Check running jobs.
100-
loadoutStatuses.CombineLatest(isProcessingObservable, gameStatuses, gameRunningTracker.GetWithCurrentStateAsStarting(), (loadout, isProcessing, game, running) => (loadout, isProcessing, game, running))
102+
loadoutStatuses.CombineLatest(isProcessingObservable, gameStatuses, gameRunningTracker.GetWithCurrentStateAsStarting(),
103+
(loadout, isProcessing, game, running) => (loadout, isProcessing, game, running))
101104
.OnUI()
102105
.Subscribe(status =>
103106
{
@@ -113,8 +116,16 @@ public ApplyControlViewModel(LoadoutId loadoutId, IServiceProvider serviceProvid
113116
&& !running
114117
&& gameStatus != GameSynchronizerState.Busy
115118
&& ldStatus == LoadoutSynchronizerState.Current;
119+
116120
})
117121
.DisposeWith(disposables);
122+
123+
_jobMonitor.HasActiveJob<SynchronizeLoadoutJob>(job => job.LoadoutId == loadoutId)
124+
.Prepend(_jobMonitor.Jobs.Any(job => job.Definition is SynchronizeLoadoutJob sJob && sJob.LoadoutId == loadoutId))
125+
.OnUI()
126+
.Subscribe(isApplying => IsApplying = isApplying)
127+
.DisposeWith(disposables);
128+
118129
}
119130
);
120131
}

src/NexusMods.App.UI/LeftMenu/Items/ApplyControl/IApplyControlViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ public interface IApplyControlViewModel : IViewModelInterface
1717

1818
bool IsProcessing { get; }
1919

20+
bool IsApplying { get; }
21+
2022
string ApplyButtonText { get; }
2123
}

src/NexusMods.DataModel/Synchronizer/SynchronizerService.cs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,31 @@ public IJobTask<ProcessLoadoutChangesJob, bool> GetShouldSynchronize(LoadoutId l
8181
/// <inheritdoc />
8282
public async Task Synchronize(LoadoutId loadoutId)
8383
{
84-
await _semaphore.WaitAsync();
85-
try
86-
{
87-
var loadout = Loadout.Load(_conn.Db, loadoutId);
88-
ThrowIfMainBinaryInUse(loadout);
84+
await _jobMonitor.Begin(new SynchronizeLoadoutJob(loadoutId),
85+
async ctx =>
86+
{
87+
await _semaphore.WaitAsync();
88+
try
89+
{
90+
var loadout = Loadout.Load(_conn.Db, loadoutId);
91+
ThrowIfMainBinaryInUse(loadout);
8992

90-
var loadoutState = GetOrAddLoadoutState(loadoutId);
91-
using var _ = loadoutState.WithLock();
93+
var loadoutState = GetOrAddLoadoutState(loadoutId);
94+
using var _ = loadoutState.WithLock();
9295

93-
var gameState = GetOrAddGameState(loadout.InstallationInstance.GameMetadataId);
94-
using var _2 = gameState.WithLock();
96+
var gameState = GetOrAddGameState(loadout.InstallationInstance.GameMetadataId);
97+
using var _2 = gameState.WithLock();
9598

96-
await loadout.InstallationInstance.GetGame().Synchronizer.Synchronize(loadout);
97-
}
98-
finally
99-
{
100-
_semaphore.Release();
101-
}
99+
await loadout.InstallationInstance.GetGame().Synchronizer.Synchronize(loadout);
100+
}
101+
finally
102+
{
103+
_semaphore.Release();
104+
}
105+
106+
return Unit.Default;
107+
}
108+
);
102109
}
103110

104111
private SynchronizerState GetOrAddLoadoutState(LoadoutId loadoutId)

src/NexusMods.Jobs/JobContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace NexusMods.Jobs;
88
public sealed class JobContext<TJobDefinition, TJobResult> : IJobWithResult<TJobResult>, IJobContext<TJobDefinition>
99
where TJobDefinition : IJobDefinition<TJobResult> where TJobResult : notnull
1010
{
11-
private readonly Subject<JobStatus> _status;
11+
private readonly BehaviorSubject<JobStatus> _status;
1212
private readonly Subject<Optional<Percent>> _progress;
1313
private readonly Subject<Optional<double>> _rateOfProgress;
1414
private readonly TJobDefinition _definition;
@@ -25,7 +25,7 @@ internal JobContext(TJobDefinition definition, IJobMonitor monitor, IJobGroup jo
2525
_action = action;
2626
_definition = definition;
2727
Monitor = monitor;
28-
_status = new Subject<JobStatus>();
28+
_status = new BehaviorSubject<JobStatus>(JobStatus.Created);
2929
_progress = new Subject<Optional<Percent>>();
3030
_rateOfProgress = new Subject<Optional<double>>();
3131

0 commit comments

Comments
 (0)