/
ZeroInstallClient.cs
164 lines (139 loc) · 6.53 KB
/
ZeroInstallClient.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright Bastian Eicher et al.
// Licensed under the GNU Lesser Public License
using System.Diagnostics;
using System.Xml.Linq;
using NanoByte.Common.Native;
using NanoByte.Common.Streams;
using ZeroInstall.Model.Selection;
namespace ZeroInstall.Client;
/// <summary>
/// Client for invoking Zero Install commands from within other applications.
/// </summary>
public class ZeroInstallClient : IZeroInstallClient
{
private readonly ISubProcess _subProcess;
private readonly ISubProcess? _guiSubProcess;
/// <summary>
/// Creates a new Zero Install client.
/// </summary>
/// <param name="subProcess">Used to launch <c>0install</c> as a child process.</param>
/// <param name="guiSubProcess">Used to launch <c>0install-win</c> as a child process.</param>
internal ZeroInstallClient(ISubProcess subProcess, ISubProcess? guiSubProcess = null)
{
_subProcess = subProcess;
_guiSubProcess = guiSubProcess;
}
/// <summary>
/// Creates a new Zero Install client.
/// </summary>
/// <param name="commandLine">The command-line used to launch <c>0install</c>. Whitespace must be properly escaped.</param>
/// <param name="guiCommandLine">The optional command-line used to launch <c>0install-win</c>. Whitespace must be properly escaped.</param>
public ZeroInstallClient(string commandLine, string? guiCommandLine = null)
: this(
subProcess: new ZeroInstallProcess(commandLine),
guiSubProcess: guiCommandLine?.To(x=> new ZeroInstallProcess(x)))
{}
/// <summary>
/// Creates a Zero Install client by detecting the location of <c>0install</c> using environment variables or the Windows registry.
/// </summary>
public static IZeroInstallClient Detect
=> new ZeroInstallClient(
commandLine: ZeroInstallEnvironment.Cli ?? GetRegistryPath("0install") ?? "0install",
guiCommandLine: ZeroInstallEnvironment.Gui ?? GetRegistryPath("0install-win"));
private static string? GetRegistryPath(string executableName)
{
if (!WindowsUtils.IsWindows) return null;
string? installLocation = RegistryUtils.GetSoftwareString("Zero Install", "InstallLocation");
if (string.IsNullOrEmpty(installLocation)) return null;
string path = Path.Combine(installLocation, $"{executableName}.exe");
return File.Exists(path) ? path.EscapeArgument() : null;
}
/// <inheritdoc/>
public async Task<Selections> SelectAsync(Requirements requirements, bool refresh = false, bool offline = false)
{
var args = new List<string> { "select", "--batch", "--xml" };
if (refresh) args.Add("--refresh");
if (offline) args.Add("--offline");
args.AddRange(requirements.ToCommandLineArgs());
string output = await Task.Run(() => _subProcess.RunAndCapture(args.ToArray()));
return XmlStorage.FromXmlString<Selections>(output);
}
/// <inheritdoc/>
public async Task<Selections> DownloadAsync(Requirements requirements, bool refresh = false)
{
var args = new List<string> { "download", "--batch" };
if (refresh) args.Add("--refresh");
args.AddRange(requirements.ToCommandLineArgs());
if (_guiSubProcess == null)
{
args.Add("--xml");
string output = await Task.Run(() => _subProcess.RunAndCapture(args.ToArray()));
return XmlStorage.FromXmlString<Selections>(output);
}
else
{
args.Add("--background");
await Task.Run(() => _guiSubProcess.Run(args.ToArray()));
return await SelectAsync(requirements, offline: true);
}
}
/// <inheritdoc/>
public void Run(Requirements requirements, bool refresh = false, bool needsTerminal = false, params string[] arguments)
{
var args = new List<string> { "run" };
if (refresh) args.Add("--refresh");
args.Add("--no-wait");
args.AddRange(requirements.ToCommandLineArgs());
args.AddRange(arguments);
var launcher = needsTerminal ? _subProcess : _guiSubProcess ?? _subProcess;
launcher.Start(args.ToArray());
}
/// <inheritdoc/>
public Process RunWithProcess(Requirements requirements, bool refresh = false, bool needsTerminal = false, params string[] arguments)
{
var args = new List<string> { "run" };
if (refresh) args.Add("--refresh");
args.AddRange(requirements.ToCommandLineArgs());
args.AddRange(arguments);
var launcher = needsTerminal ? _subProcess : _guiSubProcess ?? _subProcess;
return launcher.Start(args.ToArray());
}
/// <inheritdoc/>
public async Task<ISet<string>> GetIntegrationAsync(FeedUri uri)
{
string output = await Task.Run(() => _subProcess.RunAndCapture("list-apps", "--batch", "--xml", uri.ToStringRfc()));
const string xmlNamespace = "http://0install.de/schema/desktop-integration/app-list";
return new HashSet<string>(
XElement.Parse(output)
.Descendants(XName.Get("app", xmlNamespace)).SingleOrDefault()
?.Descendants(XName.Get("access-points", xmlNamespace)).SingleOrDefault()
?.Descendants()
.Select(x => x.Name.LocalName)
?? Enumerable.Empty<string>());
}
/// <inheritdoc/>
public async Task IntegrateAsync(FeedUri uri, IEnumerable<string>? add = null, IEnumerable<string>? remove = null)
{
var args = new List<string> { "integrate", "--batch", uri.ToStringRfc() };
void AddToArgs(string option, IEnumerable<string>? elements)
{
if (elements == null) return;
foreach (string category in elements)
{
args.Add(option);
args.Add(category);
}
}
AddToArgs("--add", add);
AddToArgs("--remove", remove);
await Task.Run(() => _subProcess.RunAndCapture(args.ToArray()));
}
/// <inheritdoc/>
public async Task RemoveAsync(FeedUri uri)
=> await Task.Run(() => _subProcess.RunAndCapture("remove", "--batch", uri.ToStringRfc()));
/// <inheritdoc/>
public async Task FetchAsync(Implementation implementation)
=> await Task.Run(() => _subProcess.RunAndCapture(
writer => writer.WriteLineAsync(new Feed { Elements = { implementation } }.ToXmlString().Replace("\n", "")),
"fetch"));
}