diff --git a/src/Client/IProcessLauncher.cs b/src/Client/IProcessLauncher.cs deleted file mode 100644 index 0d2d16fd7..000000000 --- a/src/Client/IProcessLauncher.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Bastian Eicher et al. -// Licensed under the GNU Lesser Public License - -using System.Diagnostics; - -namespace ZeroInstall.Client; - -/// -/// Launches an external process. -/// -internal interface IProcessLauncher -{ - /// - /// Starts a new and runs it in parallel with this one. - /// - /// The newly launched process. - /// There was a problem launching the executable. - /// The executable file could not be found. - /// The target process requires elevation. - Process Start(params string[] args); - - /// - /// Starts a new and waits for it to complete. - /// - /// The exit code of the child process. - /// There was a problem launching the executable. - /// The executable file could not be found. - /// The target process requires elevation. - int Run(params string[] args); -} diff --git a/src/Client/ProcessLauncher.cs b/src/Client/ProcessLauncher.cs deleted file mode 100644 index 8ccaba5b2..000000000 --- a/src/Client/ProcessLauncher.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Bastian Eicher et al. -// Licensed under the GNU Lesser Public License - -using System.Diagnostics; - -namespace ZeroInstall.Client; - -/// -/// Launches an external process. -/// -[PrimaryConstructor] -internal partial class ProcessLauncher : IProcessLauncher -{ - private readonly string _commandLine; - - /// - public Process Start(params string[] args) - => ProcessUtils.FromCommandLine(_commandLine + " " + args.JoinEscapeArguments()).Start(); - - /// - public int Run(params string[] args) - => ProcessUtils.FromCommandLine(_commandLine + " " + args.JoinEscapeArguments()).Run(); -} diff --git a/src/Client/ZeroInstallClient.cs b/src/Client/ZeroInstallClient.cs index 7fc981adb..b060fdc2f 100644 --- a/src/Client/ZeroInstallClient.cs +++ b/src/Client/ZeroInstallClient.cs @@ -15,20 +15,17 @@ namespace ZeroInstall.Client; public class ZeroInstallClient : IZeroInstallClient { private readonly ISubProcess _subProcess; - private readonly IProcessLauncher _launcher; - private readonly IProcessLauncher? _guiLauncher; + private readonly ISubProcess? _guiSubProcess; /// /// Creates a new Zero Install client. /// - /// Used to launch 0install, captures its output and waits until it has terminated. - /// Used to launch 0install as ane external process. - /// Used to launch 0install-win as ane external process. - internal ZeroInstallClient(ISubProcess subProcess, IProcessLauncher launcher, IProcessLauncher? guiLauncher = null) + /// Used to launch 0install as a child process. + /// Used to launch 0install-win as a child process. + internal ZeroInstallClient(ISubProcess subProcess, ISubProcess? guiSubProcess = null) { _subProcess = subProcess; - _launcher = launcher; - _guiLauncher = guiLauncher; + _guiSubProcess = guiSubProcess; } /// @@ -38,9 +35,8 @@ internal ZeroInstallClient(ISubProcess subProcess, IProcessLauncher launcher, IP /// The optional command-line used to launch 0install-win. Whitespace must be properly escaped. public ZeroInstallClient(string commandLine, string? guiCommandLine = null) : this( - subProcess: new SubProcess(ProcessUtils.FromCommandLine(commandLine)), - launcher: new ProcessLauncher(commandLine), - guiLauncher: guiCommandLine?.To(x=> new ProcessLauncher(x))) + subProcess: new ZeroInstallProcess(commandLine), + guiSubProcess: guiCommandLine?.To(x=> new ZeroInstallProcess(x))) {} /// @@ -81,7 +77,7 @@ public async Task DownloadAsync(Requirements requirements, bool refr if (refresh) args.Add("--refresh"); args.AddRange(requirements.ToCommandLineArgs()); - if (_guiLauncher == null) + if (_guiSubProcess == null) { args.Add("--xml"); string output = await Task.Run(() => _subProcess.RunAndCapture(args.ToArray())); @@ -90,7 +86,7 @@ public async Task DownloadAsync(Requirements requirements, bool refr else { args.Add("--background"); - await Task.Run(() => _guiLauncher.Run(args.ToArray())); + await Task.Run(() => _guiSubProcess.Run(args.ToArray())); return await SelectAsync(requirements, offline: true); } } @@ -104,10 +100,8 @@ public void Run(Requirements requirements, bool refresh = false, bool needsTermi args.AddRange(requirements.ToCommandLineArgs()); args.AddRange(arguments); - if (needsTerminal || _guiLauncher == null) - _launcher.Start(args.ToArray()); - else - _guiLauncher.Start(args.ToArray()); + var launcher = needsTerminal ? _subProcess : _guiSubProcess ?? _subProcess; + launcher.Start(args.ToArray()); } /// @@ -118,9 +112,8 @@ public Process RunWithProcess(Requirements requirements, bool refresh = false, b args.AddRange(requirements.ToCommandLineArgs()); args.AddRange(arguments); - return needsTerminal || _guiLauncher == null - ? _launcher.Start(args.ToArray()) - : _guiLauncher.Start(args.ToArray()); + var launcher = needsTerminal ? _subProcess : _guiSubProcess ?? _subProcess; + return launcher.Start(args.ToArray()); } /// diff --git a/src/Client/ZeroInstallProcess.cs b/src/Client/ZeroInstallProcess.cs new file mode 100644 index 000000000..d4d379125 --- /dev/null +++ b/src/Client/ZeroInstallProcess.cs @@ -0,0 +1,36 @@ +// Copyright Bastian Eicher et al. +// Licensed under the GNU Lesser Public License + +using NanoByte.Common.Streams; + +namespace ZeroInstall.Client; + +/// +/// Runs Zero Install as a sub/child process. +/// +internal class ZeroInstallProcess : SubProcess +{ + public ZeroInstallProcess(string commandLine) + : base(ProcessUtils.FromCommandLine(commandLine)) + {} + + protected override void HandleExitCode(int exitCode) + { + switch (exitCode) + { + case 1: // No changes + break; + case 10: // Web error + throw new WebException(); + case 11: // Access denied + throw new UnauthorizedAccessException(); + case 12: // IO error + throw new IOException(); + case 100: // User canceled + throw new OperationCanceledException(); + default: + base.HandleExitCode(exitCode); + return; + } + } +} diff --git a/src/UnitTests/Client/ZeroInstallClientGuiTest.cs b/src/UnitTests/Client/ZeroInstallClientGuiTest.cs index a2775ae54..e59703630 100644 --- a/src/UnitTests/Client/ZeroInstallClientGuiTest.cs +++ b/src/UnitTests/Client/ZeroInstallClientGuiTest.cs @@ -12,15 +12,14 @@ namespace ZeroInstall.Client; /// public class ZeroInstallClientGuiTest : TestWithMocks { - private readonly Mock _subProcessMock; - private readonly Mock _guiLauncherMock; + private readonly Mock _subProcessMock, _guiSubProcessMock; private readonly ZeroInstallClient _client; public ZeroInstallClientGuiTest() { _subProcessMock = CreateMock(); - _guiLauncherMock = CreateMock(); - _client = new(_subProcessMock.Object, CreateMock().Object, _guiLauncherMock.Object); + _guiSubProcessMock = CreateMock(); + _client = new(_subProcessMock.Object, _guiSubProcessMock.Object); } [Fact] @@ -28,8 +27,7 @@ public async Task Download() { var selections = SelectionsTest.CreateTestSelections(); - _guiLauncherMock.Setup(x => x.Run("download", "--batch", "--refresh", FeedTest.Test1Uri.ToStringRfc(), "--background")) - .Returns(0); + _guiSubProcessMock.Setup(x => x.Run("download", "--batch", "--refresh", FeedTest.Test1Uri.ToStringRfc(), "--background")); _subProcessMock.Setup(x => x.RunAndCapture(null, "select", "--batch", "--xml", "--offline", FeedTest.Test1Uri.ToStringRfc())) .Returns(selections.ToXmlString()); @@ -41,18 +39,18 @@ public async Task Download() [Fact] public void Run() { - _guiLauncherMock.Setup(x => x.Start("run", "--no-wait", FeedTest.Test1Uri.ToStringRfc(), "--arg")) - .Returns(new Process()); + _guiSubProcessMock.Setup(x => x.Start("run", "--no-wait", FeedTest.Test1Uri.ToStringRfc(), "--arg")) + .Returns(new Process()); _client.Run(FeedTest.Test1Uri, arguments: "--arg"); } [Fact] - public void RunAndWait() + public void RunWithProcess() { var process = new Process(); - _guiLauncherMock.Setup(x => x.Start("run", FeedTest.Test1Uri.ToStringRfc(), "--arg")) - .Returns(process); + _guiSubProcessMock.Setup(x => x.Start("run", FeedTest.Test1Uri.ToStringRfc(), "--arg")) + .Returns(process); _client.RunWithProcess(FeedTest.Test1Uri, arguments: "--arg") .Should().BeSameAs(process); diff --git a/src/UnitTests/Client/ZeroInstallClientTest.cs b/src/UnitTests/Client/ZeroInstallClientTest.cs index e9f99b91a..89080079e 100644 --- a/src/UnitTests/Client/ZeroInstallClientTest.cs +++ b/src/UnitTests/Client/ZeroInstallClientTest.cs @@ -14,14 +14,12 @@ namespace ZeroInstall.Client; public class ZeroInstallClientTest : TestWithMocks { private readonly Mock _subProcessMock; - private readonly Mock _launcherMock; private readonly ZeroInstallClient _client; public ZeroInstallClientTest() { _subProcessMock = CreateMock(); - _launcherMock = CreateMock(); - _client = new(_subProcessMock.Object, _launcherMock.Object); + _client = new(_subProcessMock.Object); } [Fact] @@ -51,18 +49,18 @@ public async Task Download() [Fact] public void Run() { - _launcherMock.Setup(x => x.Start("run", "--no-wait", FeedTest.Test1Uri.ToStringRfc(), "--arg")) - .Returns(new Process()); + _subProcessMock.Setup(x => x.Start("run", "--no-wait", FeedTest.Test1Uri.ToStringRfc(), "--arg")) + .Returns(new Process()); _client.Run(FeedTest.Test1Uri, arguments: "--arg"); } [Fact] - public void RunAndWait() + public void RunWithProcess() { var process = new Process(); - _launcherMock.Setup(x => x.Start("run", FeedTest.Test1Uri.ToStringRfc(), "--arg")) - .Returns(process); + _subProcessMock.Setup(x => x.Start("run", FeedTest.Test1Uri.ToStringRfc(), "--arg")) + .Returns(process); _client.RunWithProcess(FeedTest.Test1Uri, arguments: "--arg") .Should().BeSameAs(process);