Skip to content

Commit

Permalink
Refactoring for mod update (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
paoloambrosio authored Jan 4, 2025
1 parent ec9bff1 commit f2f718d
Show file tree
Hide file tree
Showing 39 changed files with 920 additions and 418 deletions.
2 changes: 1 addition & 1 deletion src/CLI/ConsoleEventLogger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Core;
using Core.API;
using Core.Utils;

namespace AMS2CM.CLI;
Expand Down
2 changes: 1 addition & 1 deletion src/CLI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using AMS2CM.CLI;
using Core;
using Core.API;

try
{
Expand Down
10 changes: 1 addition & 9 deletions src/Core/BaseEventLogger.cs → src/Core/API/BaseEventLogger.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Core.Utils;

namespace Core;
namespace Core.API;

/// <summary>
/// This class is here because of the CLI. Move it into the GUI once the CLI
Expand All @@ -27,14 +27,6 @@ public void PostProcessingStart() =>
LogMessage("Post-processing:");
public void ExtractingBootfiles(string? packageName) =>
LogMessage($"Extracting bootfiles from {packageName ?? "game"}");
public void ExtractingBootfilesErrorMultiple(IReadOnlyCollection<string> bootfilesPackageNames)
{
LogMessage("Multiple bootfiles found:");
foreach (var packageName in bootfilesPackageNames)
{
LogMessage($"- {packageName}");
}
}
public void PostProcessingVehicles() =>
LogMessage("- Appending crd file entries");
public void PostProcessingTracks() =>
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Config.cs → src/Core/API/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Core.SoftwareUpdates;
using Microsoft.Extensions.Configuration;

namespace Core;
namespace Core.API;

public class Config
{
Expand Down Expand Up @@ -34,7 +34,7 @@ public class GameConfig : Game.IConfig
public string ProcessName { get; set; } = "Undefined";
}

public class ModInstallConfig : ModInstaller.IConfig, InstallationFactory.IConfig
public class ModInstallConfig : InstallationFactory.IConfig
{
public IEnumerable<string> DirsAtRoot { get; set; } = Array.Empty<string>();
public IEnumerable<string> ExcludedFromInstall { get; set; } = Array.Empty<string>();
Expand Down
2 changes: 1 addition & 1 deletion src/Core/IModManager.cs → src/Core/API/IModManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core;
namespace Core.API;

public interface IModManager
{
Expand Down
8 changes: 5 additions & 3 deletions src/Core/Init.cs → src/Core/API/Init.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Core.Games;
using Core.Backup;
using Core.Games;
using Core.IO;
using Core.Mods;
using Core.State;

namespace Core;
namespace Core.API;

public static class Init
{
Expand All @@ -18,7 +19,8 @@ public static IModManager CreateModManager(Config config)
var modRepository = new ModRepository(modsDir);
var installationFactory = new InstallationFactory(game, tempDir, config.ModInstall);
var safeFileDelete = new WindowsRecyclingBin();
var modInstaller = new ModInstaller(installationFactory, tempDir, config.ModInstall);
var backupStrategy = new SuffixBackupStrategy();
var modInstaller = new ModInstaller(installationFactory, backupStrategy);
return new ModManager(game, modRepository, modInstaller, statePersistence, safeFileDelete, tempDir);
}
}
94 changes: 36 additions & 58 deletions src/Core/ModManager.cs → src/Core/API/ModManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
using Core.Mods;
using Core.State;
using Core.Utils;
using static Core.IModManager;
using static Core.API.IModManager;

namespace Core;
namespace Core.API;

internal class ModManager : IModManager
{
Expand Down Expand Up @@ -51,7 +51,7 @@ public List<ModState> FetchState()
var availableModPackages = enabledModPackages.Merge(disabledModPackages);

var bootfilesFailed = installedMods.Where(kv => BootfilesManager.IsBootFiles(kv.Key) && (kv.Value?.Partial ?? false)).Any();
var isModInstalled = installedMods.SelectValues<string, InternalModInstallationState, bool?>(modInstallationState =>
var isModInstalled = installedMods.SelectValues<string, ModInstallationState, bool?>(modInstallationState =>
modInstallationState is null ? false : ((modInstallationState.Partial || bootfilesFailed) ? null : true)
);
var modsOutOfDate = installedMods.SelectValues((packageName, modInstallationState) =>
Expand Down Expand Up @@ -79,7 +79,7 @@ public List<ModState> FetchState()
}).ToList();
}

private static bool IsOutOfDate(ModPackage? modPackage, InternalModInstallationState? modInstallationState)
private static bool IsOutOfDate(ModPackage? modPackage, ModInstallationState? modInstallationState)
{
if (modPackage is null || modInstallationState is null)
{
Expand Down Expand Up @@ -138,57 +138,14 @@ public void InstallEnabledMods(IEventHandler eventHandler, CancellationToken can

// Clean what left by a previous failed installation
tempDir.Cleanup();
if (RestoreOriginalState(eventHandler, cancellationToken))
{
InstallAllModFiles(eventHandler, cancellationToken);
}
UpdateMods(modRepository.ListEnabledMods(), eventHandler, cancellationToken);
tempDir.Cleanup();
}

public void UninstallAllMods(IEventHandler eventHandler, CancellationToken cancellationToken = default)
{
CheckGameNotRunning();
RestoreOriginalState(eventHandler, cancellationToken);
}

private bool RestoreOriginalState(IEventHandler eventHandler, CancellationToken cancellationToken)
{
var previousInstallation = statePersistence.ReadState().Install;
var modsLeft = new Dictionary<string, InternalModInstallationState>(previousInstallation.Mods);
try
{
modInstaller.UninstallPackages(
previousInstallation,
game.InstallationDirectory,
modInstallation =>
{
if (modInstallation.Installed == IInstallation.State.NotInstalled)
{
modsLeft.Remove(modInstallation.PackageName);
}
else
{
modsLeft[modInstallation.PackageName] = new InternalModInstallationState(
FsHash: modInstallation.PackageFsHash,
Partial: modInstallation.Installed == IInstallation.State.PartiallyInstalled,
Files: modInstallation.InstalledFiles
);
}
},
eventHandler,
cancellationToken);
}
finally
{
statePersistence.WriteState(new InternalState(
Install: new(
Time: modsLeft.Any() ? previousInstallation.Time : null,
Mods: modsLeft
)
));
}
// Success if everything was uninstalled
return !modsLeft.Any();
UpdateMods(Array.Empty<ModPackage>(), eventHandler, cancellationToken);
}

private void CheckGameNotRunning()
Expand All @@ -199,28 +156,49 @@ private void CheckGameNotRunning()
}
}

private void InstallAllModFiles(IEventHandler eventHandler, CancellationToken cancellationToken)
private void UpdateMods(IReadOnlyCollection<ModPackage> packages, IEventHandler eventHandler, CancellationToken cancellationToken)
{
var installedFilesByMod = new Dictionary<string, InternalModInstallationState>();
var previousState = statePersistence.ReadState().Install.Mods;
var currentState = new Dictionary<string, ModInstallationState>(previousState);
try
{
modInstaller.InstallPackages(
modRepository.ListEnabledMods(),
modInstaller.Apply(
previousState,
packages,
game.InstallationDirectory,
modInstallation => installedFilesByMod.Add(modInstallation.PackageName, new(
modInstallation =>
{
switch (modInstallation.Installed)
{
case IInstallation.State.Installed:
case IInstallation.State.PartiallyInstalled:
currentState.Upsert(modInstallation.PackageName,
existing => existing with
{
Partial = modInstallation.Installed == IInstallation.State.PartiallyInstalled,
Files = modInstallation.InstalledFiles
},
() => new ModInstallationState(
Time: DateTime.Now,
FsHash: modInstallation.PackageFsHash,
Partial: modInstallation.Installed == IInstallation.State.PartiallyInstalled,
Files: modInstallation.InstalledFiles
)),
));
break;
case IInstallation.State.NotInstalled:
currentState.Remove(modInstallation.PackageName);
break;
}
},
eventHandler,
cancellationToken);
}
finally
{
statePersistence.WriteState(new InternalState(
statePersistence.WriteState(new SavedState(
Install: new(
Time: DateTime.UtcNow,
Mods: installedFilesByMod
Time: currentState.Values.Max(_ => _.Time),
Mods: currentState
)
));
}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/ModState.cs → src/Core/API/ModState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core;
namespace Core.API;

public record ModState(
string PackageName,
Expand Down
3 changes: 1 addition & 2 deletions src/Core/Backup/IBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
public interface IBackupStrategy
{
public void PerformBackup(string fullPath);
public void RestoreBackup(string fullPath);
public bool RestoreBackup(string fullPath);
public void DeleteBackup(string fullPath);
public bool IsBackupFile(string fullPath);
}
11 changes: 11 additions & 0 deletions src/Core/Backup/IInstallationBackupStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Core.Mods;

namespace Core.Backup;

public interface IInstallationBackupStrategy
{
public void PerformBackup(RootedPath path);
public bool RestoreBackup(RootedPath path);
public void DeleteBackup(RootedPath path);
public void AfterInstall(RootedPath path);
}
8 changes: 8 additions & 0 deletions src/Core/Backup/IModBackupStrategyProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Core.State;

namespace Core.Backup;

public interface IModBackupStrategyProvider
{
IInstallationBackupStrategy BackupStrategy(ModInstallationState? state);
}
38 changes: 29 additions & 9 deletions src/Core/Backup/MoveFileBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,40 @@

namespace Core.Backup;

public class MoveFileBackupStrategy
public class MoveFileBackupStrategy : IBackupStrategy
{
public interface IBackupFileNaming
{
public string ToBackup(string fullPath);
public bool IsBackup(string fullPath);
}

private readonly IFileSystem fs;
private readonly Func<string, string> generateBackupFilePath;
private readonly IBackupFileNaming backupFileNaming;

public MoveFileBackupStrategy(IBackupFileNaming backupFileNaming) :
this(new FileSystem(), backupFileNaming)
{
}

public MoveFileBackupStrategy(IFileSystem fs, Func<string, string> generateBackupFilePath)
public MoveFileBackupStrategy(IFileSystem fs, IBackupFileNaming backupFileNaming)
{
this.fs = fs;
this.generateBackupFilePath = generateBackupFilePath;
this.backupFileNaming = backupFileNaming;
}

public void PerformBackup(string fullPath)
public virtual void PerformBackup(string fullPath)
{
if (backupFileNaming.IsBackup(fullPath))
{
throw new InvalidOperationException("Installing a backup file is forbidden");
}
if (!fs.File.Exists(fullPath))
{
return;
}

var backupFilePath = generateBackupFilePath(fullPath);
var backupFilePath = backupFileNaming.ToBackup(fullPath);
if (fs.File.Exists(backupFilePath))
{
fs.File.Delete(fullPath);
Expand All @@ -32,18 +46,24 @@ public void PerformBackup(string fullPath)
}
}

public void RestoreBackup(string fullPath)
public bool RestoreBackup(string fullPath)
{
var backupFilePath = generateBackupFilePath(fullPath);
if (fs.File.Exists(fullPath))
{
fs.File.Delete(fullPath);
}
var backupFilePath = backupFileNaming.ToBackup(fullPath);
if (fs.File.Exists(backupFilePath))
{
fs.File.Move(backupFilePath, fullPath);
}

return true;
}

public void DeleteBackup(string fullPath)
{
var backupFilePath = generateBackupFilePath(fullPath);
var backupFilePath = backupFileNaming.ToBackup(fullPath);
if (fs.File.Exists(backupFilePath))
{
fs.File.Delete(backupFilePath);
Expand Down
Loading

0 comments on commit f2f718d

Please sign in to comment.