Skip to content

Commit 533c54f

Browse files
committed
fix version check, reinstate api.rubberduckvba.com client
1 parent 508e41a commit 533c54f

File tree

7 files changed

+247
-32
lines changed

7 files changed

+247
-32
lines changed

Rubberduck.Core/UI/Command/VersionCheckCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Diagnostics;
3-
using System.Reflection;
43
using Rubberduck.Interaction;
54
using Rubberduck.VersionCheck;
65
using Rubberduck.Resources;
@@ -49,6 +48,12 @@ await _versionCheck
4948
.GetLatestVersionAsync(settings)
5049
.ContinueWith(t =>
5150
{
51+
if (t.IsFaulted)
52+
{
53+
Logger.Warn(t.Exception);
54+
return;
55+
}
56+
5257
if (_versionCheck.CurrentVersion < t.Result)
5358
{
5459
var proceed = true;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Rubberduck.Client.Abstract
11+
{
12+
public abstract class ApiClientBase : IDisposable
13+
{
14+
protected static readonly string UserAgentName = "Rubberduck";
15+
protected static readonly string BaseUrl = "https://api.rubberduckvba.com/api/v1/";
16+
protected static readonly string ContentTypeApplicationJson = "application/json";
17+
protected static readonly int MaxAttempts = 3;
18+
protected static readonly TimeSpan RetryCooldownDelay = TimeSpan.FromSeconds(1);
19+
20+
protected readonly Lazy<HttpClient> _client;
21+
22+
protected ApiClientBase()
23+
{
24+
_client = new Lazy<HttpClient>(() => GetClient());
25+
}
26+
27+
protected HttpClient GetClient()
28+
{
29+
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
30+
var client = new HttpClient();
31+
return ConfigureClient(client);
32+
}
33+
34+
protected virtual HttpClient ConfigureClient(HttpClient client)
35+
{
36+
var userAgentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
37+
var userAgentHeader = new ProductInfoHeaderValue(UserAgentName, userAgentVersion);
38+
39+
client.DefaultRequestHeaders.UserAgent.Add(userAgentHeader);
40+
return client;
41+
}
42+
43+
protected virtual async Task<TResult> GetResponse<TResult>(string route, CancellationToken? cancellationToken = null)
44+
{
45+
var uri = new Uri($"{BaseUrl}{route}");
46+
47+
var attempt = 0;
48+
var token = cancellationToken ?? CancellationToken.None;
49+
50+
while (!token.IsCancellationRequested && attempt <= MaxAttempts)
51+
{
52+
attempt++;
53+
var delay = attempt == 0 ? TimeSpan.Zero : RetryCooldownDelay;
54+
55+
var (success, result) = await TryGetResponse<TResult>(uri, delay, token);
56+
if (success)
57+
{
58+
return result;
59+
}
60+
}
61+
62+
token.ThrowIfCancellationRequested();
63+
throw new InvalidOperationException($"API call failed to return a result after {attempt} attempts.");
64+
}
65+
66+
private async Task<(bool, TResult)> TryGetResponse<TResult>(Uri uri, TimeSpan delay, CancellationToken token)
67+
{
68+
if (delay != TimeSpan.Zero)
69+
{
70+
await Task.Delay(delay);
71+
}
72+
73+
token.ThrowIfCancellationRequested();
74+
75+
try
76+
{
77+
using (var client = GetClient())
78+
{
79+
using (var response = await client.GetAsync(uri))
80+
{
81+
response.EnsureSuccessStatusCode();
82+
token.ThrowIfCancellationRequested();
83+
84+
var content = await response.Content.ReadAsStringAsync();
85+
var result = JsonConvert.DeserializeObject<TResult>(content);
86+
87+
return (true, result);
88+
}
89+
}
90+
}
91+
catch (OperationCanceledException)
92+
{
93+
throw;
94+
}
95+
catch
96+
{
97+
return default;
98+
}
99+
}
100+
101+
protected virtual async Task<T> Post<T>(string route, T args, CancellationToken? cancellationToken = null) => await Post<T, T>(route, args, cancellationToken ?? CancellationToken.None);
102+
103+
protected virtual async Task<TResult> Post<TArgs, TResult>(string route, TArgs args, CancellationToken? cancellationToken = null)
104+
{
105+
var uri = new Uri($"{BaseUrl}{route}");
106+
string json;
107+
try
108+
{
109+
json = JsonConvert.SerializeObject(args);
110+
}
111+
catch (Exception exception)
112+
{
113+
throw new ArgumentException("The specified arguments could not be serialized.", exception);
114+
}
115+
116+
var attempt = 0;
117+
var token = cancellationToken ?? CancellationToken.None;
118+
119+
while (!token.IsCancellationRequested && attempt <= MaxAttempts)
120+
{
121+
attempt++;
122+
var delay = attempt == 0 ? TimeSpan.Zero : RetryCooldownDelay;
123+
124+
var (success, result) = await TryPost<TResult>(uri, json, delay, token);
125+
if (success)
126+
{
127+
return result;
128+
}
129+
}
130+
131+
token.ThrowIfCancellationRequested();
132+
throw new InvalidOperationException($"API call failed to return a result after {attempt} attempts.");
133+
}
134+
135+
private async Task<(bool, TResult)> TryPost<TResult>(Uri uri, string body, TimeSpan delay, CancellationToken token)
136+
{
137+
if (delay != TimeSpan.Zero)
138+
{
139+
await Task.Delay(delay);
140+
}
141+
142+
token.ThrowIfCancellationRequested();
143+
144+
try
145+
{
146+
using (var client = GetClient())
147+
{
148+
var content = new StringContent(body, Encoding.UTF8, ContentTypeApplicationJson);
149+
using (var response = await client.PostAsync(uri, content, token))
150+
{
151+
response.EnsureSuccessStatusCode();
152+
token.ThrowIfCancellationRequested();
153+
154+
var jsonResult = await response.Content.ReadAsStringAsync();
155+
var result = JsonConvert.DeserializeObject<TResult>(jsonResult);
156+
157+
return (true, result);
158+
}
159+
}
160+
}
161+
catch (OperationCanceledException)
162+
{
163+
throw;
164+
}
165+
catch
166+
{
167+
return default;
168+
}
169+
}
170+
171+
public void Dispose()
172+
{
173+
if (_client.IsValueCreated)
174+
{
175+
_client.Value.Dispose();
176+
}
177+
}
178+
}
179+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Rubberduck.Client.Abstract;
7+
8+
namespace Rubberduck.VersionCheck
9+
{
10+
public class PublicApiClient : ApiClientBase
11+
{
12+
private static readonly string PublicTagsEndPoint = "public/tags";
13+
14+
public async Task<IEnumerable<Tag>> GetLatestTagsAsync()
15+
{
16+
var tokenSource = new CancellationTokenSource();
17+
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
18+
19+
return await GetResponse<Tag[]>(PublicTagsEndPoint, tokenSource.Token);
20+
}
21+
}
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Rubberduck.VersionCheck
5+
{
6+
public class Tag
7+
{
8+
public string Name { get; set; }
9+
public DateTime DateCreated { get; set; }
10+
public string InstallerDownloadUrl { get; set; }
11+
public int InstallerDownloads { get; set; }
12+
public bool IsPreRelease { get; set; }
13+
14+
public virtual ICollection<TagAsset> TagAssets { get; set; } = new List<TagAsset>();
15+
}
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Rubberduck.VersionCheck
2+
{
3+
public class TagAsset
4+
{
5+
public int TagId { get; set; }
6+
public string Name { get; set; }
7+
public string DownloadUrl { get; set; }
8+
}
9+
}

Rubberduck.Core/VersionCheck/VersionCheckService.cs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
using System.Linq;
55
using System.Threading;
66
using System.Threading.Tasks;
7-
using Octokit;
8-
using System.Net;
97

108
namespace Rubberduck.VersionCheck
119
{
@@ -30,11 +28,19 @@ public async Task<Version> GetLatestVersionAsync(GeneralSettings settings, Cance
3028

3129
try
3230
{
33-
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
34-
35-
return settings.IncludePreRelease
36-
? await GetGitHubNext()
37-
: await GetGitHubMain();
31+
using (var client = new PublicApiClient())
32+
{
33+
var tags = await client.GetLatestTagsAsync();
34+
var next = tags.Single(e => e.IsPreRelease).Name;
35+
var main = tags.Single(e => !e.IsPreRelease).Name;
36+
37+
var version = settings.IncludePreRelease
38+
? next.Substring("Prerelease-v".Length)
39+
: main.Substring("v".Length);
40+
41+
_latestVersion = new Version(version);
42+
return _latestVersion;
43+
}
3844
}
3945
catch
4046
{
@@ -45,28 +51,5 @@ public async Task<Version> GetLatestVersionAsync(GeneralSettings settings, Cance
4551
public Version CurrentVersion { get; }
4652
public bool IsDebugBuild { get; }
4753
public string VersionString { get; }
48-
49-
private const string GitHubOrgName = "rubberduck-vba";
50-
private const string GitHubRepoName = "Rubberduck";
51-
private const string UserAgentProductName = "Rubberduck";
52-
private GitHubClient GetGitHubClient() => new GitHubClient(new ProductHeaderValue(UserAgentProductName, CurrentVersion.ToString(3)));
53-
54-
private async Task<Version> GetGitHubMain()
55-
{
56-
var client = GetGitHubClient();
57-
var response = await client.Repository.Release.GetLatest(GitHubOrgName, GitHubRepoName);
58-
var tagName = response.TagName;
59-
60-
return new Version(tagName.Substring("v".Length));
61-
}
62-
63-
private async Task<Version> GetGitHubNext()
64-
{
65-
var client = GetGitHubClient();
66-
var response = await client.Repository.Release.GetAll(GitHubOrgName, GitHubRepoName);
67-
var tagName = response.FirstOrDefault()?.TagName ?? "Prerelease-v0.0.0";
68-
69-
return new Version(tagName.Substring("Prerelease-v".Length));
70-
}
7154
}
7255
}

Rubberduck.Main/Extension.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
2525
using Rubberduck.VBEditor.VbeRuntime;
2626
using System.IO.Abstractions;
27+
using Rubberduck.VersionCheck;
2728

2829
namespace Rubberduck
2930
{
@@ -193,7 +194,7 @@ private void InitializeAddIn()
193194

194195
if (_initialSettings?.CanShowSplash ?? false)
195196
{
196-
splash = new Splash2021(new VersionCheck.VersionCheckService(typeof(Splash2021).Assembly.GetName().Version, new PublicApiClient()));
197+
splash = new Splash2021(new VersionCheckService(typeof(Splash2021).Assembly.GetName().Version));
197198
splash.Show();
198199
splash.Refresh();
199200
}

0 commit comments

Comments
 (0)