From 4893f392501c8464f194cb38a26b8dbd93a8a013 Mon Sep 17 00:00:00 2001 From: Michael Sevestre Date: Wed, 12 Jan 2022 21:32:31 +0100 Subject: [PATCH] Fixes #1939 template db auto update (#2043) --- src/PKSim.Core/CoreConstants.cs | 4 +- src/PKSim.Core/IPKSimConfiguration.cs | 3 + .../Repositories/IRemoteTemplateRepository.cs | 1 + src/PKSim.Core/Services/IVersionChecker.cs | 17 ++++- .../Repositories/RemoteTemplateRepository.cs | 14 ++++ .../Services/VersionChecker.cs | 48 +++++------- .../Presenters/AboutPresenter.cs | 6 +- .../Services/PostLaunchChecker.cs | 35 ++++++++- templates | 2 +- .../Infrastructure/VersionCheckerSpecs.cs | 11 ++- .../Presentation/PostLaunchCheckerSpecs.cs | 74 ++++++++++++++++--- 11 files changed, 161 insertions(+), 54 deletions(-) diff --git a/src/PKSim.Core/CoreConstants.cs b/src/PKSim.Core/CoreConstants.cs index 867ca4173..0dd9733ed 100644 --- a/src/PKSim.Core/CoreConstants.cs +++ b/src/PKSim.Core/CoreConstants.cs @@ -80,11 +80,13 @@ public static class Filter public static readonly string TEMPLATE_USER_DATABASE_TEMPLATE = "PKSimTemplateDBUser.template"; public static readonly string REMOTE_TEMPLATE_SUMMARY = "templates.json"; public const string PRODUCT_NAME = "PK-Sim"; + public const string TEMPLATES_PRODUCT_NAME = "BuildingBlockTemplates"; public static readonly string PRODUCT_NAME_WITH_TRADEMARK = "PK-SimĀ®"; public static readonly string DEFAULT_SKIN = "Office 2013 Light Gray"; public static readonly string VALUE_PROPERTY_NAME = "Value"; public static readonly string PROJECT_UNDEFINED = "Undefined"; public static readonly string VERSION_FILE_URL = "https://raw.githubusercontent.com/Open-Systems-Pharmacology/Suite/master/versions.json"; + public static readonly string REMOTE_TEMPLATE_FILE_URL = "https://raw.githubusercontent.com/Open-Systems-Pharmacology/OSPSuite.BuildingBlockTemplates/main/templates.json"; public static readonly string ISSUE_TRACKER_URL = "https://github.com/open-systems-pharmacology/pk-sim/issues"; public static readonly string TEMPLATE_DATABASE_CONVERSION_WIKI_URL = @@ -92,7 +94,7 @@ public static class Filter public const string APPLICATION_NAME_TEMPLATE = "Application_"; - //tolerated precision to relativtely compare to double values + //tolerated precision to relatively compare to double values public const double DOUBLE_RELATIVE_EPSILON = 1e-2; public const char COMPOSITE_SEPARATOR = '-'; diff --git a/src/PKSim.Core/IPKSimConfiguration.cs b/src/PKSim.Core/IPKSimConfiguration.cs index 41a577cf0..4500be0d8 100644 --- a/src/PKSim.Core/IPKSimConfiguration.cs +++ b/src/PKSim.Core/IPKSimConfiguration.cs @@ -34,6 +34,9 @@ public interface IPKSimConfiguration : IApplicationConfiguration /// string RemoteTemplateSummaryPath { get; } + /// + /// Folder path where templates will be downloaded + /// string RemoteTemplateFolderPath { get; } } diff --git a/src/PKSim.Core/Repositories/IRemoteTemplateRepository.cs b/src/PKSim.Core/Repositories/IRemoteTemplateRepository.cs index 9bbfc5992..32207f1a4 100644 --- a/src/PKSim.Core/Repositories/IRemoteTemplateRepository.cs +++ b/src/PKSim.Core/Repositories/IRemoteTemplateRepository.cs @@ -12,5 +12,6 @@ public interface IRemoteTemplateRepository : IStartableRepository LoadTemplateAsync(RemoteTemplate remoteTemplate); IReadOnlyList AllReferenceTemplatesFor(RemoteTemplate remoteTemplate, T loadedTemplate); + Task UpdateLocalTemplateSummaryFile(); } } \ No newline at end of file diff --git a/src/PKSim.Core/Services/IVersionChecker.cs b/src/PKSim.Core/Services/IVersionChecker.cs index 6b73def3c..e9852b009 100644 --- a/src/PKSim.Core/Services/IVersionChecker.cs +++ b/src/PKSim.Core/Services/IVersionChecker.cs @@ -24,9 +24,24 @@ public interface IVersionChecker /// VersionInfo LatestVersion { get; } + /// + /// Returns the latest version available for the product named + /// + VersionInfo LatestVersionFor(string productName); + + /// + /// Downloads the latest version available for our software products + /// + Task DownloadLatestVersionInfoAsync(); + /// /// Returns true if a new version was found otherwise false. The latest version can be retrieved from LatestVersion /// - Task NewVersionIsAvailableAsync(); + bool NewVersionIsAvailable { get; } + + /// + /// Returns true if a new version was found for product named otherwise false. + /// + bool NewVersionIsAvailableFor(string productName, string currentVersion); } } \ No newline at end of file diff --git a/src/PKSim.Infrastructure/ORM/Repositories/RemoteTemplateRepository.cs b/src/PKSim.Infrastructure/ORM/Repositories/RemoteTemplateRepository.cs index 656dd08f4..8d14c0cdd 100644 --- a/src/PKSim.Infrastructure/ORM/Repositories/RemoteTemplateRepository.cs +++ b/src/PKSim.Infrastructure/ORM/Repositories/RemoteTemplateRepository.cs @@ -69,6 +69,20 @@ public IReadOnlyList AllReferenceTemplatesFor(RemoteTemplate } } + public async Task UpdateLocalTemplateSummaryFile() + { + var tempFile = FileHelper.GenerateTemporaryFileName(); + try + { + await downloadRemoteFile(CoreConstants.REMOTE_TEMPLATE_FILE_URL, tempFile); + FileHelper.Copy(tempFile, _configuration.RemoteTemplateSummaryPath); + } + catch (Exception) + { + //could not download the file. Do nothing + } + } + private IReadOnlyList expressionProfileFor(RemoteTemplate remoteTemplate, ISimulationSubject simulationSubject) { //TODO Not implemented yet. It will be done with the profile defined for individual as separate building block diff --git a/src/PKSim.Infrastructure/Services/VersionChecker.cs b/src/PKSim.Infrastructure/Services/VersionChecker.cs index fb5ed2cce..642d783d6 100644 --- a/src/PKSim.Infrastructure/Services/VersionChecker.cs +++ b/src/PKSim.Infrastructure/Services/VersionChecker.cs @@ -3,7 +3,6 @@ using System.Net; using System.Threading.Tasks; using OSPSuite.Core.Domain; -using OSPSuite.Utility.Exceptions; using PKSim.Core.Services; namespace PKSim.Infrastructure.Services @@ -11,57 +10,46 @@ namespace PKSim.Infrastructure.Services public class VersionChecker : IVersionChecker { private readonly IJsonSerializer _jsonSerializer; + private IReadOnlyList _allVersions = new List(); public string ProductName { get; set; } public string CurrentVersion { get; set; } public string VersionFileUrl { get; set; } - public VersionInfo LatestVersion { get; private set; } public VersionChecker(IJsonSerializer jsonSerializer) { _jsonSerializer = jsonSerializer; } - public async Task NewVersionIsAvailableAsync() + public VersionInfo LatestVersion => LatestVersionFor(ProductName); + + public VersionInfo LatestVersionFor(string productName) => _allVersions.FindByName(productName); + + public async Task DownloadLatestVersionInfoAsync() { try { - await retrieveLatestVersion(); - return newVersionIsAvailable(); - + using (var wc = new WebClient()) + { + var jsonContent = await wc.DownloadStringTaskAsync(VersionFileUrl); + _allVersions = await _jsonSerializer.DeserializeAsArrayFromString(jsonContent); + } } catch (Exception) { - return false; + //we do nothing if we cannot download the file } } - private async Task retrieveLatestVersion() - { - using (var wc = new WebClient()) - { - var jsonContent = await wc.DownloadStringTaskAsync(VersionFileUrl); - var versions = await _jsonSerializer.DeserializeAsArrayFromString(jsonContent); - LatestVersion = retrieveVersionFrom(versions); - } - } + public bool NewVersionIsAvailable => NewVersionIsAvailableFor(ProductName, CurrentVersion); - private bool newVersionIsAvailable() + public bool NewVersionIsAvailableFor(string productName, string currentVersion) { - if (LatestVersion == null) + var latestVersion = LatestVersionFor(productName); + if (latestVersion == null) return false; - var curVersion = new Version(CurrentVersion); - return curVersion.CompareTo(new Version(LatestVersion.Version)) < 0; - } - - - private VersionInfo retrieveVersionFrom(IEnumerable allVersionInfos) - { - var versionInfo = allVersionInfos.FindByName(ProductName); - if (versionInfo == null) - throw new OSPSuiteException($"{ProductName} node not available"); - - return versionInfo; + var curVersion = new Version(currentVersion); + return curVersion.CompareTo(new Version(latestVersion.Version)) < 0; } } } \ No newline at end of file diff --git a/src/PKSim.Presentation/Presenters/AboutPresenter.cs b/src/PKSim.Presentation/Presenters/AboutPresenter.cs index 5dd42f4e9..58d24133d 100644 --- a/src/PKSim.Presentation/Presenters/AboutPresenter.cs +++ b/src/PKSim.Presentation/Presenters/AboutPresenter.cs @@ -12,7 +12,7 @@ namespace PKSim.Presentation.Presenters { public interface IAboutPresenter : IDisposablePresenter { - Task CheckForUpdate(); + void CheckForUpdate(); } public class AboutPresenter : AbstractDisposablePresenter, IAboutPresenter @@ -36,9 +36,9 @@ public override void Initialize() _view.Display(); } - public async Task CheckForUpdate() + public void CheckForUpdate() { - var newVersionAvailable = await _versionChecker.NewVersionIsAvailableAsync(); + var newVersionAvailable = _versionChecker.NewVersionIsAvailable; if (newVersionAvailable) _dialogCreator.MessageBoxInfo(PKSimConstants.Information.NewVersionIsAvailable(_versionChecker.LatestVersion, Constants.PRODUCT_SITE_DOWNLOAD).RemoveHtml()); diff --git a/src/PKSim.Presentation/Services/PostLaunchChecker.cs b/src/PKSim.Presentation/Services/PostLaunchChecker.cs index 6b66f6b4b..c213a5435 100644 --- a/src/PKSim.Presentation/Services/PostLaunchChecker.cs +++ b/src/PKSim.Presentation/Services/PostLaunchChecker.cs @@ -4,6 +4,8 @@ using OSPSuite.Core.Services; using OSPSuite.Utility.Events; using PKSim.Assets; +using PKSim.Core; +using PKSim.Core.Repositories; using PKSim.Core.Services; using PKSim.Presentation.Events; @@ -16,24 +18,29 @@ public class PostLaunchChecker : IPostLaunchChecker private readonly ITemplateDatabaseCreator _templateDatabaseCreator; private readonly IEventPublisher _eventPublisher; private readonly IUserSettings _userSettings; + private readonly IRemoteTemplateRepository _remoteTemplateRepository; public PostLaunchChecker( IVersionChecker versionChecker, IWatermarkStatusChecker watermarkStatusChecker, ITemplateDatabaseCreator templateDatabaseCreator, IEventPublisher eventPublisher, - IUserSettings userSettings) + IUserSettings userSettings, + IRemoteTemplateRepository remoteTemplateRepository) { _versionChecker = versionChecker; _watermarkStatusChecker = watermarkStatusChecker; _templateDatabaseCreator = templateDatabaseCreator; _eventPublisher = eventPublisher; _userSettings = userSettings; + _remoteTemplateRepository = remoteTemplateRepository; } public async Task PerformPostLaunchCheckAsync() { - await checkForNewVersionAsync(); + await checkForNewSoftwareVersionAsync(); + + await checkForNewTemplateVersionAsync(); _watermarkStatusChecker.CheckWatermarkStatus(); @@ -41,14 +48,34 @@ public async Task PerformPostLaunchCheckAsync() } - private async Task checkForNewVersionAsync() + private async Task checkForNewTemplateVersionAsync() + { + try + { + await _versionChecker.DownloadLatestVersionInfoAsync(); + var hasNewVersion = _versionChecker.NewVersionIsAvailableFor(CoreConstants.TEMPLATES_PRODUCT_NAME, _remoteTemplateRepository.Version); + if (!hasNewVersion) + return; + + await _remoteTemplateRepository.UpdateLocalTemplateSummaryFile(); + } + catch (Exception) + { + //no need to do anything if version cannot be returned + return; + } + + + } + private async Task checkForNewSoftwareVersionAsync() { if (!_userSettings.ShowUpdateNotification) return; try { - var hasNewVersion = await _versionChecker.NewVersionIsAvailableAsync(); + await _versionChecker.DownloadLatestVersionInfoAsync(); + var hasNewVersion = _versionChecker.NewVersionIsAvailable; if (!hasNewVersion) return; } catch (Exception) diff --git a/templates b/templates index b7e74ee68..5678c8eec 160000 --- a/templates +++ b/templates @@ -1 +1 @@ -Subproject commit b7e74ee687f0ae1c701e6a4d85e10bf27c28fd56 +Subproject commit 5678c8eec95b7fbf0904b474feb56d5b70e62bc2 diff --git a/tests/PKSim.Tests/Infrastructure/VersionCheckerSpecs.cs b/tests/PKSim.Tests/Infrastructure/VersionCheckerSpecs.cs index de5fea920..635d847c5 100644 --- a/tests/PKSim.Tests/Infrastructure/VersionCheckerSpecs.cs +++ b/tests/PKSim.Tests/Infrastructure/VersionCheckerSpecs.cs @@ -31,7 +31,8 @@ protected override async Task Context() protected override async Task Because() { - _result = await sut.NewVersionIsAvailableAsync(); + await sut.DownloadLatestVersionInfoAsync(); + _result = sut.NewVersionIsAvailable; } [Observation] @@ -61,7 +62,8 @@ protected override async Task Context() protected override async Task Because() { - _result = await sut.NewVersionIsAvailableAsync(); + await sut.DownloadLatestVersionInfoAsync(); + _result = sut.NewVersionIsAvailable; } [Observation] @@ -85,7 +87,8 @@ protected override async Task Context() protected override async Task Because() { - _result = await sut.NewVersionIsAvailableAsync(); + await sut.DownloadLatestVersionInfoAsync(); + _result = sut.NewVersionIsAvailable; } [Observation] @@ -107,7 +110,7 @@ protected override async Task Context() protected override async Task Because() { - await sut.NewVersionIsAvailableAsync(); + await sut.DownloadLatestVersionInfoAsync(); } [Observation] diff --git a/tests/PKSim.Tests/Presentation/PostLaunchCheckerSpecs.cs b/tests/PKSim.Tests/Presentation/PostLaunchCheckerSpecs.cs index ba625db19..fc8f11633 100644 --- a/tests/PKSim.Tests/Presentation/PostLaunchCheckerSpecs.cs +++ b/tests/PKSim.Tests/Presentation/PostLaunchCheckerSpecs.cs @@ -6,11 +6,12 @@ using OSPSuite.Core.Services; using OSPSuite.Utility.Events; using PKSim.Assets; +using PKSim.Core; +using PKSim.Core.Repositories; using PKSim.Core.Services; using PKSim.Presentation.Events; using PKSim.Presentation.Services; - namespace PKSim.Presentation { public abstract class concern_for_PostLaunchChecker : ContextSpecificationAsync @@ -20,6 +21,7 @@ public abstract class concern_for_PostLaunchChecker : ContextSpecificationAsync< protected ITemplateDatabaseCreator TemplateDatabaseCreator; protected IUserSettings _userSettings; protected IEventPublisher _eventPublisher; + protected IRemoteTemplateRepository _remoteTemplateRepository; protected override Task Context() { @@ -28,8 +30,15 @@ protected override Task Context() TemplateDatabaseCreator = A.Fake(); _userSettings = A.Fake(); _eventPublisher = A.Fake(); + _remoteTemplateRepository = A.Fake(); - sut = new PostLaunchChecker(_versionChecker, _watermarkStatusChecker, TemplateDatabaseCreator, _eventPublisher, _userSettings); + sut = new PostLaunchChecker( + _versionChecker, + _watermarkStatusChecker, + TemplateDatabaseCreator, + _eventPublisher, + _userSettings, + _remoteTemplateRepository); return _completed; } @@ -53,9 +62,15 @@ public void should_verify_the_watermark_status() { A.CallTo(() => _watermarkStatusChecker.CheckWatermarkStatus()).MustHaveHappened(); } + + [Observation] + public void should_check_if_a_new_version_of_the_template_repository_file_is_available() + { + A.CallTo(() => _versionChecker.NewVersionIsAvailableFor(CoreConstants.TEMPLATES_PRODUCT_NAME, _remoteTemplateRepository.Version)).MustHaveHappened(); + } } - public class When_starting_the_post_launch_check_and_the_user_does_not_want_to_be_notifed_of_new_version_of_the_app : concern_for_PostLaunchChecker + public class When_starting_the_post_launch_check_and_the_user_does_not_want_to_be_notified_of_new_version_of_the_app : concern_for_PostLaunchChecker { protected override async Task Context() { @@ -71,11 +86,11 @@ protected override async Task Because() [Observation] public void should_not_check_for_a_new_version() { - A.CallTo(() => _versionChecker.NewVersionIsAvailableAsync()).MustNotHaveHappened(); + A.CallTo(() => _versionChecker.NewVersionIsAvailable).MustNotHaveHappened(); } } - public class When_starting_the_post_launch_check_and_the_user_wants_to_be_notifed_of_new_version_of_the_app_and_a_new_version_is_available : concern_for_PostLaunchChecker + public class When_starting_the_post_launch_check_and_the_user_wants_to_be_notified_of_new_version_of_the_app_and_a_new_version_is_available : concern_for_PostLaunchChecker { private readonly VersionInfo _newVersion = new VersionInfo {Version = "1.2.3"}; private ShowNotificationEvent _event; @@ -84,12 +99,11 @@ protected override async Task Context() { await base.Context(); _userSettings.ShowUpdateNotification = true; - A.CallTo(() => _versionChecker.NewVersionIsAvailableAsync()).Returns(true); + A.CallTo(() => _versionChecker.NewVersionIsAvailable).Returns(true); A.CallTo(() => _versionChecker.LatestVersion).Returns(_newVersion); A.CallTo(() => _eventPublisher.PublishEvent(A._)) .Invokes(x => _event = x.GetArgument(0)); - } protected override async Task Because() @@ -107,15 +121,15 @@ public void should_publish_a_new_version_notification() } } - public class When_starting_the_post_launch_check_and_the_user_wants_to_be_notifed_of_new_version_of_the_app_and_a_new_version_is_available_but_the_check_was_already_dismissed_by_user : concern_for_PostLaunchChecker + public class When_starting_the_post_launch_check_and_the_user_wants_to_be_notified_of_new_version_of_the_app_and_a_new_version_is_available_but_the_check_was_already_dismissed_by_user : concern_for_PostLaunchChecker { - private readonly VersionInfo _newVersion = new VersionInfo { Version = "1.2.3" }; + private readonly VersionInfo _newVersion = new VersionInfo {Version = "1.2.3"}; protected override async Task Context() { await base.Context(); _userSettings.ShowUpdateNotification = true; - A.CallTo(() => _versionChecker.NewVersionIsAvailableAsync()).Returns(true); + A.CallTo(() => _versionChecker.NewVersionIsAvailable).Returns(true); A.CallTo(() => _versionChecker.LatestVersion).Returns(_newVersion); A.CallTo(() => _userSettings.LastIgnoredVersion).Returns(_newVersion.Version); } @@ -132,4 +146,44 @@ public void should_not_publish_a_new_version_notification() } } + public class When_starting_the_post_launch_check_and_a_new_template_version_is_available : concern_for_PostLaunchChecker + { + protected override async Task Context() + { + await base.Context(); + A.CallTo(() => _versionChecker.NewVersionIsAvailableFor(CoreConstants.TEMPLATES_PRODUCT_NAME, _remoteTemplateRepository.Version)).Returns(true); + } + + protected override async Task Because() + { + await sut.PerformPostLaunchCheckAsync(); + } + + [Observation] + public void should_update_the_summary_file() + { + A.CallTo(() => _remoteTemplateRepository.UpdateLocalTemplateSummaryFile()).MustHaveHappened(); + } + } + + + public class When_starting_the_post_launch_check_and_no_new_template_version_is_available : concern_for_PostLaunchChecker + { + protected override async Task Context() + { + await base.Context(); + A.CallTo(() => _versionChecker.NewVersionIsAvailableFor(CoreConstants.TEMPLATES_PRODUCT_NAME, _remoteTemplateRepository.Version)).Returns(false); + } + + protected override async Task Because() + { + await sut.PerformPostLaunchCheckAsync(); + } + + [Observation] + public void should_update_the_summary_file() + { + A.CallTo(() => _remoteTemplateRepository.UpdateLocalTemplateSummaryFile()).MustNotHaveHappened(); + } + } } \ No newline at end of file