Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring for mod update #130

Merged
merged 18 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading