From d20664f90a84da691baeae5fb55bb28f409ed577 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 6 May 2023 12:24:01 -0400 Subject: Automatically check for updates --- CMakeLists.txt | 2 +- src/tracker_config.cpp | 4 ++ src/tracker_config.h | 2 + src/tracker_frame.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++++++-- src/tracker_frame.h | 4 ++ src/version.h | 40 ++++++++++++++++++++ 6 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 src/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3b0ba..6b36d09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,4 +43,4 @@ add_executable(lingo_ap_tracker ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) -target_link_libraries(lingo_ap_tracker PRIVATE OpenSSL::SSL OpenSSL::Crypto wx::core wx::base yaml-cpp) +target_link_libraries(lingo_ap_tracker PRIVATE OpenSSL::SSL OpenSSL::Crypto wx::core wx::base wx::net yaml-cpp) diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp index 96bb60a..c09d241 100644 --- a/src/tracker_config.cpp +++ b/src/tracker_config.cpp @@ -12,6 +12,8 @@ void TrackerConfig::Load() { ap_server = file["ap_server"].as(); ap_player = file["ap_player"].as(); ap_password = file["ap_password"].as(); + asked_to_check_for_updates = file["asked_to_check_for_updates"].as(); + should_check_for_updates = file["should_check_for_updates"].as(); } catch (const std::exception&) { // It's fine if the file can't be loaded. } @@ -22,6 +24,8 @@ void TrackerConfig::Save() { output["ap_server"] = ap_server; output["ap_player"] = ap_player; output["ap_password"] = ap_password; + output["asked_to_check_for_updates"] = asked_to_check_for_updates; + output["should_check_for_updates"] = should_check_for_updates; std::ofstream filewriter(CONFIG_FILE_NAME); filewriter << output; diff --git a/src/tracker_config.h b/src/tracker_config.h index e2e6cd7..b9460d2 100644 --- a/src/tracker_config.h +++ b/src/tracker_config.h @@ -12,6 +12,8 @@ class TrackerConfig { std::string ap_server; std::string ap_player; std::string ap_password; + bool asked_to_check_for_updates = false; + bool should_check_for_updates = false; }; TrackerConfig& GetTrackerConfig(); diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 2a862a5..0308886 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -1,11 +1,17 @@ #include "tracker_frame.h" +#include + +#include +#include + #include "ap_state.h" #include "connection_dialog.h" #include "tracker_config.h" #include "tracker_panel.h" +#include "version.h" -enum TrackerFrameIds { ID_CONNECT = 1 }; +enum TrackerFrameIds { ID_CONNECT = 1, ID_CHECK_FOR_UPDATES = 2 }; wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); @@ -25,6 +31,7 @@ TrackerFrame::TrackerFrame() wxMenu *menuHelp = new wxMenu(); menuHelp->Append(wxID_ABOUT); + menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(menuFile, "&File"); @@ -38,10 +45,30 @@ TrackerFrame::TrackerFrame() Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); + Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, + ID_CHECK_FOR_UPDATES); Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); tracker_panel_ = new TrackerPanel(this); + + if (!GetTrackerConfig().asked_to_check_for_updates) { + GetTrackerConfig().asked_to_check_for_updates = true; + + if (wxMessageBox( + "Check for updates automatically when the tracker is opened?", + "Lingo AP Tracker", wxYES_NO) == wxYES) { + GetTrackerConfig().should_check_for_updates = true; + } else { + GetTrackerConfig().should_check_for_updates = false; + } + + GetTrackerConfig().Save(); + } + + if (GetTrackerConfig().should_check_for_updates) { + CheckForUpdates(/*manual=*/false); + } } void TrackerFrame::SetStatusMessage(std::string message) { @@ -56,8 +83,12 @@ void TrackerFrame::UpdateIndicators() { } void TrackerFrame::OnAbout(wxCommandEvent &event) { - wxMessageBox("Lingo Archipelago Tracker by hatkirby", - "About lingo-ap-tracker", wxOK | wxICON_INFORMATION); + std::ostringstream message_text; + message_text << "Lingo Archipelago Tracker " << kTrackerVersion + << " by hatkirby"; + + wxMessageBox(message_text.str(), "About lingo-ap-tracker", + wxOK | wxICON_INFORMATION); } void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } @@ -76,6 +107,10 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) { } } +void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { + CheckForUpdates(/*manual=*/true); +} + void TrackerFrame::OnStateChanged(wxCommandEvent &event) { tracker_panel_->UpdateIndicators(); Refresh(); @@ -84,3 +119,63 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) { void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { SetStatusText(event.GetString()); } + +void TrackerFrame::CheckForUpdates(bool manual) { + wxWebRequest request = wxWebSession::GetDefault().CreateRequest( + this, + "https://api.github.com/repos/hatkirby/lingo-ap-tracker/" + "releases?per_page=8"); + + if (!request.IsOk()) { + if (manual) { + wxMessageBox("Could not check for updates.", "Error", + wxOK | wxICON_ERROR); + } else { + SetStatusText("Could not check for updates."); + } + + return; + } + + Bind(wxEVT_WEBREQUEST_STATE, [this, manual](wxWebRequestEvent &evt) { + if (evt.GetState() == wxWebRequest::State_Completed) { + std::string response = evt.GetResponse().AsString().ToStdString(); + nlohmann::json parsed_response = nlohmann::json::parse(response); + + if (parsed_response.is_array() && !parsed_response.empty()) { + // This will parse to 0.0.0 if it's invalid, which will always be older + // than our current version. + Version latest_version( + parsed_response[0]["tag_name"].get()); + if (kTrackerVersion < latest_version) { + std::ostringstream message_text; + message_text << "There is a newer version of Lingo AP Tracker " + "available. You have " + << kTrackerVersion << ", and the latest version is " + << latest_version << ". Would you like to update?"; + + if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == + wxYES) { + wxLaunchDefaultBrowser( + parsed_response[0]["html_url"].get()); + } + } else if (manual) { + wxMessageBox("Lingo AP Tracker is up to date!", "Lingo AP Tracker", + wxOK); + } + } else if (manual) { + wxMessageBox("Lingo AP Tracker is up to date!", "Lingo AP Tracker", + wxOK); + } + } else if (evt.GetState() == wxWebRequest::State_Failed) { + if (manual) { + wxMessageBox("Could not check for updates.", "Error", + wxOK | wxICON_ERROR); + } else { + SetStatusText("Could not check for updates."); + } + } + }); + + request.Start(); +} diff --git a/src/tracker_frame.h b/src/tracker_frame.h index 6f4e4fc..f3470fd 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -24,9 +24,13 @@ class TrackerFrame : public wxFrame { void OnExit(wxCommandEvent &event); void OnAbout(wxCommandEvent &event); void OnConnect(wxCommandEvent &event); + void OnCheckForUpdates(wxCommandEvent &event); + void OnStateChanged(wxCommandEvent &event); void OnStatusChanged(wxCommandEvent &event); + void CheckForUpdates(bool manual); + TrackerPanel *tracker_panel_; }; diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..c45ff53 --- /dev/null +++ b/src/version.h @@ -0,0 +1,40 @@ +#ifndef VERSION_H_C757E53C +#define VERSION_H_C757E53C + +#include +#include + +struct Version { + int major = 0; + int minor = 0; + int revision = 0; + + constexpr Version(int major_arg, int minor_arg, int rev_arg) + : major(major_arg), minor(minor_arg), revision(rev_arg) {} + + Version(const std::string& ver_str) { + const std::regex version_regex("v([0-9]*)\.([0-9]*)\.([0-9]*)"); + std::smatch version_match; + + if (std::regex_match(ver_str, version_match, version_regex)) { + major = std::atoi(version_match[1].str().c_str()); + minor = std::atoi(version_match[2].str().c_str()); + revision = std::atoi(version_match[3].str().c_str()); + } + } + + bool operator<(const Version& rhs) const { + return (major < rhs.major) || + (major == rhs.major && + (minor < rhs.minor || + (minor == rhs.minor && revision < rhs.revision))); + } +}; + +std::ostream& operator<<(std::ostream& out, const Version& ver) { + return out << "v" << ver.major << "." << ver.minor << "." << ver.revision; +} + +constexpr const Version kTrackerVersion = Version(0, 1, 0); + +#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file -- cgit 1.4.1