Skip to content

Commit

Permalink
Added CopyStore instruction to let launchers decide what files to ins…
Browse files Browse the repository at this point in the history
…tall
  • Loading branch information
Aragas committed Oct 4, 2024
1 parent 213f037 commit 3fa9ee6
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 30 deletions.
14 changes: 14 additions & 0 deletions src/Bannerlord.LauncherManager.Models/InstallResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ public ModuleInfoInstallInstruction() { }
[SetsRequiredMembers, JsonConstructor]
public ModuleInfoInstallInstruction(ModuleInfoExtended moduleInfo) => (ModuleInfo) = (moduleInfo);
}
public record CopyStoreInstallInstruction : IInstallInstruction
{
public InstallInstructionType Type { get; set; } = InstallInstructionType.Copy;
public required GameStore Store { get; set; }
public required string ModuleId { get; set; }
public required string Source { get; set; }
public required string Destination { get; set; }

public CopyStoreInstallInstruction() { }
[SetsRequiredMembers, JsonConstructor]
public CopyStoreInstallInstruction(GameStore store, string moduleId, string source, string destination) =>
(Store, ModuleId, Source, Destination) = (store, moduleId, source, destination);
}

public record NoneInstallInstruction : IInstallInstruction
{
Expand All @@ -42,6 +55,7 @@ public enum InstallInstructionType
None,
Copy,
ModuleInfo,
CopyStore
}

public interface IInstallInstruction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ export type GamePlatform = 'Win64' | 'Xbox' | 'Unknown';
export type NotificationType = 'hint' | 'info' | 'warning' | 'error';
export type DialogType = 'warning' | 'fileOpen' | 'fileSave';

export type InstructionType = 'Copy' | 'ModuleInfo';
export type InstructionType = 'Copy' | 'ModuleInfo' | 'CopyStore';
export interface InstallInstruction {
type: InstructionType;
store?: GameStore;
moduleInfo?: ModuleInfoExtended;
source?: string;
destination?: string;
Expand Down
3 changes: 3 additions & 0 deletions src/Bannerlord.LauncherManager.Native/Bindings.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public override void Write(Utf8JsonWriter writer, IInstallInstruction value, Jso
case CopyInstallInstruction copyInstallInstruction:
JsonSerializer.Serialize(writer, copyInstallInstruction, CustomSourceGenerationContext.CopyInstallInstruction);
break;
case CopyStoreInstallInstruction supportedStoresInstallInstruction:
JsonSerializer.Serialize(writer, supportedStoresInstallInstruction, CustomSourceGenerationContext.CopyStoreInstallInstruction);
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace Bannerlord.LauncherManager.Native;
[JsonSerializable(typeof(InstallResult))]
[JsonSerializable(typeof(ModuleInfoInstallInstruction))]
[JsonSerializable(typeof(CopyInstallInstruction))]
[JsonSerializable(typeof(CopyStoreInstallInstruction))]
[JsonSerializable(typeof(NoneInstallInstruction))]

[JsonSerializable(typeof(SupportedResult))]
Expand Down
101 changes: 76 additions & 25 deletions src/Bannerlord.LauncherManager/LauncherManagerHandler.Installer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public SupportedResult TestModuleContent(string[] files)
return SupportedResult.AsSupported;
}

private sealed record FileWithModuleInfo(string File, string ModuleBasePath, ModuleInfoExtendedWithMetadata ModuleInfo);

/// <summary>
/// External<br/>
/// Two ways to handle installation:<br/>
Expand All @@ -42,45 +44,94 @@ public SupportedResult TestModuleContent(string[] files)
/// </summary>
public InstallResult InstallModuleContent(string[] files, ModuleInfoExtendedWithMetadata[] moduleInfos)
{
var installPath = GetInstallPath();
var platform = GetPlatform(installPath, _store ??= GetStore(installPath));

var lowerSubModuleName = Constants.SubModuleName.ToLower();
if (!files.Any(x => x.ToLower().Contains(lowerSubModuleName)))
return InstallResult.AsInvalid;

files = files.Where(x => !EndsInDirectorySeparator(x)).ToArray();

var instructions = files.Where(x => x.EndsWith(Constants.SubModuleName, StringComparison.OrdinalIgnoreCase)).Select(file =>
var filesWithModuleInfos = files.Where(x => x.EndsWith(Constants.SubModuleName, StringComparison.OrdinalIgnoreCase)).SelectMany(file =>
{
var moduleInfo = moduleInfos.FirstOrDefault(x => x.Path == file);
if (moduleInfo is null) return new List<IInstallInstruction>();
if (moduleInfo is null) return new List<FileWithModuleInfo>();

var subModuleFileIdx = file.IndexOf(Constants.SubModuleName, StringComparison.OrdinalIgnoreCase);
var moduleBasePath = file.Substring(0, subModuleFileIdx);
return files.Where(y => y.StartsWith(moduleBasePath)).Where(y =>
return files.Where(y => y.StartsWith(moduleBasePath)).Select(y => new FileWithModuleInfo(y, moduleBasePath, moduleInfo));
}).ToArray();

var moduleInfoInstructions = filesWithModuleInfos.Select(x => x.ModuleInfo).Distinct().Select(x => new ModuleInfoInstallInstruction(x)).ToArray();
var copyInstructions = filesWithModuleInfos.SelectMany(GetCopyInstructions).ToArray();
var copyStoreInstructions = filesWithModuleInfos.SelectMany(GetStoreCopyInstructions).ToArray();

return new InstallResult
{
Instructions = copyInstructions.Concat(copyStoreInstructions).Concat(moduleInfoInstructions).ToList()
};
}

private IEnumerable<IInstallInstruction> GetCopyInstructions(FileWithModuleInfo filesWithModuleInfo)
{
var file = filesWithModuleInfo.File;
var moduleBasePath = filesWithModuleInfo.ModuleBasePath;
var moduleInfo = filesWithModuleInfo.ModuleInfo;

var modulePath = file.Substring(moduleBasePath.Length);
if (modulePath.StartsWith(Constants.BinFolder, StringComparison.OrdinalIgnoreCase)) yield break;

var destination = Path.Combine(Constants.ModulesFolder, moduleInfo.Id, file.Substring(moduleBasePath.Length));
yield return new CopyInstallInstruction
{
ModuleId = moduleInfo.Id,
Source = file,
Destination = destination,
};
}

private IEnumerable<IInstallInstruction> GetStoreCopyInstructions(FileWithModuleInfo filesWithModuleInfo)
{
var file = filesWithModuleInfo.File;
var moduleBasePath = filesWithModuleInfo.ModuleBasePath;
var moduleInfo = filesWithModuleInfo.ModuleInfo;

var modulePath = file.Substring(moduleBasePath.Length);
if (!modulePath.StartsWith(Constants.BinFolder, StringComparison.OrdinalIgnoreCase)) yield break;
var binPath = modulePath.Substring(4);

var destination = Path.Combine(Constants.ModulesFolder, moduleInfo.Id, file.Substring(moduleBasePath.Length));
if (binPath.StartsWith(Constants.Win64Configuration, StringComparison.OrdinalIgnoreCase))
{
yield return new CopyStoreInstallInstruction
{
var modulePath = y.Substring(moduleBasePath.Length);
if (!modulePath.StartsWith(Constants.BinFolder, StringComparison.OrdinalIgnoreCase)) return true;
var binPath = modulePath.Substring(4);
// Avoid unnecessary bin folder copy
return platform switch
{
GamePlatform.Win64 => binPath.StartsWith(Constants.Win64Configuration, StringComparison.OrdinalIgnoreCase),
GamePlatform.Xbox => binPath.StartsWith(Constants.XboxConfiguration, StringComparison.OrdinalIgnoreCase),
_ => true,
};
}).Select(file2 => new CopyInstallInstruction
Store = GameStore.Steam,
ModuleId = moduleInfo.Id,
Source = file,
Destination = destination,
};
yield return new CopyStoreInstallInstruction
{
Store = GameStore.GOG,
ModuleId = moduleInfo.Id,
Source = file2,
Destination = Path.Combine(Constants.ModulesFolder, moduleInfo.Id, file2.Substring(subModuleFileIdx))
}).Concat(new IInstallInstruction[]
Source = file,
Destination = destination,
};
yield return new CopyStoreInstallInstruction
{
new ModuleInfoInstallInstruction(moduleInfo),
}).ToList();
}).SelectMany(x => x).ToList();

return new InstallResult { Instructions = instructions };
Store = GameStore.Epic,
ModuleId = moduleInfo.Id,
Source = file,
Destination = destination,
};
}
else if (binPath.StartsWith(Constants.XboxConfiguration, StringComparison.OrdinalIgnoreCase))
{
yield return new CopyStoreInstallInstruction
{
Store = GameStore.Xbox,
ModuleId = moduleInfo.Id,
Source = file,
Destination = destination,
};
}
}
}
19 changes: 15 additions & 4 deletions test/Bannerlord.LauncherManager.Tests/HandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public void ModuleProvider_GetModules_Test()
fileSystemProvider: new CallbackFileSystemProvider(
readFileContent: Read,
writeFileContent: null!,
readDirectoryFileList: null!,
readDirectoryFileList: directory => Directory.Exists(directory) ? Directory.GetFiles(directory) : null,
readDirectoryList: directory => Directory.Exists(directory) ? Directory.GetDirectories(directory) : null
),
gameInfoProvider: new CallbackGameInfoProvider(
Expand Down Expand Up @@ -303,12 +303,14 @@ public void Handler_InstallModule_Test()
var subModuleFile = "Test\\SubModule.xml";
var win64Dll = $"Test\\bin\\{Constants.Win64Configuration}\\Test.dll";
var xboxDll = $"Test\\bin\\{Constants.XboxConfiguration}\\Test.dll";
var prefab = $"Test\\Prefabs\\prefab.xml";
var files = new[]
{
moduleFolder,
subModuleFile,
win64Dll,
xboxDll,
prefab,
};

var handler = new LauncherManagerHandlerExposer(
Expand Down Expand Up @@ -346,11 +348,20 @@ public void Handler_InstallModule_Test()
var installResult = handler.InstallModuleContent(files, [new(moduleInfo, ModuleProviderType.Default, subModuleFile)]);
Assert.That(installResult, Is.Not.Null);
Assert.That(installResult.Instructions, Is.Not.Null);
Assert.That(installResult.Instructions.Count, Is.EqualTo(3));
Assert.That(installResult.Instructions.Count, Is.EqualTo(7));
Assert.That(installResult.Instructions[0], Is.InstanceOf<CopyInstallInstruction>());
Assert.That(installResult.Instructions[1], Is.InstanceOf<CopyInstallInstruction>());
Assert.That(installResult.Instructions[2], Is.InstanceOf<ModuleInfoInstallInstruction>());
Assert.That(installResult.Instructions[2], Is.InstanceOf<CopyStoreInstallInstruction>());
Assert.That(installResult.Instructions[3], Is.InstanceOf<CopyStoreInstallInstruction>());
Assert.That(installResult.Instructions[4], Is.InstanceOf<CopyStoreInstallInstruction>());
Assert.That(installResult.Instructions[5], Is.InstanceOf<CopyStoreInstallInstruction>());
Assert.That(installResult.Instructions[6], Is.InstanceOf<ModuleInfoInstallInstruction>());
Assert.That(((CopyInstallInstruction) installResult.Instructions[0]).Source, Is.EqualTo(subModuleFile));
Assert.That(((CopyInstallInstruction) installResult.Instructions[1]).Source, Is.EqualTo(win64Dll));
Assert.That(((CopyInstallInstruction) installResult.Instructions[1]).Source, Is.EqualTo(prefab));
Assert.That(((CopyStoreInstallInstruction) installResult.Instructions[2]).Source, Is.EqualTo(win64Dll));
Assert.That(((CopyStoreInstallInstruction) installResult.Instructions[3]).Source, Is.EqualTo(win64Dll));
Assert.That(((CopyStoreInstallInstruction) installResult.Instructions[4]).Source, Is.EqualTo(win64Dll));
Assert.That(((CopyStoreInstallInstruction) installResult.Instructions[5]).Source, Is.EqualTo(xboxDll));
Assert.That(((ModuleInfoInstallInstruction) installResult.Instructions[6]).ModuleInfo, Is.EqualTo(moduleInfo));
}
}

0 comments on commit 3fa9ee6

Please sign in to comment.