mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 11:00:06 +00:00 
			
		
		
		
	Update manager and test
This commit is contained in:
		
							parent
							
								
									6412c77a9d
								
							
						
					
					
						commit
						08654bb7b0
					
				
					 5 changed files with 345 additions and 3 deletions
				
			
		
							
								
								
									
										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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat