mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 07:20:04 +00:00 
			
		
		
		
	Update manager and test
This commit is contained in:
		
							parent
							
								
									6412c77a9d
								
							
						
					
					
						commit
						08654bb7b0
					
				
					 5 changed files with 345 additions and 3 deletions
				
			
		|  | @ -64,12 +64,14 @@ set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp | ||||||
|                 source/scwx/qt/manager/radar_product_manager_notifier.hpp |                 source/scwx/qt/manager/radar_product_manager_notifier.hpp | ||||||
|                 source/scwx/qt/manager/resource_manager.hpp |                 source/scwx/qt/manager/resource_manager.hpp | ||||||
|                 source/scwx/qt/manager/settings_manager.hpp |                 source/scwx/qt/manager/settings_manager.hpp | ||||||
|                 source/scwx/qt/manager/text_event_manager.hpp) |                 source/scwx/qt/manager/text_event_manager.hpp | ||||||
|  |                 source/scwx/qt/manager/update_manager.hpp) | ||||||
| set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp | set(SRC_MANAGER source/scwx/qt/manager/radar_product_manager.cpp | ||||||
|                 source/scwx/qt/manager/radar_product_manager_notifier.cpp |                 source/scwx/qt/manager/radar_product_manager_notifier.cpp | ||||||
|                 source/scwx/qt/manager/resource_manager.cpp |                 source/scwx/qt/manager/resource_manager.cpp | ||||||
|                 source/scwx/qt/manager/settings_manager.cpp |                 source/scwx/qt/manager/settings_manager.cpp | ||||||
|                 source/scwx/qt/manager/text_event_manager.cpp) |                 source/scwx/qt/manager/text_event_manager.cpp | ||||||
|  |                 source/scwx/qt/manager/update_manager.cpp) | ||||||
| set(HDR_MAP source/scwx/qt/map/alert_layer.hpp | set(HDR_MAP source/scwx/qt/map/alert_layer.hpp | ||||||
|             source/scwx/qt/map/color_table_layer.hpp |             source/scwx/qt/map/color_table_layer.hpp | ||||||
|             source/scwx/qt/map/draw_layer.hpp |             source/scwx/qt/map/draw_layer.hpp | ||||||
|  |  | ||||||
							
								
								
									
										256
									
								
								scwx-qt/source/scwx/qt/manager/update_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								scwx-qt/source/scwx/qt/manager/update_manager.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,256 @@ | ||||||
|  | #include <scwx/qt/manager/update_manager.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <mutex> | ||||||
|  | #include <regex> | ||||||
|  | 
 | ||||||
|  | #include <boost/json.hpp> | ||||||
|  | #include <cpr/cpr.h> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::manager::update_manager"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static const std::string kGithubApiBase {"https://api.github.com"}; | ||||||
|  | static const std::string kScwxReleaseEndpoint { | ||||||
|  |    kGithubApiBase + "/repos/dpaulat/supercell-wx/releases"}; | ||||||
|  | 
 | ||||||
|  | class UpdateManager::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl(UpdateManager* self) : self_ {self}, releases_ {} {} | ||||||
|  | 
 | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    static std::string        GetVersionString(const std::string& releaseName); | ||||||
|  |    static boost::json::value ParseResponseText(const std::string& s); | ||||||
|  | 
 | ||||||
|  |    size_t PopulateReleases(); | ||||||
|  |    size_t AddReleases(const boost::json::value& json); | ||||||
|  |    std::pair<std::vector<types::gh::Release>::iterator, std::string> | ||||||
|  |    FindLatestRelease(); | ||||||
|  | 
 | ||||||
|  |    UpdateManager* self_; | ||||||
|  | 
 | ||||||
|  |    std::vector<types::gh::Release> releases_; | ||||||
|  |    types::gh::Release              latestRelease_; | ||||||
|  |    std::string                     latestVersion_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | UpdateManager::UpdateManager() : p(std::make_unique<Impl>(this)) {} | ||||||
|  | UpdateManager::~UpdateManager() = default; | ||||||
|  | 
 | ||||||
|  | types::gh::Release UpdateManager::latest_release() const | ||||||
|  | { | ||||||
|  |    return p->latestRelease_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string UpdateManager::latest_version() const | ||||||
|  | { | ||||||
|  |    return p->latestVersion_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string | ||||||
|  | UpdateManager::Impl::GetVersionString(const std::string& releaseName) | ||||||
|  | { | ||||||
|  |    static const std::regex re {"\\d+\\.\\d+\\.\\d+"}; | ||||||
|  |    std::string             versionString {}; | ||||||
|  |    std::smatch             m; | ||||||
|  | 
 | ||||||
|  |    std::regex_search(releaseName, m, re); | ||||||
|  | 
 | ||||||
|  |    if (!m.empty()) | ||||||
|  |    { | ||||||
|  |       versionString = m[0].str(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return versionString; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | boost::json::value UpdateManager::Impl::ParseResponseText(const std::string& s) | ||||||
|  | { | ||||||
|  |    boost::json::stream_parser p; | ||||||
|  |    boost::json::error_code    ec; | ||||||
|  | 
 | ||||||
|  |    p.write(s, ec); | ||||||
|  |    if (ec) | ||||||
|  |    { | ||||||
|  |       logger_->warn("{}", ec.message()); | ||||||
|  |       return nullptr; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    p.finish(ec); | ||||||
|  |    if (ec) | ||||||
|  |    { | ||||||
|  |       logger_->warn("{}", ec.message()); | ||||||
|  |       return nullptr; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return p.release(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool UpdateManager::CheckForUpdates(const std::string& currentVersion) | ||||||
|  | { | ||||||
|  |    logger_->info("Checking for updates"); | ||||||
|  | 
 | ||||||
|  |    // Query GitHub for releases
 | ||||||
|  |    size_t numReleases = p->PopulateReleases(); | ||||||
|  |    bool   newRelease  = false; | ||||||
|  | 
 | ||||||
|  |    // If GitHub returned valid releases
 | ||||||
|  |    if (numReleases > 0) | ||||||
|  |    { | ||||||
|  |       // Get the latest release
 | ||||||
|  |       auto [latestRelease, latestVersion] = p->FindLatestRelease(); | ||||||
|  | 
 | ||||||
|  |       // Validate the latest release, and compare to the current version
 | ||||||
|  |       if (latestRelease != p->releases_.end() && latestVersion > currentVersion) | ||||||
|  |       { | ||||||
|  |          logger_->info("An update is available: {}", latestVersion); | ||||||
|  | 
 | ||||||
|  |          p->latestRelease_ = *latestRelease; | ||||||
|  |          p->latestVersion_ = latestVersion; | ||||||
|  |          newRelease        = true; | ||||||
|  |          emit UpdateAvailable(latestVersion, *latestRelease); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return newRelease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t UpdateManager::Impl::PopulateReleases() | ||||||
|  | { | ||||||
|  |    static constexpr size_t perPage = | ||||||
|  |       100u;                // The number of results per page (max 100)
 | ||||||
|  |    size_t page       = 1u; // Page number of the results to fetch
 | ||||||
|  |    size_t numResults = 0u; | ||||||
|  | 
 | ||||||
|  |    static const std::string perPageString {fmt::format("{}", perPage)}; | ||||||
|  | 
 | ||||||
|  |    // Clear any existing releases
 | ||||||
|  |    releases_.clear(); | ||||||
|  | 
 | ||||||
|  |    do | ||||||
|  |    { | ||||||
|  |       const std::string pageString {fmt::format("{}", page)}; | ||||||
|  | 
 | ||||||
|  |       cpr::Response r = cpr::Get( | ||||||
|  |          cpr::Url {kScwxReleaseEndpoint}, | ||||||
|  |          cpr::Parameters {{"per_page", perPageString}, {"page", pageString}}, | ||||||
|  |          cpr::Header {{"accept", "application/vnd.github+json"}, | ||||||
|  |                       {"X-GitHub-Api-Version", "2022-11-28"}}); | ||||||
|  | 
 | ||||||
|  |       // Successful REST API query
 | ||||||
|  |       if (r.status_code == 200) | ||||||
|  |       { | ||||||
|  |          boost::json::value json = Impl::ParseResponseText(r.text); | ||||||
|  |          if (json == nullptr) | ||||||
|  |          { | ||||||
|  |             logger_->warn("Response not JSON: {}", r.header["content-type"]); | ||||||
|  |             break; | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          // Add results from response
 | ||||||
|  |          size_t newResults = AddReleases(json); | ||||||
|  |          numResults += newResults; | ||||||
|  | 
 | ||||||
|  |          if (newResults < perPage) | ||||||
|  |          { | ||||||
|  |             // We have reached the last page of results
 | ||||||
|  |             break; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          logger_->warn( | ||||||
|  |             "Invalid API response: [{}] {}", r.status_code, r.error.message); | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Check page is less than 100, this is to prevent an infinite loop
 | ||||||
|  |    } while (++page < 100); | ||||||
|  | 
 | ||||||
|  |    return numResults; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t UpdateManager::Impl::AddReleases(const boost::json::value& json) | ||||||
|  | { | ||||||
|  |    // Parse releases
 | ||||||
|  |    std::vector<types::gh::Release> newReleases {}; | ||||||
|  |    try | ||||||
|  |    { | ||||||
|  |       newReleases = | ||||||
|  |          boost::json::value_to<std::vector<types::gh::Release>>(json); | ||||||
|  |    } | ||||||
|  |    catch (const std::exception& ex) | ||||||
|  |    { | ||||||
|  |       logger_->warn("Error parsing JSON: {}", ex.what()); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    size_t newReleaseCount = newReleases.size(); | ||||||
|  | 
 | ||||||
|  |    // Add releases to the current list
 | ||||||
|  |    releases_.insert(releases_.end(), newReleases.begin(), newReleases.end()); | ||||||
|  | 
 | ||||||
|  |    return newReleaseCount; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::pair<std::vector<types::gh::Release>::iterator, std::string> | ||||||
|  | UpdateManager::Impl::FindLatestRelease() | ||||||
|  | { | ||||||
|  |    // Initialize the latest release to the end iterator
 | ||||||
|  |    std::vector<types::gh::Release>::iterator latestRelease = releases_.end(); | ||||||
|  |    std::string                               latestReleaseVersion {}; | ||||||
|  | 
 | ||||||
|  |    for (auto it = releases_.begin(); it != releases_.end(); ++it) | ||||||
|  |    { | ||||||
|  |       if (it->draft_ || it->prerelease_) | ||||||
|  |       { | ||||||
|  |          // Skip drafts and prereleases
 | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Get the version string of the current release
 | ||||||
|  |       std::string currentVersion {GetVersionString(it->name_)}; | ||||||
|  | 
 | ||||||
|  |       // If not set, or current version is lexographically newer
 | ||||||
|  |       if (latestRelease == releases_.end() || | ||||||
|  |           currentVersion > latestReleaseVersion) | ||||||
|  |       { | ||||||
|  |          // Update the latest release
 | ||||||
|  |          latestRelease        = it; | ||||||
|  |          latestReleaseVersion = currentVersion; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return {latestRelease, latestReleaseVersion}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<UpdateManager> UpdateManager::Instance() | ||||||
|  | { | ||||||
|  |    static std::weak_ptr<UpdateManager> updateManagerReference_ {}; | ||||||
|  |    static std::mutex                   instanceMutex_ {}; | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock(instanceMutex_); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<UpdateManager> updateManager = | ||||||
|  |       updateManagerReference_.lock(); | ||||||
|  | 
 | ||||||
|  |    if (updateManager == nullptr) | ||||||
|  |    { | ||||||
|  |       updateManager           = std::make_shared<UpdateManager>(); | ||||||
|  |       updateManagerReference_ = updateManager; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return updateManager; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										43
									
								
								scwx-qt/source/scwx/qt/manager/update_manager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								scwx-qt/source/scwx/qt/manager/update_manager.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/qt/types/github_types.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | #include <QObject> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class UpdateManager : public QObject | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit UpdateManager(); | ||||||
|  |    ~UpdateManager(); | ||||||
|  | 
 | ||||||
|  |    types::gh::Release latest_release() const; | ||||||
|  |    std::string        latest_version() const; | ||||||
|  | 
 | ||||||
|  |    bool CheckForUpdates(const std::string& currentVersion = {}); | ||||||
|  | 
 | ||||||
|  |    static std::shared_ptr<UpdateManager> Instance(); | ||||||
|  | 
 | ||||||
|  | signals: | ||||||
|  |    void UpdateAvailable(const std::string&        latestVersion, | ||||||
|  |                         const types::gh::Release& latestRelease); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										40
									
								
								test/source/scwx/qt/manager/update_manager.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								test/source/scwx/qt/manager/update_manager.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | #include <scwx/qt/manager/update_manager.hpp> | ||||||
|  | 
 | ||||||
|  | #include <gtest/gtest.h> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | TEST(UpdateManagerTest, CheckForUpdates) | ||||||
|  | { | ||||||
|  |    auto               updateManager = UpdateManager::Instance(); | ||||||
|  |    bool               updateFound; | ||||||
|  |    types::gh::Release latestRelease; | ||||||
|  |    std::string        latestVersion; | ||||||
|  | 
 | ||||||
|  |    // Check for updates, and expect an update to be found
 | ||||||
|  |    updateFound   = updateManager->CheckForUpdates("0.0.0"); | ||||||
|  |    latestRelease = updateManager->latest_release(); | ||||||
|  |    latestVersion = updateManager->latest_version(); | ||||||
|  | 
 | ||||||
|  |    EXPECT_EQ(updateFound, true); | ||||||
|  |    EXPECT_GT(latestRelease.name_.size(), 0); | ||||||
|  |    EXPECT_GT(latestRelease.htmlUrl_.size(), 0); | ||||||
|  |    EXPECT_GT(latestRelease.body_.size(), 0); | ||||||
|  |    EXPECT_EQ(latestRelease.draft_, false); | ||||||
|  |    EXPECT_EQ(latestRelease.prerelease_, false); | ||||||
|  |    EXPECT_GT(latestVersion, "0.0.0"); | ||||||
|  | 
 | ||||||
|  |    // Check for updates, and expect no updates to be found
 | ||||||
|  |    updateFound = updateManager->CheckForUpdates("9999.99.99"); | ||||||
|  | 
 | ||||||
|  |    EXPECT_EQ(updateFound, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -21,7 +21,8 @@ set(SRC_PROVIDER_TESTS source/scwx/provider/aws_level2_data_provider.test.cpp | ||||||
|                        source/scwx/provider/warnings_provider.test.cpp) |                        source/scwx/provider/warnings_provider.test.cpp) | ||||||
| set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.test.cpp | set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.test.cpp | ||||||
|                         source/scwx/qt/config/radar_site.test.cpp) |                         source/scwx/qt/config/radar_site.test.cpp) | ||||||
| set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.test.cpp) | set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.test.cpp | ||||||
|  |                          source/scwx/qt/manager/update_manager.test.cpp) | ||||||
| set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp) | set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp) | ||||||
| set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp | set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp | ||||||
|                           source/scwx/qt/settings/settings_variable.test.cpp) |                           source/scwx/qt/settings/settings_variable.test.cpp) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat