mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 05:50: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
|
|
@ -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/resource_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
|
||||
source/scwx/qt/manager/radar_product_manager_notifier.cpp
|
||||
source/scwx/qt/manager/resource_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
|
||||
source/scwx/qt/map/color_table_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)
|
||||
set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.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_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp
|
||||
source/scwx/qt/settings/settings_variable.test.cpp)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue