Skip to content
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
7 changes: 5 additions & 2 deletions src/Core/API/BaseEventLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ public void UninstallStart() =>
LogMessage($"Uninstalling mods:");
public void UninstallCurrent(string packageName) =>
LogMessage($"- {packageName}");
public void UninstallSkipModified(string filePath) =>
LogMessage($" Skipping modified file {filePath}");
public void UninstallEnd()
{
}

public void BackupSkipped(RootedPath path) =>
LogMessage($" Skipping backup of {path.Full}");
public void RestoreSkipped(RootedPath path) =>
LogMessage($" Skipping restore of {path.Full}");
}
10 changes: 5 additions & 5 deletions src/Core/API/Init.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ public static IModManager CreateModManager(Config config)
var statePersistence = new JsonFileStatePersistence(modsDir);
var modRepository = new FileSystemRepository(modsDir);
var safeFileDelete = new WindowsRecyclingBin();
var packagesUpdater = CreateModPackagesUpdater(config.ModInstall, game, tempDir);
var packagesUpdater = CreatePackagesUpdater(config.ModInstall, game, tempDir);
return new ModManager(game, modRepository, packagesUpdater, statePersistence, safeFileDelete, tempDir);
}

internal static ModPackagesUpdater CreateModPackagesUpdater(
internal static IPackagesUpdater<IEventHandler> CreatePackagesUpdater(
ModInstallConfig installerConfig,
IGame game,
ITempDir tempDir)
{
var backupStrategy = new SuffixBackupStrategy();
var backupStrategyProvider = new SkipUpdatedBackupStrategy.Provider(backupStrategy);
return new ModPackagesUpdater(
var backupStrategyProvider = new SkipUpdatedBackupStrategy.Provider<IEventHandler>(
new SuffixBackupStrategy.Provider<PackageInstallationState, IEventHandler>());
return new ModPackagesUpdater<IEventHandler>(
new FileSystemInstallerFactory(), backupStrategyProvider,
TimeProvider.System, game, tempDir, installerConfig);
}
Expand Down
8 changes: 4 additions & 4 deletions src/Core/API/ModManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ internal class ModManager : IModManager
private readonly ISafeFileDelete safeFileDelete;
private readonly ITempDir tempDir;

private readonly ModPackagesUpdater packagesUpdater;
private readonly IPackagesUpdater<IEventHandler> packagesUpdater;

internal ModManager(IGame game, IPackageRepository packageRepository, ModPackagesUpdater packagesUpdater, IStatePersistence statePersistence, ISafeFileDelete safeFileDelete, ITempDir tempDir)
internal ModManager(IGame game, IPackageRepository packageRepository, IPackagesUpdater<IEventHandler> packagesUpdater, IStatePersistence statePersistence, ISafeFileDelete safeFileDelete, ITempDir tempDir)
{
this.game = game;
this.packageRepository = packageRepository;
Expand Down Expand Up @@ -47,7 +47,7 @@ public List<ModState> FetchState()
var availableModPackages = enabledModPackages.Merge(disabledModPackages);

var isModInstalled = DependencyResolver
.CollectValues(installedMods, s => s.Dependencies ?? Array.Empty<string>(), s => s?.Partial ?? true)
.CollectValues(installedMods, s => s.Dependencies.Concat(s.ShadowedBy).ToArray(), s => s?.Partial ?? true)
.SelectValues<string, IReadOnlySet<bool>, bool?>(partials => partials.Any(p => p) ? null : true);

var modsOutOfDate = installedMods.SelectValues((packageName, modInstallationState) =>
Expand Down Expand Up @@ -158,7 +158,7 @@ private void UpdateMods(IEnumerable<Package> packages, IEventHandler eventHandle
nextState =>
statePersistence.WriteState(new SavedState(
Install: new InstallationState(
Time: nextState.Values.Max(s => s.Time),
Time: null,
Mods: nextState
)
)),
Expand Down
44 changes: 24 additions & 20 deletions src/Core/Mods/Installation/ModPackagesUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,16 @@

namespace Core.Mods.Installation;

public class ModPackagesUpdater : PackagesUpdater<ModPackagesUpdater.IEventHandler>
public class ModPackagesUpdater<TEventHandler> : PackagesUpdater<TEventHandler>
where TEventHandler : ModPackagesUpdater.IEventHandler
{
#region TODO Move to a better place when not called all over the place

internal const string BootfilesPrefix = "__bootfiles";

internal static bool IsBootFiles(string packageName) =>
packageName.StartsWith(BootfilesPrefix);

#endregion

public interface IEventHandler : PackagesUpdater.IEventHandler, BootfilesInstaller.IEventHandler
{
}

private readonly IGame game;
private readonly ITempDir tempDir;
private readonly ModInstaller.IConfig config;

public ModPackagesUpdater(
IInstallerFactory installerFactory,
IBackupStrategyProvider<PackageInstallationState> backupStrategyProvider,
IBackupStrategyProvider<PackageInstallationState, TEventHandler> backupStrategyProvider,
TimeProvider timeProvider,
IGame game,
ITempDir tempDir,
Expand All @@ -45,23 +33,39 @@ protected override void Apply(
IReadOnlyDictionary<string, PackageInstallationState> currentState,
IReadOnlyCollection<IInstaller> installers,
string installDir,
Action<string, PackageInstallationState?> afterInstall,
IEventHandler eventHandler,
Action<string, PackageInstallationState?> updatePackageState,
TEventHandler eventHandler,
CancellationToken cancellationToken)
{
var (bootfiles, notBootfiles) = installers.Partition(p => IsBootFiles(p.PackageName));
var (bootfiles, notBootfiles) = installers.Partition(p => ModPackagesUpdater.IsBootFiles(p.PackageName));

var bootfilesInstaller = CreateBootfilesInstaller(bootfiles, eventHandler);
var allInstallers = notBootfiles
.Select(i => new ModInstaller(i, bootfilesInstaller.PackageName, game, tempDir, config))
.Append(bootfilesInstaller).ToImmutableArray();

base.Apply(currentState, allInstallers, installDir, afterInstall, eventHandler, cancellationToken);
base.Apply(currentState, allInstallers, installDir, updatePackageState, eventHandler, cancellationToken);
}

private IInstaller CreateBootfilesInstaller(IEnumerable<IInstaller> bootfilesPackageInstallers, IEventHandler eventHandler)
private IInstaller CreateBootfilesInstaller(IEnumerable<IInstaller> bootfilesPackageInstallers, ModPackagesUpdater.IEventHandler eventHandler)
{
var installer = bootfilesPackageInstallers.FirstOrDefault();
return new BootfilesInstaller(installer, game, tempDir, eventHandler, config);
}
}

public static class ModPackagesUpdater
{
#region TODO Move to a better place when not called all over the place

internal const string BootfilesPrefix = "__bootfiles";

internal static bool IsBootFiles(string packageName) =>
packageName.StartsWith(BootfilesPrefix);

#endregion

public interface IEventHandler : PackagesUpdater.IEventHandler, BootfilesInstaller.IEventHandler
{
}
}
9 changes: 9 additions & 0 deletions src/Core/Packages/Installation/Backup/IBackupEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Core.Utils;

namespace Core.Packages.Installation.Backup;

public interface IBackupEventHandler
{
void BackupSkipped(RootedPath path);
void RestoreSkipped(RootedPath path);
}
21 changes: 20 additions & 1 deletion src/Core/Packages/Installation/Backup/IBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,27 @@ namespace Core.Packages.Installation.Backup;

public interface IBackupStrategy
{
/// <summary>
/// Performs backup of a file.
/// </summary>
/// <param name="path">File to back up.</param>
public void PerformBackup(RootedPath path);
public bool RestoreBackup(RootedPath path);

/// <summary>
/// Restores and deletes a previously performed backup.
/// </summary>
/// <param name="path">File to restore.</param>
public void RestoreBackup(RootedPath path);

/// <summary>
/// Removes an existing backup without restoring it.
/// </summary>
/// <param name="path"></param>
public void DeleteBackup(RootedPath path);

/// <summary>
/// Optional post-install steps to track files overwritten by game updates.
/// </summary>
/// <param name="path"></param>
public void AfterInstall(RootedPath path);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Core.Packages.Installation.Backup;

public interface IBackupStrategyProvider<in TState>
public interface IBackupStrategyProvider<in TState, in TEventHandler>
{
IBackupStrategy BackupStrategy(TState? state);
IBackupStrategy BackupStrategy(TState? state, TEventHandler? eventHandler);
}
21 changes: 11 additions & 10 deletions src/Core/Packages/Installation/Backup/MoveFileBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ public interface IBackupFileNaming

private readonly IFileSystem fs;
private readonly IBackupFileNaming backupFileNaming;
private readonly IBackupEventHandler? eventHandler;

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

public MoveFileBackupStrategy(IFileSystem fs, IBackupFileNaming backupFileNaming)
internal MoveFileBackupStrategy(IFileSystem fs, IBackupFileNaming backupFileNaming, IBackupEventHandler? eventHandler)
{
this.fs = fs;
this.backupFileNaming = backupFileNaming;
this.eventHandler = eventHandler;
}

public virtual void PerformBackup(RootedPath path)
Expand All @@ -37,17 +39,18 @@ public virtual void PerformBackup(RootedPath path)
}

var backupFilePath = backupFileNaming.ToBackup(path.Full);

if (fs.File.Exists(backupFilePath))
{
fs.File.Delete(path.Full);
eventHandler?.BackupSkipped(path);
return;
}
else
{
fs.File.Move(path.Full, backupFilePath);
}

fs.File.Move(path.Full, backupFilePath);
}

public bool RestoreBackup(RootedPath path)
public void RestoreBackup(RootedPath path)
{
if (fs.File.Exists(path.Full))
{
Expand All @@ -58,8 +61,6 @@ public bool RestoreBackup(RootedPath path)
{
fs.File.Move(backupFilePath, path.Full);
}

return true;
}

public void DeleteBackup(RootedPath path)
Expand Down
33 changes: 20 additions & 13 deletions src/Core/Packages/Installation/Backup/SkipUpdatedBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,45 @@ namespace Core.Packages.Installation.Backup;
/// </summary>
internal class SkipUpdatedBackupStrategy : IBackupStrategy
{
internal class Provider : IBackupStrategyProvider<PackageInstallationState>
internal class Provider<TEventHandler> : IBackupStrategyProvider<PackageInstallationState, TEventHandler>
where TEventHandler : IBackupEventHandler
{
private readonly IBackupStrategy baseStrategy;
private readonly IBackupStrategyProvider<PackageInstallationState, TEventHandler> baseProvider;

public Provider(IBackupStrategy baseStrategy)
public Provider(IBackupStrategyProvider<PackageInstallationState, TEventHandler> baseProvider)
{
this.baseStrategy = baseStrategy;
this.baseProvider = baseProvider;
}

public IBackupStrategy BackupStrategy(PackageInstallationState? state) =>
new SkipUpdatedBackupStrategy(baseStrategy, state?.Time);
public IBackupStrategy BackupStrategy(PackageInstallationState? state, TEventHandler? eventHandler) {
var baseStrategy = baseProvider.BackupStrategy(state, eventHandler);
return new SkipUpdatedBackupStrategy(baseStrategy, state?.Time, eventHandler);
}
}

private readonly IFileSystem fs;
private readonly IBackupStrategy inner;
private readonly DateTime? backupTimeUtc;
private readonly IBackupEventHandler? eventHandler;

private SkipUpdatedBackupStrategy(
IBackupStrategy backupStrategy,
DateTime? backupTimeUtc) :
this(new FileSystem(), backupStrategy, backupTimeUtc)
DateTime? backupTimeUtc,
IBackupEventHandler? eventHandler) :
this(new FileSystem(), backupStrategy, backupTimeUtc, eventHandler)
{
}

internal SkipUpdatedBackupStrategy(
IFileSystem fs,
IBackupStrategy backupStrategy,
DateTime? backupTimeUtc)
DateTime? backupTimeUtc,
IBackupEventHandler? eventHandler)
{
this.fs = fs;
inner = backupStrategy;
this.backupTimeUtc = backupTimeUtc;
this.eventHandler = eventHandler;
}

public void DeleteBackup(RootedPath path) =>
Expand All @@ -48,15 +55,15 @@ public void DeleteBackup(RootedPath path) =>
public void PerformBackup(RootedPath path) =>
inner.PerformBackup(path);

public bool RestoreBackup(RootedPath path)
public void RestoreBackup(RootedPath path)
{
if (FileWasOverwritten(path))
{
inner.DeleteBackup(path);
return false;
eventHandler?.RestoreSkipped(path);
return;
}

return inner.RestoreBackup(path);
inner.RestoreBackup(path);
}

private bool FileWasOverwritten(RootedPath path) =>
Expand Down
10 changes: 9 additions & 1 deletion src/Core/Packages/Installation/Backup/SuffixBackupStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

public class SuffixBackupStrategy : MoveFileBackupStrategy
{
internal class Provider<TState, TEventHandler> : IBackupStrategyProvider<TState, TEventHandler>
where TEventHandler : IBackupEventHandler
{
public IBackupStrategy BackupStrategy(TState? _, TEventHandler? eventHandler) =>
new SuffixBackupStrategy(eventHandler);
}

private class BackupFileNaming : IBackupFileNaming
{
private const string BackupSuffix = ".orig";
Expand All @@ -10,7 +17,8 @@ private class BackupFileNaming : IBackupFileNaming
public bool IsBackup(string fullPath) => fullPath.EndsWith(BackupSuffix);
}

public SuffixBackupStrategy() : base(new BackupFileNaming())
public SuffixBackupStrategy(IBackupEventHandler? eventHandler) :
base(new BackupFileNaming(), eventHandler)
{
}
}
8 changes: 4 additions & 4 deletions src/Core/Packages/Installation/IPackagesUpdater.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using Core.Packages.Installation.Installers;
using Core.Packages.Repository;

namespace Core.Packages.Installation;

public interface IPackagesUpdater<in TEventHandler>
{
void Apply(
IReadOnlyDictionary<string, PackageInstallationState> currentState,
IReadOnlyCollection<IInstaller> installers,
IReadOnlyDictionary<string, PackageInstallationState> previousState,
IEnumerable<Package> packages,
string installDir,
Action<string, PackageInstallationState?> afterInstall,
Action<IReadOnlyDictionary<string, PackageInstallationState>> afterInstall,
TEventHandler eventHandler,
CancellationToken cancellationToken);
}
Loading
Loading