Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Removed OctoKit dependency and updated file downloaders to support au…
Browse files Browse the repository at this point in the history
…thorization headers
  • Loading branch information
caesay committed Feb 3, 2022
1 parent 80754f1 commit b64d85d
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 210 deletions.
62 changes: 32 additions & 30 deletions src/Squirrel/FileDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ public interface IFileDownloader
/// <param name="progress">
/// A delegate for reporting download progress, with expected values from 0-100.
/// </param>
Task DownloadFile(string url, string targetFile, Action<int> progress);
/// <param name="authorization">
/// Text to be sent in the 'Authorization' header of the request.
/// </param>
Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null);

/// <summary>
/// Returns a byte array containing the contents of the file at the specified url
/// </summary>
Task<byte[]> DownloadBytes(string url);
Task<byte[]> DownloadBytes(string url, string authorization = null);

/// <summary>
/// Returns a string containing the contents of the specified url
/// </summary>
Task<string> DownloadString(string url);
Task<string> DownloadString(string url, string authorization = null);
}

/// <inheritdoc cref="IFileDownloader"/>
Expand All @@ -46,9 +49,9 @@ public class HttpClientFileDownloader : IFileDownloader
public static ProductInfoHeaderValue UserAgent => new("Squirrel", AssemblyRuntimeInfo.ExecutingAssemblyName.Version.ToString());

/// <inheritdoc />
public virtual async Task DownloadFile(string url, string targetFile, Action<int> progress)
public virtual async Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization)
{
using var client = CreateHttpClient();
using var client = CreateHttpClient(authorization);
try {
using (var fs = File.Open(targetFile, FileMode.Create)) {
await DownloadToStreamInternal(client, url, fs, progress).ConfigureAwait(false);
Expand All @@ -63,9 +66,9 @@ public virtual async Task DownloadFile(string url, string targetFile, Action<int
}

/// <inheritdoc />
public virtual async Task<byte[]> DownloadBytes(string url)
public virtual async Task<byte[]> DownloadBytes(string url, string authorization)
{
using var client = CreateHttpClient();
using var client = CreateHttpClient(authorization);
try {
return await client.GetByteArrayAsync(url).ConfigureAwait(false);
} catch {
Expand All @@ -76,9 +79,9 @@ public virtual async Task<byte[]> DownloadBytes(string url)
}

/// <inheritdoc />
public virtual async Task<string> DownloadString(string url)
public virtual async Task<string> DownloadString(string url, string authorization)
{
using var client = CreateHttpClient();
using var client = CreateHttpClient(authorization);
try {
return await client.GetStringAsync(url).ConfigureAwait(false);
} catch {
Expand All @@ -97,8 +100,9 @@ protected virtual async Task DownloadToStreamInternal(HttpClient client, string
// https://stackoverflow.com/a/46497896/184746
// Get the http headers first to examine the content length
using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
var contentLength = response.Content.Headers.ContentLength;
response.EnsureSuccessStatusCode();

var contentLength = response.Content.Headers.ContentLength;
using var download = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

// Ignore progress reporting when no progress reporter was
Expand Down Expand Up @@ -130,10 +134,10 @@ protected virtual async Task DownloadToStreamInternal(HttpClient client, string
}

/// <summary>
/// Creates a <see cref="HttpClient"/> used in every request, override this
/// function to add a custom Proxy or other configuration.
/// Creates a new <see cref="HttpClient"/> for every request. Override this
/// function to add a custom proxy or other http configuration.
/// </summary>
protected virtual HttpClient CreateHttpClient()
protected virtual HttpClient CreateHttpClient(string authorization)
{
var handler = new HttpClientHandler() {
AllowAutoRedirect = true,
Expand All @@ -142,6 +146,8 @@ protected virtual HttpClient CreateHttpClient()
};
var client = new HttpClient(handler, true);
client.DefaultRequestHeaders.UserAgent.Add(UserAgent);
if (authorization != null)
client.DefaultRequestHeaders.Add("Authorization", authorization);
return client;
}
}
Expand All @@ -150,21 +156,12 @@ protected virtual HttpClient CreateHttpClient()
[Obsolete("Use HttpClientFileDownloader")]
public class FileDownloader : IFileDownloader, IEnableLogger
{
private readonly WebClient _providedClient;

/// <summary>
/// Create a new <see cref="FileDownloader"/>, optionally providing a custom WebClient
/// </summary>
public FileDownloader(WebClient providedClient = null)
{
_providedClient = providedClient;
}

/// <inheritdoc />
public async Task DownloadFile(string url, string targetFile, Action<int> progress)
public virtual async Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization)
{
using (var wc = _providedClient ?? CreateWebClient()) {
using (var wc = CreateWebClient()) {
var failedUrl = default(string);
wc.Headers.Add("Authorization", authorization);

var lastSignalled = DateTime.MinValue;
wc.DownloadProgressChanged += (sender, args) => {
Expand Down Expand Up @@ -199,10 +196,11 @@ await this.WarnIfThrows(
}

/// <inheritdoc />
public async Task<byte[]> DownloadBytes(string url)
public virtual async Task<byte[]> DownloadBytes(string url, string authorization)
{
using (var wc = _providedClient ?? CreateWebClient()) {
using (var wc = CreateWebClient()) {
var failedUrl = default(string);
wc.Headers.Add("Authorization", authorization);

retry:
try {
Expand All @@ -222,10 +220,11 @@ public async Task<byte[]> DownloadBytes(string url)
}

/// <inheritdoc />
public async Task<string> DownloadString(string url)
public virtual async Task<string> DownloadString(string url, string authorization)
{
using (var wc = _providedClient ?? CreateWebClient()) {
using (var wc = CreateWebClient()) {
var failedUrl = default(string);
wc.Headers.Add("Authorization", authorization);

retry:
try {
Expand All @@ -244,7 +243,10 @@ public async Task<string> DownloadString(string url)
}
}

private static WebClient CreateWebClient()
/// <summary>
/// Creates and returns a new WebClient for every requst
/// </summary>
protected virtual WebClient CreateWebClient()
{
var ret = new WebClient();
var wp = WebRequest.DefaultWebProxy;
Expand Down
42 changes: 19 additions & 23 deletions src/Squirrel/GithubUpdateManager.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -82,15 +80,19 @@ public override async Task DownloadReleases(IEnumerable<ReleaseEntry> releasesTo
private async Task EnsureReleaseUrl()
{
if (this._updateUrlOrPath == null) {
this._updateUrlOrPath = await GetLatestGithubRelease().ConfigureAwait(false);
this._updateUrlOrPath = await GetLatestGithubReleaseUrl().ConfigureAwait(false);
}
}

private async Task<string> GetLatestGithubRelease()
private async Task<string> GetLatestGithubReleaseUrl()
{
var repoUri = new Uri(_repoUrl);
var userAgent = new ProductInfoHeaderValue("Squirrel", AssemblyRuntimeInfo.ExecutingAssemblyName.Version.ToString());
var releases = await GetGithubReleases(repoUri, _accessToken, _prerelease, _urlDownloader).ConfigureAwait(false);
return releases.First().DownloadUrl;
}

internal static async Task<IEnumerable<GithubRelease>> GetGithubReleases(Uri repoUri, string token, bool prerelease, IFileDownloader downloader)
{
if (repoUri.Segments.Length != 3) {
throw new Exception("Repo URL must be to the root URL of the repo e.g. https://github.com/myuser/myrepo");
}
Expand All @@ -99,41 +101,33 @@ private async Task<string> GetLatestGithubRelease()
.Append(repoUri.AbsolutePath)
.Append("/releases");

if (!string.IsNullOrWhiteSpace(_accessToken))
releasesApiBuilder.Append("?access_token=").Append(_accessToken);

Uri baseAddress;

if (repoUri.Host.EndsWith("github.com", StringComparison.OrdinalIgnoreCase)) {
baseAddress = new Uri("https://api.github.com/");
} else {
// if it's not github.com, it's probably an Enterprise server
// now the problem with Enterprise is that the API doesn't come prefixed
// it comes suffixed
// so the API path of http://internal.github.server.local API location is
// http://interal.github.server.local/api/v3.
// it comes suffixed so the API path of http://internal.github.server.local
// API location is http://interal.github.server.local/api/v3
baseAddress = new Uri(string.Format("{0}{1}{2}/api/v3/", repoUri.Scheme, Uri.SchemeDelimiter, repoUri.Host));
}

// above ^^ notice the end slashes for the baseAddress, explained here: http://stackoverflow.com/a/23438417/162694

using var client = new HttpClient() { BaseAddress = baseAddress };
client.DefaultRequestHeaders.UserAgent.Add(userAgent);
var response = await client.GetAsync(releasesApiBuilder.ToString()).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string bearer = null;
if (!string.IsNullOrWhiteSpace(token))
bearer = "Bearer " + token;

var releases = SimpleJson.DeserializeObject<List<Release>>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
var latestRelease = releases
.Where(x => _prerelease || !x.Prerelease)
.OrderByDescending(x => x.PublishedAt)
.First();
var fullPath = new Uri(baseAddress, releasesApiBuilder.ToString());
var response = await downloader.DownloadString(fullPath.ToString(), bearer).ConfigureAwait(false);

var latestReleaseUrl = latestRelease.HtmlUrl.Replace("/tag/", "/download/");
return latestReleaseUrl;
var releases = SimpleJson.DeserializeObject<List<GithubRelease>>(response);
return releases.OrderByDescending(d => d.PublishedAt).Where(x => prerelease || !x.Prerelease);
}

[DataContract]
private class Release
internal class GithubRelease
{
[DataMember(Name = "prerelease")]
public bool Prerelease { get; set; }
Expand All @@ -143,6 +137,8 @@ private class Release

[DataMember(Name = "html_url")]
public string HtmlUrl { get; set; }

public string DownloadUrl => HtmlUrl.Replace("/tag/", "/download/");
}
}
}
1 change: 0 additions & 1 deletion src/SquirrelCli/SquirrelCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
Expand Down
64 changes: 62 additions & 2 deletions src/SquirrelCli/Sync/GitHubRepository.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Squirrel;
using Squirrel.SimpleSplat;

namespace SquirrelCli.Sources
{
internal class GitHubRepository : IPackageRepository
{
private SyncGithubOptions _options;

internal readonly static IFullLogger Log = SquirrelLocator.Current.GetService<ILogManager>().GetLogger(typeof(GitHubRepository));

public GitHubRepository(SyncGithubOptions options)
{
_options = options;
}

public Task DownloadRecentPackages()
public async Task DownloadRecentPackages()
{
return SyncImplementations.SyncFromGitHub(_options.repoUrl, _options.token, new DirectoryInfo(_options.releaseDir));
var dl = Utility.CreateDefaultDownloader();

var releaseDirectoryInfo = new DirectoryInfo(_options.releaseDir);
if (!releaseDirectoryInfo.Exists)
releaseDirectoryInfo.Create();

var releases = await GithubUpdateManager.GetGithubReleases(
new Uri(_options.repoUrl), _options.token, false, dl);

if (!releases.Any()) {
Log.Warn("No github releases found.");
return;
}

string bearer = null;
if (!string.IsNullOrWhiteSpace(_options.token))
bearer = "Bearer " + _options.token;

var lastRelease = await GetLastReleaseUrl(releases, dl, bearer);
if (lastRelease.Url == null) {
Log.Warn("No github releases found with a valid release attached.");
return;
}

Log.Info("Downloading package from " + lastRelease.Url);

var localFile = Path.Combine(releaseDirectoryInfo.FullName, lastRelease.Filename);
await dl.DownloadFile(lastRelease.Url, localFile, null, bearer);

var rf = ReleaseEntry.GenerateFromFile(localFile);
ReleaseEntry.WriteReleaseFile(new[] { rf }, Path.Combine(releaseDirectoryInfo.FullName, "RELEASES"));
}

private async Task<(string Url, string Filename)> GetLastReleaseUrl(IEnumerable<GithubUpdateManager.GithubRelease> releases, IFileDownloader dl, string bearer)
{
foreach (var r in releases) {
var releasesUrl = Utility.AppendPathToUri(new Uri(r.DownloadUrl), "RELEASES");

Log.Info("Downloading metadata from " + releasesUrl);

var releasesText = await dl.DownloadString(releasesUrl.ToString(), bearer);

var entries = ReleaseEntry.ParseReleaseFile(releasesText);
var latestAsset = entries
.Where(p => p.Version != null)
.Where(p => !p.IsDelta)
.OrderByDescending(p => p.Version)
.FirstOrDefault();

if (latestAsset != null) {
return (Utility.AppendPathToUri(new Uri(r.DownloadUrl), latestAsset.Filename).ToString(), latestAsset.Filename);
}
}

return (null, null);
}

public Task UploadMissingPackages()
Expand Down
Loading

0 comments on commit b64d85d

Please sign in to comment.