From f7f714912e69fbb6df6d146886a75d6e223da865 Mon Sep 17 00:00:00 2001 From: Sarah Ohlin Date: Tue, 1 Nov 2022 19:52:39 -0400 Subject: [PATCH] feat: in-app update checker --- CMakeLists.txt | 4 ++++ game/api.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++- game/api.hpp | 14 +++++++++++++ game/app.cpp | 45 +++++++++++++++++++++++++++++++++++++++++ launcher.py | 10 ++++----- 5 files changed, 122 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 149f600..f3eb819 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,10 @@ else() list(APPEND flags -DSERVER_URL="http://localhost:3000") endif() +if(APP_VERSION) + list(APPEND flags "-DAPP_TAG=${APP_VERSION}") +endif() + if(WIN32) set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/appicon.rc") add_executable(bq-r WIN32 ${sources} ${imgui_sources} ${APP_ICON_RESOURCE_WINDOWS}) diff --git a/game/api.cpp b/game/api.cpp index b1f0229..3b98cfb 100644 --- a/game/api.cpp +++ b/game/api.cpp @@ -5,8 +5,11 @@ #include "level.hpp" #include "settings.hpp" +#define STRINGIFY(s) #s + api::api() - : m_cli(settings::get().server_url()) { + : m_cli(settings::get().server_url()), + m_gh_cli("https://api.github.com") { } api &api::get() { @@ -219,6 +222,56 @@ std::future api::upload_level(::level l, const char *title, const }); } +const char *api::version() const { +#ifndef NDEBUG + return "vDEBUG"; +#elifdef APP_TAG + return STRINGIFY(APP_TAG); +#else + return "vUNKNOWN"; +#endif +} + +std::future api::is_up_to_date() { + std::string ctag(version()); + return std::async([this, ctag]() -> api::update_response { + try { + if (auto res = m_gh_cli.Get("/repos/sarahkittyy/blockquest-remake/releases/latest")) { + nlohmann::json result = nlohmann::json::parse(res->body); + if (res->status == 200) { + return { + .success = true, + .up_to_date = ctag == result["tag_name"], + .latest_version = result["tag_name"] + }; + } else { + return { + .success = false, + .error = "Invalid response from github" + }; + } + } else { + throw "Could not connect to github api"; + } + } catch (const char *e) { + return { + .success = false, + .error = e, + }; + } catch (std::exception &e) { + return { + .success = false, + .error = e.what() + }; + } catch (...) { + return { + .success = false, + .error = "Unknown error." + }; + } + }); +} + bool api::search_query::operator==(const search_query &other) const { return limit == other.limit && query == other.query && diff --git a/game/api.hpp b/game/api.hpp index 1306927..d787721 100644 --- a/game/api.hpp +++ b/game/api.hpp @@ -55,6 +55,19 @@ class api { std::future upload_level(::level l, const char* title, const char* description, bool override = false); std::future download_level(int id); + struct update_response { + bool success; + std::optional up_to_date; + std::optional latest_version; + std::optional error; + }; + + // get the current app version + const char* version() const; + + // is this application up-to-date + std::future is_up_to_date(); + std::future search_levels(search_query q); // sends a download ping to be run when we fetch a level @@ -66,4 +79,5 @@ class api { api(api&& other) = delete; httplib::Client m_cli; + httplib::Client m_gh_cli; }; diff --git a/game/app.cpp b/game/app.cpp index 792754c..15fe4f4 100644 --- a/game/app.cpp +++ b/game/app.cpp @@ -8,10 +8,17 @@ #include "auth.hpp" #include "debug.hpp" +#include "gui/image_text_button.hpp" +#include "imgui-SFML.h" +#include "imgui.h" + #include "states/debug.hpp" #include "states/edit.hpp" #include "states/search.hpp" +#include "api.hpp" +#include "util.hpp" + app::app(int argc, char** argv) { if (!ImGui::SFML::Init(resource::get().window())) throw "Could not initialize ImGui"; @@ -46,6 +53,10 @@ int app::run() { sf::RenderWindow& win = resource::get().window(); + std::future version_future = api::get().is_up_to_date(); + std::optional version_resp; + bool version_ack = false; + // app loop while (win.isOpen()) { while (win.pollEvent(evt)) { @@ -72,6 +83,40 @@ int app::run() { // imgui rendering m_fsm.imdraw(); debug::get().imdraw(dt); + + // versioning / update stuff + if (version_future.valid() && util::ready(version_future)) { + version_resp = version_future.get(); + } + if (!version_ack && version_resp.has_value()) { + if (!version_resp->success) { + ImGui::OpenPopup("Warning###UpdateWarning"); + } else if (!*version_resp->up_to_date) { + ImGui::OpenPopup("A new update is available!###UpdateError"); + } + } + bool dummy = true; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; + ImGui::SetNextWindowSize(ImVec2(500, 500), ImGuiCond_Appearing); + if (ImGui::BeginPopupModal("Warning###UpdateWarning", &dummy, flags)) { + version_ack = true; + ImGui::TextWrapped("Could not run the updater - %s", version_resp->error->c_str()); + ImGui::TextWrapped("Some things may not work correctly if you are on a version too old!"); + ImGui::Text("Current version: %s\n", api::get().version()); + if (ImGui::ImageButtonWithText(resource::get().imtex("assets/gui/yes.png"), "Ok")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SetNextWindowSize(ImVec2(500, 500), ImGuiCond_Appearing); + if (ImGui::BeginPopupModal("A new update is available!###UpdateError", &dummy, flags)) { + version_ack = true; + ImGui::TextWrapped("An update from version %s to %s is available.", api::get().version(), version_resp->latest_version->c_str()); + ImGui::TextWrapped("Run the game through launcher.exe to update."); + if (ImGui::ImageButtonWithText(resource::get().imtex("assets/gui/yes.png"), "Ok")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::EndFrame(); win.clear(m_fsm.bg()); diff --git a/launcher.py b/launcher.py index d84dd05..6a6b8ec 100644 --- a/launcher.py +++ b/launcher.py @@ -1,9 +1,9 @@ import requests -import readchar import os import sys import shutil import distutils.dir_util +import time def stage1(): version_path = getattr(sys, '_MEIPASS', os.getcwd()) @@ -12,10 +12,10 @@ def stage1(): current_version = version.read().strip() def exit_err(err): - print(f"Error: {err}") - print("Press any key to quit") - readchar.readchar() - sys.exit(-1) + print(f"Could not update: {err}") + print("Launching...") + time.sleep(1) + launch() print(f'Current version: {current_version}\nChecking for updates...')