Skip to content

Commit

Permalink
Use the GitHub release as source for the autoupdater instead of the u…
Browse files Browse the repository at this point in the history
…pdate server
  • Loading branch information
Fulgen301 committed Jul 27, 2024
1 parent 2e59329 commit 8285f54
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 183 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -501,9 +501,17 @@ list(PREPEND MACRO_TARGETS standard)

# Define macros

function (set_timestamp)
string(TIMESTAMP C4UPDATE_TIMESTAMP UTC)
set(C4UPDATE_BUILD_TIMESTAMP ${C4UPDATE_TIMESTAMP} PARENT_SCOPE)
endfunction ()

set_timestamp()

foreach (MACRO_TARGET ${MACRO_TARGETS})
target_compile_definitions(${MACRO_TARGET} PUBLIC
C4_OS="${C4_OS}"
C4UPDATE_BUILD_TIMESTAMP="${C4UPDATE_BUILD_TIMESTAMP}"
ICONV_CONST=
STD_APPUSERMODELID="LegacyClonkTeam.LegacyClonk"
# A standard product name for this project which is used in window registration etc.
Expand Down
7 changes: 4 additions & 3 deletions src/C4DownloadDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void C4DownloadDlg::OnIdle()
iProgress = static_cast<int32_t>(100 * HTTPClient.getDownloadedSize() / iSize);
}
}
SetStatus(LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, GetFilename(HTTPClient.getURL())).c_str(), iProgress);
SetStatus(LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, filename).c_str(), iProgress);
}

void C4DownloadDlg::UserClose(bool fOK)
Expand All @@ -149,7 +149,8 @@ bool C4DownloadDlg::ShowModal(C4GUI::Screen *pScreen, const char *szURL, const c
// show dlg
if (!Show(pScreen, true)) return false;
// start query
if (!HTTPClient.Query(StdBuf{}, true)) return false;
if (!HTTPClient.Query(StdBuf{}, true, {{"Accept", "application/octet-stream"}})) return false;
filename = GetFilename(szSaveAsFilename);
// first time status update
OnIdle();
// cycle until query is finished or aborted
Expand Down Expand Up @@ -179,7 +180,7 @@ bool C4DownloadDlg::DownloadFile(const char *szDLType, C4GUI::Screen *pScreen, c
// otherwise, show an appropriate error
const char *szError = pDlg->GetError();
if (!szError || !*szError) szError = LoadResStr(C4ResStrTableKey::IDS_PRC_UNKOWNERROR);
std::string error{LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADERROR, GetFilename(szURL), szError)};
std::string error{LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADERROR, GetFilename(szSaveAsFilename), szError)};
// it's a 404: display extended message
if (error.contains("404") && szNotFoundMessage)
{
Expand Down
1 change: 1 addition & 0 deletions src/C4DownloadDlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class C4DownloadDlg : public C4GUI::Dialog
C4GUI::ProgressBar *pProgressBar;
C4GUI::Button *pCancelBtn;
const char *szError;
const char *filename{nullptr};
#ifdef _WIN32
bool fWinSock;
#endif
Expand Down
250 changes: 83 additions & 167 deletions src/C4UpdateDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@

#include <C4Log.h>

#include <ctime>
#include <format>
#include <iomanip>
#include <sstream>

#ifdef _WIN32
#include <shellapi.h>
Expand All @@ -36,6 +39,12 @@
#include <arpa/inet.h>
#endif

#include <boost/json.hpp>

#define C4XVERBUILD_STRING C4XVERTOC4XVERS(C4XVERBUILD)

static constexpr auto UpdateFileName = "lc_" C4XVERBUILD_STRING "_" C4_OS ".c4u";

int C4UpdateDlg::pid;
int C4UpdateDlg::c4group_output[2];
bool C4UpdateDlg::succeeded;
Expand Down Expand Up @@ -125,48 +134,15 @@ void C4UpdateDlg::UpdateText()

// static update application function

bool C4UpdateDlg::DoUpdate(const C4GameVersion &rUpdateVersion, C4GUI::Screen *pScreen)
bool C4UpdateDlg::DoUpdate(const std::int64_t assetId, C4GUI::Screen *pScreen)
{
std::string updateFile;
std::string updateURL;
// Double check for valid update
if (!IsValidUpdate(rUpdateVersion)) return false;
// Objects major update: we will update to the first minor of the next major version - we can not skip major versions or jump directly to a higher minor of the next major version.
if (rUpdateVersion.iVer[2] > C4XVER3)
updateFile = std::format(C4CFG_UpdateMajor, rUpdateVersion.iVer[0], rUpdateVersion.iVer[1], C4XVER3 + 1, 0, C4_OS);
// Objects version match: engine update only
else if ((rUpdateVersion.iVer[2] == C4XVER3) && (rUpdateVersion.iVer[3] == C4XVER4))
updateFile = std::format(C4CFG_UpdateEngine, rUpdateVersion.iBuild, C4_OS);
// Objects version mismatch: full objects update
else
updateFile = std::format(C4CFG_UpdateObjects, rUpdateVersion.iVer[0], rUpdateVersion.iVer[1], rUpdateVersion.iVer[2], rUpdateVersion.iVer[3], rUpdateVersion.iBuild, C4_OS);
// Compose full update URL by using update server address and replacing last path element name with the update file
int iLastElement = SCharLastPos('/', Config.Network.UpdateServerAddress);
if (iLastElement > -1)
{
updateURL = Config.Network.UpdateServerAddress;
updateURL.resize(iLastElement + 1);
updateURL += updateFile;
}
else
{
// No last slash in update server address?
// Append update file as new segment instead - maybe somebody wants
// to set up their update server this way
updateURL = Config.Network.UpdateServerAddress;
updateURL += '/';
updateURL += updateFile;
}
// Determine local filename for update group
StdStrBuf strLocalFilename; strLocalFilename.Copy(GetFilename(updateFile.c_str()));
// Download update group to temp path
strLocalFilename.Copy(Config.AtTempPath(strLocalFilename.getData()));
const std::string filePath{Config.AtTempPath(UpdateFileName)};
// Download update group
if (!C4DownloadDlg::DownloadFile(LoadResStr(C4ResStrTableKey::IDS_TYPE_UPDATE), pScreen, updateURL.c_str(), strLocalFilename.getData(), LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATENOTAVAILABLE)))
if (!C4DownloadDlg::DownloadFile(LoadResStr(C4ResStrTableKey::IDS_TYPE_UPDATE), pScreen, std::format("https://api.github.com/repos/legacyclonk/LegacyClonk/releases/assets/{}", assetId).c_str(), filePath.c_str(), LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATENOTAVAILABLE)))
// Download failed (return success, because error message has already been shown)
return true;
// Apply downloaded update
return ApplyUpdate(strLocalFilename.getData(), true, pScreen);
return ApplyUpdate(filePath.c_str(), true, pScreen);
}

bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4GUI::Screen *pScreen)
Expand Down Expand Up @@ -243,22 +219,6 @@ bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4G
return succeeded;
}

bool C4UpdateDlg::IsValidUpdate(const C4GameVersion &rNewVer)
{
// Engine or game version mismatch
if ((rNewVer.iVer[0] != C4XVER1) || (rNewVer.iVer[1] != C4XVER2)) return false;
// Objects major is higher...
if ((rNewVer.iVer[2] > C4XVER3)
// ...or objects major is the same and objects minor is higher...
|| ((rNewVer.iVer[2] == C4XVER3) && (rNewVer.iVer[3] > C4XVER4))
// ...or build number is higher
|| (rNewVer.iBuild > C4XVERBUILD))
// Update okay
return true;
// Otherwise
return false;
}

bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
{
// Automatic update only once a day
Expand All @@ -268,23 +228,43 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
// Store the time of this update check (whether it's automatic or not or successful or not)
Config.Network.LastUpdateTime = static_cast<int32_t>(time(nullptr));
// Get current update version from server
C4GameVersion UpdateVersion;
C4GUI::Dialog *pWaitDlg = nullptr;

const auto showErrorDialog = [pScreen](const char *const body, const char *const title)
{
if (pScreen)
{
std::string message{LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED)};
if (body)
{
message.append(": ").append(body);
}

pScreen->ShowMessage(message.c_str(), title, C4GUI::Ico_Ex_Update);
}

return false;
};

C4Network2HTTPClient client;

if (!client.Init() || !client.SetServer("https://api.github.com/repos/legacyclonk/LegacyClonk/releases/tags/v" C4XVERBUILD_STRING "_multiple_sections_open_beta"))
{
return showErrorDialog(client.GetError(), LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES));
}

if (pScreen && C4GUI::IsGUIValid())
{
pWaitDlg = new C4GUI::MessageDialog(LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES), Config.Network.UpdateServerAddress, C4GUI::MessageDialog::btnAbort, C4GUI::Ico_Ex_Update, C4GUI::MessageDialog::dsRegular);
pWaitDlg = new C4GUI::MessageDialog(LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES), client.getServerName(), C4GUI::MessageDialog::btnAbort, C4GUI::Ico_Ex_Update, C4GUI::MessageDialog::dsRegular);
pWaitDlg->SetDelOnClose(false);
pScreen->ShowDialog(pWaitDlg, false);
}
C4Network2VersionInfoClient VerChecker;

bool fSuccess = false, fAborted = false;
StdStrBuf strUpdateRedirect;
const std::string query{std::format("{}?action=version", Config.Network.UpdateServerAddress)};
if (VerChecker.Init() && VerChecker.SetServer(query) && VerChecker.QueryVersion())
if (client.Query(StdBuf{}, false))
{
Application.InteractiveThread.AddProc(&VerChecker);
// wait for version check to terminate
while (VerChecker.isBusy())
while (client.isBusy())
{
C4AppHandleResult hr;
while ((hr = Application.HandleMessage()) == HR_Message) {}
Expand All @@ -295,73 +275,73 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
}
if (!fAborted)
{
fSuccess = VerChecker.GetVersion(&UpdateVersion);
VerChecker.GetRedirect(strUpdateRedirect);
fSuccess = client.isSuccess();
}
Application.InteractiveThread.RemoveProc(&VerChecker);
}
if (pScreen && C4GUI::IsGUIValid()) delete pWaitDlg;
// User abort
if (fAborted)
{
return false;
}
// Error during update check

if (!fSuccess)
{
if (pScreen)
{
StdStrBuf sError; sError.Copy(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED));
const char *szErrMsg = VerChecker.GetError();
if (szErrMsg)
{
sError.Append(": ");
sError.Append(szErrMsg);
}
pScreen->ShowMessage(sError.getData(), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
}
return false;
return showErrorDialog(client.GetError(), client.getServerName());
}

// UpdateServer Redirection
if ((strUpdateRedirect.getLength() > 0) && !SEqual(strUpdateRedirect.getData(), Config.Network.UpdateServerAddress))
std::tm timePoint{};
std::int64_t assetId{0};

try
{
// this is a new redirect. Inform the user and auto-change servers if desired
const char *newServer = strUpdateRedirect.getData();
if (pScreen)
const auto parseDateTime = [](const std::string_view string)
{
const std::string message{LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECTMSG, newServer)};
if (!pScreen->ShowMessageModal(message.c_str(), LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECT), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_OfficialServer))
std::stringstream stream;
stream << string;

std::tm timePoint;
stream >> std::get_time(&timePoint, "%Y-%m-%dT%TZ");
timePoint.tm_isdst = -1;
return timePoint;
};

const auto assets = boost::json::parse(client.getResultString().getData()).as_object().at("assets").as_array();

if (const auto it = std::find_if(assets.begin(), assets.end(), [](const auto &asset) { return asset.as_object().at("name").as_string() == UpdateFileName; }); it != assets.end())
{
const auto &asset = it->get_object();

auto updatedAt = parseDateTime(static_cast<std::string_view>(asset.at("updated_at").as_string()));
auto engineBuiltAt = parseDateTime(C4UPDATE_BUILD_TIMESTAMP);

if (std::mktime(&updatedAt) - std::mktime(&engineBuiltAt) > 10 * 60)
{
// apply new server setting
SCopy(newServer, Config.Network.UpdateServerAddress, CFG_MaxString);
Config.Save();
pScreen->ShowMessageModal(LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECTDONE), LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECT), C4GUI::MessageDialog::btnOK, C4GUI::Ico_OfficialServer);
// abort the update check - user should try again
return false;
timePoint = updatedAt;
assetId = asset.at("id").to_number<std::int64_t>();
}
}
else
{
SCopy(newServer, Config.Network.UpdateServerAddress, CFG_MaxString);
Config.Save();
return false;
}
}
catch (const std::exception &e)
{
return showErrorDialog(e.what(), client.getServerName());
}

if (!pScreen)
{
return C4UpdateDlg::IsValidUpdate(UpdateVersion);
return timePoint.tm_year > 0;
}

// Applicable update available
if (C4UpdateDlg::IsValidUpdate(UpdateVersion))
if (timePoint.tm_year > 0)
{
// Prompt user, then apply update
const std::string message{LoadResStr(C4ResStrTableKey::IDS_MSG_ANUPDATETOVERSIONISAVAILA, UpdateVersion.GetString())};
if (pScreen->ShowMessageModal(message.c_str(), Config.Network.UpdateServerAddress, C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Ex_Update))
if (!DoUpdate(UpdateVersion, pScreen))
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
std::ostringstream stream;
stream << std::put_time(&timePoint, "%c");
const std::string msg{LoadResStr(C4ResStrTableKey::IDS_MSG_ANUPDATETOVERSIONISAVAILA, stream.str())};
if (pScreen->ShowMessageModal(msg.c_str(), client.getServerName(), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Ex_Update))
if (!DoUpdate(assetId, pScreen))
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED), client.getServerName(), C4GUI::Ico_Ex_Update);
else
return true;
}
Expand All @@ -370,72 +350,8 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
{
// Message (if not automatic)
if (!fAutomatic)
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_NOUPDATEAVAILABLEFORTHISV), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_NOUPDATEAVAILABLEFORTHISV), client.getServerName(), C4GUI::Ico_Ex_Update);
}
// Done (and no update has been done)
return false;
}

// *** C4Network2VersionInfoClient

bool C4Network2VersionInfoClient::QueryVersion()
{
// Perform an Query query
return Query(StdBuf{}, false);
}

bool C4Network2VersionInfoClient::GetVersion(C4GameVersion *piVerOut)
{
// Sanity check
if (isBusy() || !isSuccess()) return false;
// Parse response
piVerOut->Set("", 0, 0, 0, 0, 0);
try
{
CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(
mkNamingAdapt(
mkParAdapt(*piVerOut, false),
"Version"),
C4ENGINENAME), getResultString());
}
catch (const StdCompiler::Exception &e)
{
SetError(e.what());
return false;
}
// validate version
if (!piVerOut->iVer[0])
{
SetError(LoadResStr(C4ResStrTableKey::IDS_ERR_INVALIDREPLYFROMSERVER));
return false;
}
// done; version OK!
return true;
}

bool C4Network2VersionInfoClient::GetRedirect(StdStrBuf &rRedirect)
{
// Sanity check
if (isBusy() || !isSuccess()) return false;
StdStrBuf strUpdateRedirect;
try
{
CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(
mkNamingAdapt(mkParAdapt(strUpdateRedirect, StdCompiler::RCT_All), "UpdateServerRedirect", ""),
C4ENGINENAME), getResultString());
}
catch (const StdCompiler::Exception &e)
{
SetError(e.what());
return false;
}
// did we get something?
if (strUpdateRedirect.getLength() > 0)
{
rRedirect.Copy(strUpdateRedirect);
return true;
}
// no, we didn't
rRedirect.Clear();
return false;
}
Loading

0 comments on commit 8285f54

Please sign in to comment.