Skip to content
This repository has been archived by the owner on Dec 7, 2024. It is now read-only.

Commit

Permalink
Added update checking
Browse files Browse the repository at this point in the history
Also unified version info data to just be put in one place
  • Loading branch information
WorkingRobot committed Jun 19, 2020
1 parent 2ca5ebf commit d965c2e
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 45 deletions.
6 changes: 3 additions & 3 deletions gui/GameUpdateChecker.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
#include "GameUpdateChecker.h"

#ifndef LOG_SECTION
#define LOG_SECTION "UpdateChecker"
#define LOG_SECTION "GameUpdateChecker"
#endif

#include "../Logger.h"

GameUpdateChecker::GameUpdateChecker(fs::path cachePath, UpdateCallback callback, std::chrono::milliseconds checkInterval) :
GameUpdateChecker::GameUpdateChecker(fs::path cachePath, GameUpdateCallback callback, std::chrono::milliseconds checkInterval) :
Auth(cachePath),
Callback(callback),
CheckInterval(checkInterval)
{
LOG_DEBUG("Initializing force update");
ForceUpdate();
LOG_DEBUG("Creating update thread");
LOG_DEBUG("Creating game update thread");
UpdateThread = std::thread(&GameUpdateChecker::Thread, this);
UpdateThread.detach(); // causes some exception when the deconstructer is trying to join it otherwise
}
Expand Down
6 changes: 3 additions & 3 deletions gui/GameUpdateChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

namespace fs = std::filesystem;

typedef std::function<void(const std::string& Url, const std::string& Version)> UpdateCallback;
typedef std::function<void(const std::string& Url, const std::string& Version)> GameUpdateCallback;

class GameUpdateChecker {
public:
GameUpdateChecker(fs::path cachePath, UpdateCallback callback, std::chrono::milliseconds checkInterval);
GameUpdateChecker(fs::path cachePath, GameUpdateCallback callback, std::chrono::milliseconds checkInterval);
~GameUpdateChecker();

void SetInterval(std::chrono::milliseconds newInterval);
Expand Down Expand Up @@ -49,7 +49,7 @@ class GameUpdateChecker {
std::string LatestUrl;
std::string LatestVersion;

UpdateCallback Callback;
GameUpdateCallback Callback;
std::thread UpdateThread;
std::chrono::steady_clock::time_point UpdateWakeup;
cancel_flag UpdateFlag;
Expand Down
94 changes: 94 additions & 0 deletions gui/UpdateChecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "UpdateChecker.h"

#define GH_REPO "WorkingRobot/EGL2"
#define GH_RELEASE_URL "https://api.github.com/repos/" GH_REPO "/releases/latest"

#ifndef LOG_SECTION
#define LOG_SECTION "UpdateChecker"
#endif

#include "../Logger.h"
#include "../web/http.h"
#include "versioninfo.h"

#include <rapidjson/document.h>

UpdateChecker::UpdateChecker(UpdateCallback callback, std::chrono::milliseconds checkInterval) :
Callback(callback),
CheckInterval(checkInterval),
LatestVersion(VERSION_STRING)
{
LOG_DEBUG("Creating update thread");
UpdateThread = std::thread(&UpdateChecker::Thread, this);
UpdateThread.detach(); // causes some exception when the deconstructer is trying to join it otherwise
}

UpdateChecker::~UpdateChecker() {
UpdateFlag.cancel();
}

void UpdateChecker::SetInterval(std::chrono::milliseconds newInterval)
{
CheckInterval.store(newInterval);
UpdateWakeup = std::chrono::steady_clock::time_point::min(); // force update
}

void UpdateChecker::StopUpdateThread()
{
UpdateFlag.cancel();
}

bool UpdateChecker::ForceUpdate() {
auto conn = Client::CreateConnection();
conn->SetUrl(GH_RELEASE_URL);
conn->SetUserAgent(GH_REPO);

if (!Client::Execute(conn, cancel_flag(), true)) {
LOG_WARN("Could not check for EGL2 update");
return false;
}

if (conn->GetResponseCode() != 200) {
return false;
}

rapidjson::Document releaseInfo;
releaseInfo.Parse(conn->GetResponseBody().c_str());

if (releaseInfo.HasParseError()) {
LOG_ERROR("Getting release info: JSON Parse Error %d @ %zu", releaseInfo.GetParseError(), releaseInfo.GetErrorOffset());
return false;
}

auto version = releaseInfo["tag_name"].GetString();
if (LatestVersion == version) {
LOG_DEBUG("NO EGL2 UPDATE");
return false;
}

LatestInfo.Version = version;
LatestInfo.Url = releaseInfo["html_url"].GetString();

auto& exeAsset = releaseInfo["assets"][0];
LatestInfo.DownloadUrl = exeAsset["browser_download_url"].GetString();
LatestInfo.DownloadCount = exeAsset["download_count"].GetInt();
LatestInfo.DownloadSize = exeAsset["size"].GetInt();
return true;
}

UpdateInfo& UpdateChecker::GetLatestInfo()
{
return LatestInfo;
}

void UpdateChecker::Thread() {
while (!UpdateFlag.cancelled()) {
do {
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
} while (std::chrono::steady_clock::now() < UpdateWakeup);
if (ForceUpdate()) {
Callback(LatestInfo);
}
UpdateWakeup = std::chrono::steady_clock::now() + CheckInterval.load();
}
}
43 changes: 43 additions & 0 deletions gui/UpdateChecker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once
#include "../containers/cancel_flag.h"

#include <functional>
#include <thread>

struct UpdateInfo {
std::string Version;
std::string Url;

std::string DownloadUrl;
int DownloadCount;
int DownloadSize;
};

typedef std::function<void(const UpdateInfo& Info)> UpdateCallback;

class UpdateChecker {
public:
UpdateChecker(UpdateCallback callback, std::chrono::milliseconds checkInterval);
~UpdateChecker();

void SetInterval(std::chrono::milliseconds newInterval);

void StopUpdateThread();

bool ForceUpdate();

UpdateInfo& GetLatestInfo();

private:
void Thread();

std::atomic<std::chrono::milliseconds> CheckInterval;

std::string LatestVersion;
UpdateInfo LatestInfo;

UpdateCallback Callback;
std::thread UpdateThread;
std::chrono::steady_clock::time_point UpdateWakeup;
cancel_flag UpdateFlag;
};
96 changes: 66 additions & 30 deletions gui/cMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ cMain::cMain(wxApp* app, const fs::path& settingsPath, const fs::path& manifestP
SettingsPath(settingsPath),
Settings(SettingsDefault()),
Auth(personalAuth),
UpdateAvailable(false) {
GameUpdateAvailable(false) {
LOG_DEBUG("Setting up (%s, %s)", settingsPath.string().c_str(), manifestPath.string().c_str());

this->SetIcon(wxICON(APP_ICON));
Expand Down Expand Up @@ -215,12 +215,12 @@ cMain::cMain(wxApp* app, const fs::path& settingsPath, const fs::path& manifestP

std::thread([=]() {
SetStatus(LSTR(MAIN_STATUS_STARTING));
LOG_DEBUG("Creating update checker");
GameChecker = std::make_unique<GameUpdateChecker>(
LOG_DEBUG("Creating game update checker");
GameUpdater = std::make_unique<GameUpdateChecker>(
manifestPath,
[this](const std::string& Url, const std::string& Version) { OnUpdate(Version, Url); },
[this](const std::string& Url, const std::string& Version) { OnGameUpdate(Version, Url); },
SettingsGetUpdateInterval(&Settings));
Mount(GameChecker->GetLatestUrl());
Mount(GameUpdater->GetLatestUrl());
LOG_DEBUG("Enabling buttons");
SetStatus(LSTR(MAIN_STATUS_PLAYABLE));
SIDE_BUTTON_OBJ(verify)->Enable();
Expand All @@ -229,10 +229,19 @@ cMain::cMain(wxApp* app, const fs::path& settingsPath, const fs::path& manifestP
auto ct = Build->GetMissingChunkCount();
LOG_DEBUG("%d missing chunks", ct);
if (ct) {
OnUpdate(GameChecker->GetLatestVersion());
OnGameUpdate(GameUpdater->GetLatestVersion());
}
}).detach();


std::thread([=]() {
LOG_DEBUG("Creating update checker");
Updater = std::make_unique<UpdateChecker>(
[this](const UpdateInfo& Info) { OnUpdate(Info); },
ch::milliseconds(5 * 60 * 1000)); // every 5 minutes, both for ratelimiting purposes and no real reason to be lower
LOG_DEBUG("Created update checker");
}).detach();

Systray = new SystrayIcon(this);
Systray->SetIcon(wxICON(APP_ICON), "EGL2");
}
Expand Down Expand Up @@ -262,8 +271,8 @@ void cMain::OnSettingsClicked(bool onStartup) {
LOG_ERROR("Could not open settings file to write");
}
}, &SettingsValidate, [this]() {
if (GameChecker) {
GameChecker->SetInterval(SettingsGetUpdateInterval(&Settings));
if (GameUpdater) {
GameUpdater->SetInterval(SettingsGetUpdateInterval(&Settings));
}
SetupWnd.reset();
this->Raise();
Expand Down Expand Up @@ -327,8 +336,8 @@ void cMain::OnVerifyClicked() {
}

void cMain::OnPlayClicked() {
if (UpdateAvailable) {
BeginUpdate();
if (GameUpdateAvailable) {
BeginGameUpdate();
}
else {
if (FirstAuthLaunched) {
Expand Down Expand Up @@ -389,12 +398,21 @@ void cMain::SetStatus(const wxString& string) {
statusBar->SetLabel(string);
}

void cMain::OnUpdate(const std::string& Version, const std::optional<std::string>& Url)
void cMain::Mount(const std::string& Url) {
LOG_INFO("Setting up cache directory");
MountedBuild::SetupCacheDirectory(Settings.CacheDir);
LOG_INFO("Mounting new url: %s", Url.c_str());
Build.reset(new MountedBuild(GameUpdater->GetManifest(Url), fs::path(Settings.CacheDir) / MOUNT_FOLDER, Settings.CacheDir, SettingsGetStorageFlags(&Settings), Settings.BufferCount));
LOG_INFO("Setting up game dir");
Build->SetupGameDirectory([](unsigned int m) {}, []() {}, []() {}, cancel_flag(), Settings.ThreadCount);
}

void cMain::OnGameUpdate(const std::string& Version, const std::optional<std::string>& Url)
{
UpdateAvailable = true;
GameUpdateAvailable = true;
playBtn->SetLabel(LSTR(MAIN_BTN_UPDATE));
if (Url.has_value()) {
UpdateUrl = Url;
GameUpdateUrl = Url;
}

CallAfter([=]() {
Expand All @@ -403,15 +421,15 @@ void cMain::OnUpdate(const std::string& Version, const std::optional<std::string
if (!notif->AddAction(42, LSTR(MAIN_NOTIF_ACTION))) {
LOG_WARN("Actions aren't supported");
}
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_ACTION, std::bind(&cMain::BeginUpdate, this));
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, std::bind(&cMain::BeginUpdate, this));
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_ACTION, std::bind(&cMain::BeginGameUpdate, this));
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, std::bind(&cMain::BeginGameUpdate, this));
if (!notif->Show(wxNotificationMessage::Timeout_Never)) {
LOG_ERROR("Couldn't launch update notification");
LOG_ERROR("Couldn't launch game update notification");
}
});
}

void cMain::BeginUpdate()
void cMain::BeginGameUpdate()
{
if (UpdateWnd) {
UpdateWnd->Restore();
Expand All @@ -420,36 +438,54 @@ void cMain::BeginUpdate()
return;
}

if (!UpdateAvailable) {
if (!GameUpdateAvailable) {
return;
}
std::thread([this]() {
if (!UpdateUrl.has_value()) {
if (!GameUpdateUrl.has_value()) {
LOG_DEBUG("Downloading unavailable chunks");
}
else {
LOG_DEBUG("Beginning update: %s", UpdateUrl->c_str());
Mount(*UpdateUrl);
LOG_DEBUG("Beginning update: %s", GameUpdateUrl->c_str());
Mount(*GameUpdateUrl);
}

this->CallAfter([this]() {
RUN_PROGRESS(LSTR(MAIN_PROG_UPDATE), UpdateWnd, [this](bool cancelled) {
LOG_DEBUG("EXIT NOTICED");
if (!cancelled) {
UpdateAvailable = false;
GameUpdateAvailable = false;
playBtn->SetLabel(LSTR(MAIN_BTN_PLAY));
UpdateUrl.reset();
GameUpdateUrl.reset();
}
}, PreloadAllChunks, Settings.ThreadCount);
});
}).detach();
}

void cMain::Mount(const std::string& Url) {
LOG_INFO("Setting up cache directory");
MountedBuild::SetupCacheDirectory(Settings.CacheDir);
LOG_INFO("Mounting new url: %s", Url.c_str());
Build.reset(new MountedBuild(GameChecker->GetManifest(Url), fs::path(Settings.CacheDir) / MOUNT_FOLDER, Settings.CacheDir, SettingsGetStorageFlags(&Settings), Settings.BufferCount));
LOG_INFO("Setting up game dir");
Build->SetupGameDirectory([](unsigned int m) {}, []() {}, []() {}, cancel_flag(), Settings.ThreadCount);
void cMain::OnUpdate(const UpdateInfo& Info)
{
UpdateUrl = Info.Url;

CallAfter([=]() {
auto notif = new wxGenericNotificationMessage("New EGL2 Update!", wxString::Format("%s is now available!\n%s, %d downloads", Info.Version, Stats::GetReadableSize(Info.DownloadSize), Info.DownloadCount), this);
notif->SetIcon(wxICON(APP_ICON));
if (!notif->AddAction(42, LSTR(MAIN_NOTIF_ACTION))) {
LOG_WARN("Actions aren't supported");
}
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_ACTION, std::bind(&cMain::BeginUpdate, this));
notif->Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, std::bind(&cMain::BeginUpdate, this));
if (!notif->Show(wxNotificationMessage::Timeout_Never)) {
LOG_ERROR("Couldn't launch update notification");
}
});
}

void cMain::BeginUpdate()
{
if (UpdateUrl.empty()) {
return;
}
wxLaunchDefaultBrowser(UpdateUrl);
UpdateUrl.clear();
}
Loading

0 comments on commit d965c2e

Please sign in to comment.