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
84 changes: 73 additions & 11 deletions WheelWizard.Test/Features/LinuxDolphinInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,29 @@ public LinuxDolphinInstallerTests()
}

[Fact]
public void IsDolphinInstalledInFlatpak_ReturnsTrue_WhenFlatpakInfoExitCodeIsZero()
public void IsDolphinInstalledInFlatpak_ReturnsTrue_WhenFlatpakListHasDolphin()
{
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(0));
_processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _).Returns(Ok(0)).AndDoes(callInfo => callInfo[2] = "Application ID\norg.DolphinEmu.dolphin-emu\n");

var result = _installer.IsDolphinInstalledInFlatpak();

Assert.True(result);
}

[Fact]
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakInfoExitCodeIsNonZero()
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakListHasNoDolphin()
{
_processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu").Returns(Ok(1));
_processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _).Returns(Ok(0)).AndDoes(callInfo => callInfo[2] = "Application ID\n");

var result = _installer.IsDolphinInstalledInFlatpak();

Assert.False(result);
}

[Fact]
public void IsDolphinInstalledInFlatpak_ReturnsFalse_WhenFlatpakListFails()
{
_processService.Run("flatpak", "list --app --columns=application", out _, out _).Returns(Ok(1));

var result = _installer.IsDolphinInstalledInFlatpak();

Expand Down Expand Up @@ -77,13 +87,53 @@ public async Task InstallFlatpak_ReturnsSuccess_WhenInstallCompletesAndCommandBe
Assert.True(result.IsSuccess);
}

[Fact]
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenDolphinRemoteCommandFails()
{
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
_processService
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
.Returns(Ok(1));
_processService
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
.Returns(Ok(0));

var result = await _installer.InstallFlatpakDolphin();

Assert.True(result.IsFailure);
Assert.Contains("exit code 1", result.Error.Message);
}

[Fact]
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenFlathubRemoteCommandFails()
{
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
_processService
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
.Returns(Ok(0));
_processService
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
.Returns(Ok(1));

var result = await _installer.InstallFlatpakDolphin();

Assert.True(result.IsFailure);
Assert.Contains("exit code 1", result.Error.Message);
}

[Fact]
public async Task InstallFlatpakDolphin_ReturnsFailure_WhenDolphinInstallCommandFails()
{
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
_processService
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult<OperationResult<int>>(Ok(1)));
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
.Returns(Ok(0));
_processService
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
.Returns(Ok(0));
_processService
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult(Ok(1)));

var result = await _installer.InstallFlatpakDolphin();

Expand All @@ -96,8 +146,14 @@ public async Task InstallFlatpakDolphin_ReturnsFailure_WhenWarmupLaunchFails()
{
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
_processService
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
.Returns(Ok(0));
_processService
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
.Returns(Ok(0));
_processService
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult(Ok(0)));
_processService
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
.Returns(Task.FromResult<OperationResult>(Fail("Launch failed")));
Expand All @@ -113,11 +169,17 @@ public async Task InstallFlatpakDolphin_ReturnsSuccess_WhenInstallAndWarmupSucce
{
_commandEnvironment.IsCommandAvailable("flatpak").Returns(true);
_processService
.RunWithProgressAsync("pkexec", "flatpak --system install -y org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult<OperationResult<int>>(Ok(0)));
.Run("flatpak", "remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo")
.Returns(Ok(0));
_processService
.Run("flatpak", "remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
.Returns(Ok(0));
_processService
.RunWithProgressAsync("flatpak", "install --user -y dolphin org.DolphinEmu.dolphin-emu", Arg.Any<IProgress<int>?>())
.Returns(Task.FromResult(Ok(0)));
_processService
.LaunchAndStopAsync("flatpak", "run org.DolphinEmu.dolphin-emu", TimeSpan.FromSeconds(4))
.Returns(Task.FromResult<OperationResult>(Ok()));
.Returns(Task.FromResult(Ok()));

var result = await _installer.InstallFlatpakDolphin();

Expand Down
23 changes: 10 additions & 13 deletions WheelWizard.Test/Features/Settings/SettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ public class SettingsManagerTests
[Fact]
public void Get_Throws_WhenRequestedTypeDoesNotMatchSettingType()
{
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
var manager = CreateManager(new MockFileSystem(), out _, out _);

Assert.Throws<InvalidOperationException>(() => manager.Get<int>(manager.WW_LANGUAGE));
}

[Fact]
public void Set_Throws_WhenProvidedValueIsNull()
{
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
var manager = CreateManager(new MockFileSystem(), out _, out _);

Assert.Throws<ArgumentNullException>(() => manager.Set<string>(manager.WW_LANGUAGE, null!));
}

[Fact]
public void Set_ReturnsFalse_WhenValidationFails()
{
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
var manager = CreateManager(new MockFileSystem(), out _, out _);

var result = manager.Set(manager.FOCUSED_USER, 99, skipSave: true);

Expand All @@ -49,7 +49,7 @@ public void Set_ReturnsFalse_WhenValidationFails()
[InlineData(2.01)]
public void SavedWindowScale_RejectsValuesOutsideBounds(double scale)
{
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
var manager = CreateManager(new MockFileSystem(), out _, out _);

var result = manager.Set(manager.SAVED_WINDOW_SCALE, scale, skipSave: true);

Expand All @@ -62,7 +62,7 @@ public void SavedWindowScale_RejectsValuesOutsideBounds(double scale)
[InlineData(2.01)]
public void WindowScalePreview_RejectsValuesOutsideBounds(double scale)
{
var manager = CreateManager(new MockFileSystem(), out _, out _, out _);
var manager = CreateManager(new MockFileSystem(), out _, out _);

var result = manager.Set(manager.WINDOW_SCALE, scale, skipSave: true);

Expand All @@ -73,7 +73,7 @@ public void WindowScalePreview_RejectsValuesOutsideBounds(double scale)
[Fact]
public void ValidateCorePathSettings_ReturnsAllExpectedIssues_WhenDefaultsAreInvalid()
{
var manager = CreateManager(new RealFileSystem(), out _, out _, out _);
var manager = CreateManager(new RealFileSystem(), out _, out _);
#pragma warning disable CS0618
SettingsRuntime.Initialize(manager);
#pragma warning restore CS0618
Expand All @@ -91,7 +91,7 @@ public void ValidateCorePathSettings_ReturnsAllExpectedIssues_WhenDefaultsAreInv
public void PathsSetupCorrectly_ReturnsTrue_WhenCorePathsAreValid()
{
var fileSystem = new MockFileSystem();
var manager = CreateManager(fileSystem, out _, out _, out _);
var manager = CreateManager(fileSystem, out _, out _);
var userFolderPath = $"/wheelwizard-user-{Guid.NewGuid():N}";
var gameFilePath = Path.Combine(userFolderPath, "game.iso");
var dolphinLocation = SettingsTestUtils.GetValidDolphinLocation(fileSystem);
Expand All @@ -110,7 +110,7 @@ public void PathsSetupCorrectly_ReturnsTrue_WhenCorePathsAreValid()
[Fact]
public void LoadSettings_CallsUnderlyingManagersOnlyOnce()
{
var manager = CreateManager(new MockFileSystem(), out var whWzManager, out var dolphinManager, out _);
var manager = CreateManager(new MockFileSystem(), out var whWzManager, out var dolphinManager);

manager.LoadSettings();
manager.LoadSettings();
Expand All @@ -122,16 +122,13 @@ public void LoadSettings_CallsUnderlyingManagersOnlyOnce()
private static SettingsManager CreateManager(
IFileSystem fileSystem,
out IWhWzSettingManager whWzSettingManager,
out IDolphinSettingManager dolphinSettingManager,
out ILinuxDolphinInstaller linuxDolphinInstaller
out IDolphinSettingManager dolphinSettingManager
)
{
whWzSettingManager = Substitute.For<IWhWzSettingManager>();
dolphinSettingManager = Substitute.For<IDolphinSettingManager>();
linuxDolphinInstaller = Substitute.For<ILinuxDolphinInstaller>();
linuxDolphinInstaller.IsDolphinInstalledInFlatpak().Returns(true);

return new SettingsManager(whWzSettingManager, dolphinSettingManager, linuxDolphinInstaller, fileSystem);
return new SettingsManager(whWzSettingManager, dolphinSettingManager, fileSystem);
}
}

Expand Down
42 changes: 34 additions & 8 deletions WheelWizard/Features/DolphinInstaller/LinuxDolphinInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ public sealed class LinuxDolphinInstaller(ILinuxCommandEnvironment commandEnviro
{
public bool IsDolphinInstalledInFlatpak()
{
var processResult = processService.Run("flatpak", "info org.DolphinEmu.dolphin-emu");
return processResult.IsSuccess && processResult.Value == 0;
const string dolphinAppId = "org.DolphinEmu.dolphin-emu";
var processResult = processService.Run("flatpak", "list --app --columns=application", out var stdOut, out _);

return processResult.IsSuccess && processResult.Value == 0 && stdOut
.Split('\n')
.Any(line => line == dolphinAppId);
}

public bool IsDolphinInstalledNative()
{
return commandEnvironment.IsCommandAvailable("dolphin-emu");
if (!commandEnvironment.IsCommandAvailable("dolphin-emu"))
{
return false;
}
var processResult = processService.Run("dolphin-emu", "--version");
return processResult.IsSuccess && processResult.Value == 0;
}

public bool IsFlatpakInstalled()
Expand Down Expand Up @@ -62,17 +71,34 @@ public async Task<OperationResult> InstallFlatpakDolphin(IProgress<int>? progres
return installFlatpakResult;
}

var addRemoteResult = processService.Run(
"flatpak",
"remote-add --if-not-exists --user dolphin https://flatpak.dolphin-emu.org/releases.flatpakrepo"
);
if (addRemoteResult.IsFailure)
return addRemoteResult.Error;

if (addRemoteResult.Value != 0)
return Fail($"Adding the Dolphin Flatpak remote failed with exit code {addRemoteResult.Value}.");

addRemoteResult = processService.Run(
"flatpak",
"remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo"
);
if (addRemoteResult.IsFailure)
return addRemoteResult.Error;

if (addRemoteResult.Value != 0)
return Fail($"Adding the Flathub Flatpak remote failed with exit code {addRemoteResult.Value}.");

var installDolphinResult = await processService.RunWithProgressAsync(
"pkexec",
"flatpak --system install -y org.DolphinEmu.dolphin-emu",
"flatpak",
"install --user -y dolphin org.DolphinEmu.dolphin-emu",
progress
);
if (installDolphinResult.IsFailure)
return installDolphinResult.Error;

if (installDolphinResult.Value is 126 or 127)
return Fail("You need to be an administrator to install Dolphin via Flatpak.");

if (installDolphinResult.Value != 0)
return Fail($"Dolphin installation failed with exit code {installDolphinResult.Value}.");

Expand Down
18 changes: 16 additions & 2 deletions WheelWizard/Features/DolphinInstaller/LinuxProcessService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ namespace WheelWizard.DolphinInstaller;

public interface ILinuxProcessService
{
OperationResult<int> Run(string fileName, string arguments, out string stdOut, out string stdErr);
OperationResult<int> Run(string fileName, string arguments);
Task<OperationResult<int>> RunWithProgressAsync(string fileName, string arguments, IProgress<int>? progress = null);
Task<OperationResult> LaunchAndStopAsync(string fileName, string arguments, TimeSpan duration);
}

public sealed class LinuxProcessService : ILinuxProcessService
{
public OperationResult<int> Run(string fileName, string arguments)
public OperationResult<int> Run(string fileName, string arguments, out string stdOut, out string stdErr)
{
return TryCatch(
var localStdOut = "";
var localStdErr = "";
var result = TryCatch(
() =>
{
var processInfo = new ProcessStartInfo
Expand All @@ -31,11 +34,22 @@ public OperationResult<int> Run(string fileName, string arguments)
if (process == null)
return -1;

localStdOut = process.StandardOutput.ReadToEnd();
localStdErr = process.StandardError.ReadToEnd();
process.WaitForExit();
return process.ExitCode;
},
$"Failed to run process: {fileName} {arguments}"
);

stdOut = localStdOut;
stdErr = localStdErr;
return result;
}

public OperationResult<int> Run(string fileName, string arguments)
{
return Run(fileName, arguments, out _, out _);
}

public async Task<OperationResult<int>> RunWithProgressAsync(string fileName, string arguments, IProgress<int>? progress = null)
Expand Down
11 changes: 1 addition & 10 deletions WheelWizard/Features/Settings/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class SettingsManager : ISettingsManager
{
private readonly IWhWzSettingManager _whWzSettingManager;
private readonly IDolphinSettingManager _dolphinSettingManager;
private readonly ILinuxDolphinInstaller _linuxDolphinInstaller;
private readonly IFileSystem _fileSystem;

private readonly Setting _dolphinCompilationMode;
Expand All @@ -27,13 +26,11 @@ public class SettingsManager : ISettingsManager
public SettingsManager(
IWhWzSettingManager whWzSettingManager,
IDolphinSettingManager dolphinSettingManager,
ILinuxDolphinInstaller linuxDolphinInstaller,
IFileSystem fileSystem
)
{
_whWzSettingManager = whWzSettingManager;
_dolphinSettingManager = dolphinSettingManager;
_linuxDolphinInstaller = linuxDolphinInstaller;
_fileSystem = fileSystem;

#region WhWz settings
Expand All @@ -48,12 +45,6 @@ IFileSystem fileSystem

if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return EnvHelper.IsValidUnixCommand(pathOrCommand);

if (PathManager.IsFlatpakDolphinFilePath(pathOrCommand) && !_linuxDolphinInstaller.IsDolphinInstalledInFlatpak())
return false;

return EnvHelper.IsValidUnixCommand(pathOrCommand);
}

Expand Down Expand Up @@ -99,7 +90,7 @@ IFileSystem fileSystem
return false;

// `~/.dolphin-emu` would be used if it exists
if (!PathManager.IsFlatpakDolphinFilePath() && _fileSystem.Directory.Exists(PathManager.LinuxDolphinLegacyFolderPath))
if (!PathManager.IsFlatpakDolphinFilePath(dolphinLocation) && _fileSystem.Directory.Exists(PathManager.LinuxDolphinLegacyFolderPath))
return false;

return true;
Expand Down
8 changes: 5 additions & 3 deletions WheelWizard/Resources/Languages/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ cs:
rooms: "Místnosti"
miis: "Miiy"
text:
made_by_string: "Vytvořili: {$1} \\n a {$2}"
made_by_string: |
Vytvořili: {$1}
a {$2}
thanks_translators: "Díky moc všem překladatelům:"
wh_wz_translation_percentage: "Překlady pro tento jazyk jsou z {$1}% kompletní"
powered_gamebanana: "Poháněno společností GameBanana"
Expand Down Expand Up @@ -241,10 +243,10 @@ cs:
rr_to_old:
extra: "Tvoje verze Retro Rewind je moc stará na aktualizaci. Chtěl bys znovu nainstalovat Retro Rewind?"
title: "Verze Retro Rewind je moc stará."
rr_not_determent:
rr_not_determined:
title: "Stáhnout Retro Rewind"
extra: "Tvojí verzi Retro Rewind se nepodařilo určit. Chtěl bys si stáhnout Retro Rewind?"
dolphin_flatpack:
dolphin_flatpak:
title: "Instalace Dolphin Flatpak"
install_mod:
title: "Chceš stáhnout a nainstalovat mod: {$1}?"
Expand Down
Loading
Loading