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
75 changes: 0 additions & 75 deletions API/Choco/Choco.cs

This file was deleted.

42 changes: 42 additions & 0 deletions API/Choco/ChocoCommunityWebClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Chocolatey.Models;
using Flurl;
using Flurl.Http.Xml;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using NuGet.Versioning;

namespace Chocolatey;

public class ChocoCommunityWebClient : IChocoSearchService
{
public async Task<IReadOnlyList<Package>> SearchAsync(string query, string targetFramework = "", bool includePrerelease = false, int top = 30, int skip = 0)
{
var doc = await Constants.CHOCOLATEY_API_HOST.AppendPathSegment("Search()")
.SetQueryParam("$filter", "IsLatestVersion").SetQueryParam("$top", top).SetQueryParam("$skip", skip)
.SetQueryParam("searchTerm", "'" + query + "'")
.SetQueryParam("targetFramework", "'" + targetFramework + "'")
.SetQueryParam("includePrerelease", includePrerelease.ToLowerString())
.GetXDocumentAsync();
IEnumerable<XElement> entries = doc.Root.Elements().Where(elem => elem.Name.LocalName == "entry");

// Parse Atom entries into Package model
return entries.Select(entry => new Package(entry)).ToList();
}

public async Task<Package> GetPackageAsync(string id, NuGetVersion version)
{
var entry = await Constants.CHOCOLATEY_API_HOST.AppendPathSegment($"Packages(Id='{id}',Version='{version}')")
.GetXDocumentAsync();
return new Package(entry.Root);
}

public async Task<string> GetPackagePropertyAsync(string id, NuGetVersion version, string propertyName)
{
var entry = await Constants.CHOCOLATEY_API_HOST.AppendPathSegment($"Packages(Id='{id}',Version='{version}')")
.AppendPathSegment(propertyName)
.GetXDocumentAsync();
return entry.Root.Value;
}
}
2 changes: 2 additions & 0 deletions API/Choco/Chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CliWrap" Version="3.8.2" />
<PackageReference Include="Flurl.Http" Version="4.0.2" />
<PackageReference Include="Flurl.Http.Xml" Version="4.0.0" />
<PackageReference Include="NuGet.Versioning" Version="6.13.2" />
</ItemGroup>

</Project>
89 changes: 89 additions & 0 deletions API/Choco/Cli/ChocoAdminCliClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Chocolatey.Models;
using NuGet.Versioning;

namespace Chocolatey.Cli;

public class ChocoAdminCliClient : IChocoPackageService
{
public bool NoOp { get; set; } = false;

public async Task<bool> InstallAsync(string id, NuGetVersion? version = null, IProgress<PackageProgress>? progress = null)
{
return await Task.Run(delegate
{
List<string> args = ["install", id];

ChocoArgumentsBuilder argBuilder = new()
{
Version = version,
Yes = true,
LimitOutput = true,
NoOp = NoOp
};

argBuilder.Build(args);

var process = RunChocoAsAdmin(args);

return process.ExitCode is 0;
});
}

public IAsyncEnumerable<(string Id, NuGetVersion Version)> ListAsync()
{
throw new NotImplementedException();
}

public async Task<bool> UninstallAsync(string id, NuGetVersion? version = null, IProgress<PackageProgress>? progress = null)
{
return await Task.Run(delegate
{
List<string> args = ["uninstall", id];

ChocoArgumentsBuilder argBuilder = new()
{
Version = version,
Yes = true,
LimitOutput = true,
NoOp = NoOp
};

argBuilder.Build(args);

var process = RunChocoAsAdmin(args);

return process.ExitCode is 0;
});
}

public Task<bool> UpgradeAllAsync(IProgress<PackageProgress>? progress = null)
{
throw new NotImplementedException();
}

public Task<bool> UpgradeAsync(string id, IProgress<PackageProgress>? progress = null)
{
throw new NotImplementedException();
}

private Process RunChocoAsAdmin(List<string> args)
{
ProcessStartInfo info = new(ChocoArgumentsBuilder.CHOCO_EXE)
{
Arguments = string.Join(" ", args),

// Required to run as admin
UseShellExecute = true,
Verb = "runas",
};

var process = Process.Start(info);
process.WaitForExit();

return process;
}
}
33 changes: 33 additions & 0 deletions API/Choco/Cli/ChocoArgumentsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using NuGet.Versioning;

namespace Chocolatey.Cli;

internal class ChocoArgumentsBuilder
{
public const string CHOCO_EXE = "choco";

public NuGetVersion? Version { get; set; }
public bool Yes { get; set; }
public bool LimitOutput { get; set; }
public bool NoOp { get; set; }

public void Build(List<string> args) => args.AddRange(Build());

[Pure]
public IEnumerable<string> Build()
{
if (Version is not null)
yield return $"--version=\"'{Version}'\"";

if (Yes)
yield return "-y";

if (LimitOutput)
yield return "--limit-output";

if (NoOp)
yield return "--noop";
}
}
131 changes: 131 additions & 0 deletions API/Choco/Cli/ChocoCliClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Chocolatey.Models;
using CliWrap;
using NuGet.Versioning;

namespace Chocolatey.Cli;

public partial class ChocoCliClient : IChocoPackageService
{
private static readonly Regex _rxProgress = new(@"Progress: Downloading (?<id>[\w.\-_]+) (?<ver>\d+(\.\d+){0,3}(-[\w\d]+)?)\.\.\. (?<prog>\d{1,3})%", RegexOptions.Compiled);

public bool NoOp { get; set; } = false;

public async Task<bool> InstallAsync(string id, NuGetVersion? version = null, IProgress<PackageProgress>? progress = null)
{
List<string> args = ["install", id];

ChocoArgumentsBuilder argBuilder = new()
{
Version = version,
Yes = true,
LimitOutput = true,
NoOp = NoOp
};

argBuilder.Build(args);

var result = await CliWrap.Cli.Wrap(ChocoArgumentsBuilder.CHOCO_EXE)
.WithArguments(args)
.WithStandardOutputPipe(PipeTarget.ToDelegate(HandleStdOut))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync();

return result.ExitCode is 0;

void HandleStdOut(string line)
{
if (progress is null)
return;

var match = _rxProgress.Match(line);
if (!match.Success)
return;

var id = match.Groups["id"].Value;
var version = NuGetVersion.Parse(match.Groups["ver"].Value);
var percentage = int.Parse(match.Groups["prog"].Value);
progress.Report(new(id, version, percentage));
}
}

public async Task<bool> UninstallAsync(string id, NuGetVersion? version = null, IProgress<PackageProgress>? progress = null)
{
List<string> args = ["uninstall", id];

ChocoArgumentsBuilder argBuilder = new()
{
Version = version,
Yes = true,
LimitOutput = true,
NoOp = NoOp
};

argBuilder.Build(args);

var result = await CliWrap.Cli.Wrap(ChocoArgumentsBuilder.CHOCO_EXE)
.WithArguments(args)
.WithStandardOutputPipe(PipeTarget.ToDelegate(HandleStdOut))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync();

return result.ExitCode is 0;

void HandleStdOut(string line)
{
if (progress is null)
return;

var match = _rxProgress.Match(line);
if (!match.Success)
return;

var id = match.Groups["id"].Value;
var version = NuGetVersion.Parse(match.Groups["ver"].Value);
var percentage = int.Parse(match.Groups["prog"].Value);
progress.Report(new(id, version, percentage));
}
}

public async Task<bool> UpgradeAllAsync(IProgress<PackageProgress>? progress = null)
{
throw new NotImplementedException();
}

public async Task<bool> UpgradeAsync(string id, IProgress<PackageProgress>? progress = null)
{
throw new NotImplementedException();
}

public async IAsyncEnumerable<(string Id, NuGetVersion Version)> ListAsync()
{
ChocoArgumentsBuilder argBuilder = new()
{
LimitOutput = true,
};

argBuilder.Build(["list"]);

List<(string, NuGetVersion)> installedPackages = [];

var result = await CliWrap.Cli.Wrap(ChocoArgumentsBuilder.CHOCO_EXE)
.WithArguments((List<string>)["list"])
.WithStandardOutputPipe(PipeTarget.ToDelegate(HandleStdOut))
.WithValidation(CommandResultValidation.None)
.ExecuteAsync();

foreach (var p in installedPackages)
yield return p;

void HandleStdOut(string line)
{
int separatorIdx = line.IndexOf('|');
var id = line.Substring(0, separatorIdx).Trim();
var version = NuGetVersion.Parse(line.Substring(separatorIdx + 1).Trim());
installedPackages.Add((id, version));
}
}
}
3 changes: 2 additions & 1 deletion API/Choco/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ namespace Chocolatey
{
public static class Constants
{
private const string CHOCOLATEY_API_BASE = "community.chocolatey.org/api/v2";
private const string CHOCOLATEY_DOMAIN = "chocolatey.org";
private const string CHOCOLATEY_API_BASE = CHOCOLATEY_DOMAIN + "/api/v2";
public const string CHOCOLATEY_API_HOST = "https://" + CHOCOLATEY_API_BASE;

public static readonly XNamespace XMLNS_ATOM = "http://www.w3.org/2005/Atom";
Expand Down
Loading