From fc0cab54d2095871c05d9011079505d8e58ed1e8 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 13 Nov 2021 16:06:48 +0100 Subject: [PATCH] feat: add updater --- include/misc/Updater.h | 37 +++++++++ include/render/gui/TowerGui.h | 1 + include/render/gui/UpdateMenu.h | 29 +++++++ src/TowerDefense.cpp | 3 + src/misc/Updater.cpp | 133 ++++++++++++++++++++++++++++++++ src/render/gui/TowerGui.cpp | 4 + src/render/gui/UpdateMenu.cpp | 82 ++++++++++++++++++++ 7 files changed, 289 insertions(+) create mode 100644 include/misc/Updater.h create mode 100644 include/render/gui/UpdateMenu.h create mode 100644 src/misc/Updater.cpp create mode 100644 src/render/gui/UpdateMenu.cpp diff --git a/include/misc/Updater.h b/include/misc/Updater.h new file mode 100644 index 0000000..cfc6292 --- /dev/null +++ b/include/misc/Updater.h @@ -0,0 +1,37 @@ +#pragma once + +#include "misc/DataBuffer.h" + +namespace td { +namespace utils { + +class Updater { +private: + float m_Progress; + bool m_DownloadComplete; + bool m_FileWrited; + bool m_CancelDownload; + DataBuffer m_FileBuffer; +public: + Updater() : m_Progress(0), m_DownloadComplete(false), m_FileWrited(false), m_CancelDownload(false) {} + + bool checkUpdate(); + void downloadUpdate(); + void cancelDownload() { m_CancelDownload = true; m_Progress = 0.0f; m_DownloadComplete = false; } + bool writeFile(); + + void clearCache() { m_FileBuffer.Clear(); } + + float getDownloadProgress() { return m_Progress; } + bool isDownloadComplete() { return m_DownloadComplete; } + bool isFileWrited() { return m_FileWrited; } + + static std::string getLocalFilePath(); + static void removeOldFile(); +private: + std::string getDownloadFileURL(); + std::uint32_t getLocalFileSize(); +}; + +} // namespace utils +} // namespace td diff --git a/include/render/gui/TowerGui.h b/include/render/gui/TowerGui.h index 310ee76..cf0af9b 100644 --- a/include/render/gui/TowerGui.h +++ b/include/render/gui/TowerGui.h @@ -41,6 +41,7 @@ private: std::unique_ptr m_MainMenu; std::unique_ptr m_GameMenu; std::unique_ptr m_FrameMenu; + std::unique_ptr m_UpdateMenu; bool m_DemoOpened = false; public: diff --git a/include/render/gui/UpdateMenu.h b/include/render/gui/UpdateMenu.h new file mode 100644 index 0000000..7d921b1 --- /dev/null +++ b/include/render/gui/UpdateMenu.h @@ -0,0 +1,29 @@ +#pragma once + +#include "GuiWidget.h" + +#include "misc/Updater.h" + +#include + +namespace td { +namespace gui { + +class UpdateMenu : public GuiWidget { +private: + bool m_Opened; + std::string m_Error; + utils::Updater m_Updater; + std::shared_future m_UpdateAvailable; +public: + UpdateMenu(client::Client* client); + + virtual void render(); +private: + void checkUpdates(); + bool isUpdateChecked(); + void renderErrorPopup(); +}; + +} // namespace gui +} // namespace td diff --git a/src/TowerDefense.cpp b/src/TowerDefense.cpp index eef34c1..ce3ddc1 100644 --- a/src/TowerDefense.cpp +++ b/src/TowerDefense.cpp @@ -7,12 +7,15 @@ //============================================================================ #include "window/Display.h" +#include "misc/Updater.h" #ifdef __ANDROID__ extern "C" #endif int main(int argc, const char* args[]) { + td::utils::Updater::removeOldFile(); + Display::create(); while (!Display::isCloseRequested()) { Display::pollEvents(); diff --git a/src/misc/Updater.cpp b/src/misc/Updater.cpp new file mode 100644 index 0000000..5debf1b --- /dev/null +++ b/src/misc/Updater.cpp @@ -0,0 +1,133 @@ +#include "misc/Updater.h" +#include "misc/Platform.h" +#include "misc/DataBuffer.h" + +#include "network/HttpLib.h" + +#ifdef _WIN32 +#include +#include +#endif + +namespace td { +namespace utils { + +bool Updater::checkUpdate() { + std::string newFileUrl = getDownloadFileURL(); + + if (newFileUrl.empty()) return false; + + httplib::Client client("thesims.freeboxos.fr", 30000); + if (auto res = client.Head(newFileUrl.c_str())) { + std::uint32_t distantFileSize = std::stol(res.value().get_header_value("Content-Length")); + std::uint32_t localFileSize = getLocalFileSize(); + + if (distantFileSize != localFileSize) { + return true; + } + return false; + } else { + std::cerr << "Error : " << res.error() << std::endl; + return false; + } +} + +std::uint32_t Updater::getLocalFileSize() { + std::string localFile = getLocalFilePath(); + + if (localFile.empty()) return 0; + + std::ifstream in(localFile, std::ifstream::ate | std::ifstream::binary); + return in.tellg(); +} + +std::string Updater::getDownloadFileURL() { + Os systemOs = getSystemOs(); + Architecture systemArch = getSystemArchitecture(); + + switch (systemOs) { + + case Os::Windows: { + + switch (systemArch) { + case Architecture::x86_64: + return "/Tower%20Defense/Tower%20Defense%20Windows%20(x86_64).exe"; + case Architecture::x86: + return "/Tower%20Defense/Tower%20Defense%20Windows%20(x86).exe"; + default: + return ""; + } + + } + + default: // not supported on Android and Linux (package managers are better) + return ""; + } +} + +std::string Updater::getLocalFilePath() { +#ifdef _WIN32 + char filePathBuffer[1024]; + GetModuleFileName(nullptr, filePathBuffer, sizeof(filePathBuffer)); + + std::string filePath = filePathBuffer; + std::cout << filePath << std::endl; + return filePath; +#endif + return ""; +} + +void Updater::downloadUpdate() { + if (m_DownloadComplete) return; + + m_CancelDownload = false; + + std::string newFileUrl = getDownloadFileURL(); + + httplib::Client client("thesims.freeboxos.fr", 30000); + + httplib::ContentReceiver reciever = [&](const char* data, size_t data_length) { + std::string fileData(data, data_length); + m_FileBuffer << fileData; + return true; + }; + + httplib::Progress progress = [&](uint64_t len, uint64_t total) { + m_Progress = (float)len * 100.0f / total; + return !m_CancelDownload; + }; + + auto res = client.Get(newFileUrl.c_str(), reciever, progress); + + if (!m_CancelDownload) { + m_DownloadComplete = true; + } +} + +void Updater::removeOldFile(){ + std::remove(std::string(getLocalFilePath() + "_old").c_str()); +} + +bool Updater::writeFile() { + if (m_FileWrited || !m_DownloadComplete) return false; + + std::cout << m_FileBuffer.GetSize() << std::endl; + + if(!m_FileBuffer.WriteFile(getLocalFilePath() + "_recent")) + return false; + + #ifdef _WIN32 + std::rename(getLocalFilePath().c_str(), std::string(getLocalFilePath() + "_old").c_str()); + std::rename(std::string(getLocalFilePath() + "_recent").c_str(), getLocalFilePath().c_str()); + #endif + + m_FileWrited = true; + m_DownloadComplete = false; + + clearCache(); + + return true; +} + +} // namespace utils +} // namespace td diff --git a/src/render/gui/TowerGui.cpp b/src/render/gui/TowerGui.cpp index b6f9ba8..9bbf294 100644 --- a/src/render/gui/TowerGui.cpp +++ b/src/render/gui/TowerGui.cpp @@ -11,6 +11,7 @@ #include "render/gui/MainMenu.h" #include "render/gui/GameMenu.h" #include "render/gui/FrameMenu.h" +#include "render/gui/UpdateMenu.h" #include "imgui/imgui_impl_opengl3.h" #include "imgui/imgui_impl_sdl.h" @@ -24,6 +25,7 @@ void TowerGui::initWidgets() { m_MainMenu = std::make_unique(m_Client.get()); m_GameMenu = std::make_unique(m_Client.get()); m_FrameMenu = std::make_unique(m_Client.get()); + m_UpdateMenu = std::make_unique(m_Client.get()); } TowerGui::TowerGui(SDL_Window* sdl_window, SDL_GLContext glContext, td::render::Renderer* renderer) : m_Window(sdl_window), @@ -74,7 +76,9 @@ void TowerGui::render() { if (m_DemoOpened) ImGui::ShowDemoWindow(&m_DemoOpened); + m_FrameMenu->render(); + m_UpdateMenu->render(); endFrame(); } diff --git a/src/render/gui/UpdateMenu.cpp b/src/render/gui/UpdateMenu.cpp new file mode 100644 index 0000000..112747b --- /dev/null +++ b/src/render/gui/UpdateMenu.cpp @@ -0,0 +1,82 @@ +#include "render/gui/UpdateMenu.h" + +#include "render/gui/imgui/imgui.h" + +#include + +namespace td { +namespace gui { + +UpdateMenu::UpdateMenu(client::Client* client) : GuiWidget(client), m_Opened(true) { + checkUpdates(); +} + +void UpdateMenu::render() { + renderErrorPopup(); + if (m_Opened) { + ImGui::Begin("Updater", &m_Opened); + if (isUpdateChecked()) { + + bool updateAvailable = m_UpdateAvailable.get(); + if (updateAvailable) { + + if(m_Updater.isFileWrited()){ + ImGui::Text("The update is now installed"); + ImGui::Text("The game needs to be restarted"); + }else if (m_Updater.isDownloadComplete()) { + ImGui::Text("Download done!"); + if(ImGui::Button("Install")){ + if(!m_Updater.writeFile()){ + m_Error = "Failed to write file !\n"; + ImGui::OpenPopup("UpdateError"); + } + } + if(ImGui::Button("Cancel")){ + m_Updater.cancelDownload(); + m_Updater.clearCache(); + } + } else { + if (m_Updater.getDownloadProgress() > 0) { + ImGui::Text("Downloading ..."); + ImGui::ProgressBar(m_Updater.getDownloadProgress()); + if (ImGui::Button("Cancel")) { + m_Updater.cancelDownload(); + } + } else { + ImGui::Text("An update is available!"); + if (ImGui::Button("Download")) { + m_Updater.downloadUpdate(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + m_Opened = false; + } + } + } + } else { + ImGui::Text("No update available!"); + } + } else { + ImGui::Text("Checking updates ..."); + } + ImGui::End(); + } +} + +void UpdateMenu::renderErrorPopup(){ + if(ImGui::BeginPopup("UpdateError")){ + ImGui::Text("Error : %s", m_Error.c_str()); + ImGui::EndPopup(); + } +} + +bool UpdateMenu::isUpdateChecked() { + return m_UpdateAvailable.wait_for(std::chrono::seconds(0)) == std::future_status::ready; +} + +void UpdateMenu::checkUpdates() { + m_UpdateAvailable = std::async(std::launch::async, [&]() { return m_Updater.checkUpdate();}); +} + +} // namespace gui +} // namespace td