diff --git a/Rubberduck.Core/Properties/Settings.Designer.cs b/Rubberduck.Core/Properties/Settings.Designer.cs
index 017f2c6c14..90eeb2cf96 100644
--- a/Rubberduck.Core/Properties/Settings.Designer.cs
+++ b/Rubberduck.Core/Properties/Settings.Designer.cs
@@ -12,7 +12,7 @@ namespace Rubberduck.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
diff --git a/Rubberduck.Core/Rubberduck.Core.csproj b/Rubberduck.Core/Rubberduck.Core.csproj
index 9c4dc78bb9..cda42e9170 100644
--- a/Rubberduck.Core/Rubberduck.Core.csproj
+++ b/Rubberduck.Core/Rubberduck.Core.csproj
@@ -93,4 +93,17 @@
2.0.20525
+
+
+ True
+ True
+ Settings.settings
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
diff --git a/Rubberduck.Core/Settings/GeneralSettings.cs b/Rubberduck.Core/Settings/GeneralSettings.cs
index aac14af9fa..f9b9664735 100644
--- a/Rubberduck.Core/Settings/GeneralSettings.cs
+++ b/Rubberduck.Core/Settings/GeneralSettings.cs
@@ -13,6 +13,7 @@ public interface IGeneralSettings
DisplayLanguageSetting Language { get; set; }
bool CanShowSplash { get; set; }
bool CanCheckVersion { get; set; }
+ string ApiBaseUrl { get; set; }
bool IncludePreRelease { get; set; }
bool CompileBeforeParse { get; set; }
bool IsSmartIndenterPrompted { get; set; }
@@ -45,6 +46,7 @@ public DisplayLanguageSetting Language
public bool CanShowSplash { get; set; }
public bool CanCheckVersion { get; set; }
+ public string ApiBaseUrl { get; set; }
public bool IncludePreRelease { get; set; }
public bool CompileBeforeParse { get; set; }
public bool IsSmartIndenterPrompted { get; set; }
@@ -103,7 +105,8 @@ public bool Equals(GeneralSettings other)
EnableExperimentalFeatures.Count == other.EnableExperimentalFeatures.Count &&
EnableExperimentalFeatures.All(other.EnableExperimentalFeatures.Contains) &&
SetDpiUnaware == other.SetDpiUnaware &&
- EnableFolderDragAndDrop == other.EnableFolderDragAndDrop;
+ EnableFolderDragAndDrop == other.EnableFolderDragAndDrop &&
+ ApiBaseUrl == other.ApiBaseUrl;
}
}
}
\ No newline at end of file
diff --git a/Rubberduck.Core/UI/Command/VersionCheckCommand.cs b/Rubberduck.Core/UI/Command/VersionCheckCommand.cs
index ed6fd27e6c..1a00fc42f5 100644
--- a/Rubberduck.Core/UI/Command/VersionCheckCommand.cs
+++ b/Rubberduck.Core/UI/Command/VersionCheckCommand.cs
@@ -53,40 +53,31 @@ protected override async void OnExecute(object parameter)
Logger.Info("Executing version check...");
var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
- await _versionCheck
- .GetLatestVersionAsync(settings, tokenSource.Token)
- .ContinueWith(t =>
- {
- if (t.IsFaulted)
- {
- Logger.Warn(t.Exception);
- return;
- }
+ var latest = await _versionCheck.GetLatestVersionAsync(settings, tokenSource.Token);
- if (_versionCheck.CurrentVersion < t.Result)
- {
- var proceed = true;
- if (_versionCheck.IsDebugBuild || !settings.IncludePreRelease)
- {
- // if the latest version has a revision number and isn't a pre-release build,
- // avoid prompting since we can't know if the build already includes the latest version.
- proceed = t.Result.Revision == 0;
- }
+ if (_versionCheck.CurrentVersion < latest)
+ {
+ var proceed = true;
+ if (_versionCheck.IsDebugBuild || !settings.IncludePreRelease)
+ {
+ // if the latest version has a revision number and isn't a pre-release build,
+ // avoid prompting since we can't know if the build already includes the latest version.
+ proceed = latest.Revision == 0;
+ }
- if (proceed)
- {
- PromptAndBrowse(t.Result, settings.IncludePreRelease);
- }
- else
- {
- Logger.Info("Version check skips notification of an existing newer version available.");
- }
- }
- else
- {
- Logger.Info("Version check completed: running current latest.");
- }
- });
+ if (proceed)
+ {
+ PromptAndBrowse(latest, settings.IncludePreRelease);
+ }
+ else
+ {
+ Logger.Info("Version check skips notification of an existing newer version available.");
+ }
+ }
+ else if (latest != default)
+ {
+ Logger.Info("Version check completed: running current latest.");
+ }
}
private void PromptAndBrowse(Version latestVersion, bool includePreRelease)
diff --git a/Rubberduck.Core/UI/Splash2021.cs b/Rubberduck.Core/UI/Splash2021.cs
index 227f33d439..f9bce49444 100644
--- a/Rubberduck.Core/UI/Splash2021.cs
+++ b/Rubberduck.Core/UI/Splash2021.cs
@@ -1,6 +1,5 @@
using System.Drawing;
using System.Windows.Forms;
-using Rubberduck.VersionCheck;
namespace Rubberduck.UI
{
@@ -11,9 +10,9 @@ public Splash2021()
InitializeComponent();
}
- public Splash2021(IVersionCheckService versionCheck) : this()
+ public Splash2021(string versionString) : this()
{
- VersionLabel.Text = string.Format(Resources.RubberduckUI.Rubberduck_AboutBuild, versionCheck.VersionString);
+ VersionLabel.Text = string.Format(Resources.RubberduckUI.Rubberduck_AboutBuild, versionString);
VersionLabel.Parent = pictureBox1;
VersionLabel.BackColor = Color.Transparent;
}
diff --git a/Rubberduck.Core/VersionCheck/ApiClientBase.cs b/Rubberduck.Core/VersionCheck/ApiClientBase.cs
index 54d5b8c220..f94f5f588e 100644
--- a/Rubberduck.Core/VersionCheck/ApiClientBase.cs
+++ b/Rubberduck.Core/VersionCheck/ApiClientBase.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using Rubberduck.Settings;
using System;
using System.Net;
using System.Net.Http;
@@ -9,40 +10,69 @@
namespace Rubberduck.Client.Abstract
{
- public abstract class ApiClientBase : IDisposable
+ public interface IHttpClientProvider
+ {
+ HttpClient GetClient();
+ }
+
+ public sealed class HttpClientProvider : IHttpClientProvider, IDisposable
+ {
+ private readonly Lazy _client;
+
+ public HttpClientProvider(Func getClient)
+ {
+ _client = new Lazy(getClient);
+ }
+
+ public HttpClient GetClient()
+ {
+ return _client.Value;
+ }
+
+ public void Dispose()
+ {
+ if (_client.IsValueCreated)
+ {
+ _client.Value.Dispose();
+ }
+ }
+ }
+
+ public abstract class ApiClientBase
{
protected static readonly string UserAgentName = "Rubberduck";
- protected static readonly string BaseUrl = "https://api.rubberduckvba.com/api/v1/";
protected static readonly string ContentTypeApplicationJson = "application/json";
protected static readonly int MaxAttempts = 3;
protected static readonly TimeSpan RetryCooldownDelay = TimeSpan.FromSeconds(1);
- protected readonly Lazy _client;
+ protected readonly IHttpClientProvider _clientProvider;
+ protected readonly string _baseUrl;
- protected ApiClientBase()
+ protected ApiClientBase(IGeneralSettings settings, IHttpClientProvider clientProvider)
{
- _client = new Lazy(() => GetClient());
+ _clientProvider = clientProvider;
+ _baseUrl = string.IsNullOrWhiteSpace(settings.ApiBaseUrl) ? "https://api.rubberduckvba.com/api/v1/" : settings.ApiBaseUrl;
}
protected HttpClient GetClient()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
- var client = new HttpClient();
- return ConfigureClient(client);
+ var client = _clientProvider.GetClient();
+ ConfigureClient(client);
+ return client;
}
- protected virtual HttpClient ConfigureClient(HttpClient client)
+ protected virtual void ConfigureClient(HttpClient client)
{
var userAgentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
var userAgentHeader = new ProductInfoHeaderValue(UserAgentName, userAgentVersion);
client.DefaultRequestHeaders.UserAgent.Add(userAgentHeader);
- return client;
}
protected virtual async Task GetResponse(string route, CancellationToken? cancellationToken = null)
{
- var uri = new Uri($"{BaseUrl}{route}");
+ var uri = new Uri($"{_baseUrl}{route}");
var attempt = 0;
var token = cancellationToken ?? CancellationToken.None;
@@ -102,7 +132,7 @@ protected virtual async Task GetResponse(string route, Cancell
protected virtual async Task Post(string route, TArgs args, CancellationToken? cancellationToken = null)
{
- var uri = new Uri($"{BaseUrl}{route}");
+ var uri = new Uri($"{_baseUrl}{route}");
string json;
try
{
@@ -167,13 +197,5 @@ protected virtual async Task Post(string route, TArgs a
return default;
}
}
-
- public void Dispose()
- {
- if (_client.IsValueCreated)
- {
- _client.Value.Dispose();
- }
- }
}
}
\ No newline at end of file
diff --git a/Rubberduck.Core/VersionCheck/PublicApiClient.cs b/Rubberduck.Core/VersionCheck/PublicApiClient.cs
index 7f19450852..fc6fcdab48 100644
--- a/Rubberduck.Core/VersionCheck/PublicApiClient.cs
+++ b/Rubberduck.Core/VersionCheck/PublicApiClient.cs
@@ -1,16 +1,26 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Rubberduck.Client.Abstract;
+using Rubberduck.Settings;
namespace Rubberduck.VersionCheck
{
- public class PublicApiClient : ApiClientBase
+ public interface IPublicApiClient
+ {
+ Task> GetLatestTagsAsync(CancellationToken token);
+ }
+
+ public class PublicApiClient : ApiClientBase, IPublicApiClient
{
private static readonly string PublicTagsEndPoint = "public/tags";
+ public PublicApiClient(IGeneralSettings settings, IHttpClientProvider clientProvider)
+ : base(settings, clientProvider)
+ {
+ }
+
public async Task> GetLatestTagsAsync(CancellationToken token)
{
return await GetResponse(PublicTagsEndPoint, token);
diff --git a/Rubberduck.Core/VersionCheck/VersionCheckService.cs b/Rubberduck.Core/VersionCheck/VersionCheckService.cs
index 2d2e0ab86d..3ce72f66a7 100644
--- a/Rubberduck.Core/VersionCheck/VersionCheckService.cs
+++ b/Rubberduck.Core/VersionCheck/VersionCheckService.cs
@@ -11,11 +11,15 @@ namespace Rubberduck.VersionCheck
public class VersionCheckService : IVersionCheckService
{
private static readonly ILogger _logger = LogManager.GetCurrentClassLogger();
+ private readonly IPublicApiClient _client;
/// That would be the version of the assembly for the _Extension class.
- public VersionCheckService(Version version)
+ ///
+ public VersionCheckService(Version version, IPublicApiClient client)
{
CurrentVersion = version;
+ _client = client;
+
#if DEBUG
IsDebugBuild = true;
#endif
@@ -32,9 +36,9 @@ public async Task GetLatestVersionAsync(GeneralSettings settings, Cance
return _latestVersion;
}
- using (var client = new PublicApiClient())
+ try
{
- var tags = await client.GetLatestTagsAsync(token);
+ var tags = await _client.GetLatestTagsAsync(token);
var next = tags.Single(e => e.IsPreRelease);
var main = tags.Single(e => !e.IsPreRelease);
@@ -42,9 +46,13 @@ public async Task GetLatestVersionAsync(GeneralSettings settings, Cance
_latestVersion = settings.IncludePreRelease ? next.Version : main.Version;
_logger.Info($"Check prerelease: {settings.IncludePreRelease}; latest: v{_latestVersion.ToString(4)}");
+ }
+ catch ( Exception ex )
+ {
+ _logger.Warn(ex, "Version check failed.");
- return _latestVersion;
}
+ return _latestVersion;
}
public Version CurrentVersion { get; }
diff --git a/Rubberduck.Core/app.config b/Rubberduck.Core/app.config
index b2fcaac379..3bb8bfca4b 100644
--- a/Rubberduck.Core/app.config
+++ b/Rubberduck.Core/app.config
@@ -221,6 +221,7 @@
0
false
+ https://api.rubberduckvba.com/api/v1/
diff --git a/Rubberduck.Main/Extension.cs b/Rubberduck.Main/Extension.cs
index 0adfc3b543..4475bcc04f 100644
--- a/Rubberduck.Main/Extension.cs
+++ b/Rubberduck.Main/Extension.cs
@@ -194,7 +194,7 @@ private void InitializeAddIn()
if (_initialSettings?.CanShowSplash ?? false)
{
- splash = new Splash2021(new VersionCheckService(typeof(Splash2021).Assembly.GetName().Version));
+ splash = new Splash2021(string.Format(RubberduckUI.Rubberduck_AboutBuild, Assembly.GetExecutingAssembly().GetName().Version.ToString(3)));
splash.Show();
splash.Refresh();
}
diff --git a/RubberduckTests/VersionCheckTests.cs b/RubberduckTests/VersionCheckTests.cs
index fe9fb39ac1..be0f27bc9c 100644
--- a/RubberduckTests/VersionCheckTests.cs
+++ b/RubberduckTests/VersionCheckTests.cs
@@ -1,5 +1,7 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
+using System.Web;
using Moq;
using NUnit.Framework;
using Rubberduck.Interaction;
@@ -10,6 +12,24 @@
namespace RubberduckTests
{
+ [TestFixture]
+ public class VersionCheckServiceTests
+ {
+ [Test]
+ public async Task GetLatestVersionThrowsHttpException_IsHandled()
+ {
+ var appVersion = new Version();
+ var apiClient = new Mock();
+ apiClient.Setup(m => m.GetLatestTagsAsync(It.IsAny())).Throws();
+
+ var sut = new VersionCheckService(appVersion, apiClient.Object);
+
+ var result = await sut.GetLatestVersionAsync(new GeneralSettings(), CancellationToken.None);
+
+ Assert.IsNull(result);
+ }
+ }
+
[TestFixture]
public class VersionCheckTests
{
@@ -34,12 +54,12 @@ private VersionCheckCommand CreateSUT(Configuration config, Version currentVersi
mockConfig.Setup(m => m.Read()).Returns(() => config);
mockService = new Mock();
-
+
mockService.Setup(m => m.CurrentVersion)
.Returns(() => currentVersion);
mockService.Setup(m => m.GetLatestVersionAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(() => latestVersion);
+ .ReturnsAsync(() => latestVersion);
return new VersionCheckCommand(mockService.Object, mockPrompt.Object, mockProcess.Object, mockConfig.Object);
}