Skip to content

Commit 4c24f66

Browse files
committed
Use the GitHub release as source for the autoupdater instead of the update server
1 parent b64c6c6 commit 4c24f66

File tree

5 files changed

+97
-183
lines changed

5 files changed

+97
-183
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,17 @@ list(PREPEND MACRO_TARGETS standard)
501501

502502
# Define macros
503503

504+
function (set_timestamp)
505+
string(TIMESTAMP C4UPDATE_TIMESTAMP UTC)
506+
set(C4UPDATE_BUILD_TIMESTAMP ${C4UPDATE_TIMESTAMP} PARENT_SCOPE)
507+
endfunction ()
508+
509+
set_timestamp()
510+
504511
foreach (MACRO_TARGET ${MACRO_TARGETS})
505512
target_compile_definitions(${MACRO_TARGET} PUBLIC
506513
C4_OS="${C4_OS}"
514+
C4UPDATE_BUILD_TIMESTAMP="${C4UPDATE_BUILD_TIMESTAMP}"
507515
ICONV_CONST=
508516
STD_APPUSERMODELID="LegacyClonkTeam.LegacyClonk"
509517
# A standard product name for this project which is used in window registration etc.

src/C4DownloadDlg.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void C4DownloadDlg::OnIdle()
122122
iProgress = static_cast<int32_t>(100 * HTTPClient.getDownloadedSize() / iSize);
123123
}
124124
}
125-
SetStatus(LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, GetFilename(HTTPClient.getURL())).c_str(), iProgress);
125+
SetStatus(LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADINGFILE, filename).c_str(), iProgress);
126126
}
127127

128128
void C4DownloadDlg::UserClose(bool fOK)
@@ -149,7 +149,8 @@ bool C4DownloadDlg::ShowModal(C4GUI::Screen *pScreen, const char *szURL, const c
149149
// show dlg
150150
if (!Show(pScreen, true)) return false;
151151
// start query
152-
if (!HTTPClient.Query(StdBuf{}, true)) return false;
152+
if (!HTTPClient.Query(StdBuf{}, true, {{"Accept", "application/octet-stream"}})) return false;
153+
filename = GetFilename(szSaveAsFilename);
153154
// first time status update
154155
OnIdle();
155156
// cycle until query is finished or aborted
@@ -179,7 +180,7 @@ bool C4DownloadDlg::DownloadFile(const char *szDLType, C4GUI::Screen *pScreen, c
179180
// otherwise, show an appropriate error
180181
const char *szError = pDlg->GetError();
181182
if (!szError || !*szError) szError = LoadResStr(C4ResStrTableKey::IDS_PRC_UNKOWNERROR);
182-
std::string error{LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADERROR, GetFilename(szURL), szError)};
183+
std::string error{LoadResStr(C4ResStrTableKey::IDS_PRC_DOWNLOADERROR, GetFilename(szSaveAsFilename), szError)};
183184
// it's a 404: display extended message
184185
if (error.contains("404") && szNotFoundMessage)
185186
{

src/C4DownloadDlg.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class C4DownloadDlg : public C4GUI::Dialog
3535
C4GUI::ProgressBar *pProgressBar;
3636
C4GUI::Button *pCancelBtn;
3737
const char *szError;
38+
const char *filename{nullptr};
3839
#ifdef _WIN32
3940
bool fWinSock;
4041
#endif

src/C4UpdateDlg.cpp

Lines changed: 83 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323

2424
#include <C4Log.h>
2525

26+
#include <ctime>
2627
#include <format>
28+
#include <iomanip>
29+
#include <sstream>
2730

2831
#ifdef _WIN32
2932
#include <shellapi.h>
@@ -36,6 +39,12 @@
3639
#include <arpa/inet.h>
3740
#endif
3841

42+
#include <boost/json.hpp>
43+
44+
#define C4XVERBUILD_STRING C4XVERTOC4XVERS(C4XVERBUILD)
45+
46+
static constexpr auto UpdateFileName = "lc_" C4XVERBUILD_STRING "_" C4_OS ".c4u";
47+
3948
int C4UpdateDlg::pid;
4049
int C4UpdateDlg::c4group_output[2];
4150
bool C4UpdateDlg::succeeded;
@@ -125,48 +134,15 @@ void C4UpdateDlg::UpdateText()
125134

126135
// static update application function
127136

128-
bool C4UpdateDlg::DoUpdate(const C4GameVersion &rUpdateVersion, C4GUI::Screen *pScreen)
137+
bool C4UpdateDlg::DoUpdate(const std::int64_t assetId, C4GUI::Screen *pScreen)
129138
{
130-
std::string updateFile;
131-
std::string updateURL;
132-
// Double check for valid update
133-
if (!IsValidUpdate(rUpdateVersion)) return false;
134-
// 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.
135-
if (rUpdateVersion.iVer[2] > C4XVER3)
136-
updateFile = std::format(C4CFG_UpdateMajor, rUpdateVersion.iVer[0], rUpdateVersion.iVer[1], C4XVER3 + 1, 0, C4_OS);
137-
// Objects version match: engine update only
138-
else if ((rUpdateVersion.iVer[2] == C4XVER3) && (rUpdateVersion.iVer[3] == C4XVER4))
139-
updateFile = std::format(C4CFG_UpdateEngine, rUpdateVersion.iBuild, C4_OS);
140-
// Objects version mismatch: full objects update
141-
else
142-
updateFile = std::format(C4CFG_UpdateObjects, rUpdateVersion.iVer[0], rUpdateVersion.iVer[1], rUpdateVersion.iVer[2], rUpdateVersion.iVer[3], rUpdateVersion.iBuild, C4_OS);
143-
// Compose full update URL by using update server address and replacing last path element name with the update file
144-
int iLastElement = SCharLastPos('/', Config.Network.UpdateServerAddress);
145-
if (iLastElement > -1)
146-
{
147-
updateURL = Config.Network.UpdateServerAddress;
148-
updateURL.resize(iLastElement + 1);
149-
updateURL += updateFile;
150-
}
151-
else
152-
{
153-
// No last slash in update server address?
154-
// Append update file as new segment instead - maybe somebody wants
155-
// to set up their update server this way
156-
updateURL = Config.Network.UpdateServerAddress;
157-
updateURL += '/';
158-
updateURL += updateFile;
159-
}
160-
// Determine local filename for update group
161-
StdStrBuf strLocalFilename; strLocalFilename.Copy(GetFilename(updateFile.c_str()));
162-
// Download update group to temp path
163-
strLocalFilename.Copy(Config.AtTempPath(strLocalFilename.getData()));
139+
const std::string filePath{Config.AtTempPath(UpdateFileName)};
164140
// Download update group
165-
if (!C4DownloadDlg::DownloadFile(LoadResStr(C4ResStrTableKey::IDS_TYPE_UPDATE), pScreen, updateURL.c_str(), strLocalFilename.getData(), LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATENOTAVAILABLE)))
141+
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)))
166142
// Download failed (return success, because error message has already been shown)
167143
return true;
168144
// Apply downloaded update
169-
return ApplyUpdate(strLocalFilename.getData(), true, pScreen);
145+
return ApplyUpdate(filePath.c_str(), true, pScreen);
170146
}
171147

172148
bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4GUI::Screen *pScreen)
@@ -243,22 +219,6 @@ bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4G
243219
return succeeded;
244220
}
245221

246-
bool C4UpdateDlg::IsValidUpdate(const C4GameVersion &rNewVer)
247-
{
248-
// Engine or game version mismatch
249-
if ((rNewVer.iVer[0] != C4XVER1) || (rNewVer.iVer[1] != C4XVER2)) return false;
250-
// Objects major is higher...
251-
if ((rNewVer.iVer[2] > C4XVER3)
252-
// ...or objects major is the same and objects minor is higher...
253-
|| ((rNewVer.iVer[2] == C4XVER3) && (rNewVer.iVer[3] > C4XVER4))
254-
// ...or build number is higher
255-
|| (rNewVer.iBuild > C4XVERBUILD))
256-
// Update okay
257-
return true;
258-
// Otherwise
259-
return false;
260-
}
261-
262222
bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
263223
{
264224
// Automatic update only once a day
@@ -268,23 +228,43 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
268228
// Store the time of this update check (whether it's automatic or not or successful or not)
269229
Config.Network.LastUpdateTime = static_cast<int32_t>(time(nullptr));
270230
// Get current update version from server
271-
C4GameVersion UpdateVersion;
272231
C4GUI::Dialog *pWaitDlg = nullptr;
232+
233+
const auto showErrorDialog = [pScreen](const char *const body, const char *const title)
234+
{
235+
if (pScreen)
236+
{
237+
std::string message{LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED)};
238+
if (body)
239+
{
240+
message.append(": ").append(body);
241+
}
242+
243+
pScreen->ShowMessage(message.c_str(), title, C4GUI::Ico_Ex_Update);
244+
}
245+
246+
return false;
247+
};
248+
249+
C4Network2HTTPClient client;
250+
251+
if (!client.Init() || !client.SetServer("https://api.github.com/repos/legacyclonk/LegacyClonk/releases/tags/v" C4XVERBUILD_STRING "_multiple_sections_open_beta"))
252+
{
253+
return showErrorDialog(client.GetError(), LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES));
254+
}
255+
273256
if (pScreen && C4GUI::IsGUIValid())
274257
{
275-
pWaitDlg = new C4GUI::MessageDialog(LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES), Config.Network.UpdateServerAddress, C4GUI::MessageDialog::btnAbort, C4GUI::Ico_Ex_Update, C4GUI::MessageDialog::dsRegular);
258+
pWaitDlg = new C4GUI::MessageDialog(LoadResStr(C4ResStrTableKey::IDS_MSG_LOOKINGFORUPDATES), client.getServerName(), C4GUI::MessageDialog::btnAbort, C4GUI::Ico_Ex_Update, C4GUI::MessageDialog::dsRegular);
276259
pWaitDlg->SetDelOnClose(false);
277260
pScreen->ShowDialog(pWaitDlg, false);
278261
}
279-
C4Network2VersionInfoClient VerChecker;
262+
280263
bool fSuccess = false, fAborted = false;
281-
StdStrBuf strUpdateRedirect;
282-
const std::string query{std::format("{}?action=version", Config.Network.UpdateServerAddress)};
283-
if (VerChecker.Init() && VerChecker.SetServer(query) && VerChecker.QueryVersion())
264+
if (client.Query(StdBuf{}, false))
284265
{
285-
Application.InteractiveThread.AddProc(&VerChecker);
286266
// wait for version check to terminate
287-
while (VerChecker.isBusy())
267+
while (client.isBusy())
288268
{
289269
C4AppHandleResult hr;
290270
while ((hr = Application.HandleMessage()) == HR_Message) {}
@@ -295,73 +275,73 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
295275
}
296276
if (!fAborted)
297277
{
298-
fSuccess = VerChecker.GetVersion(&UpdateVersion);
299-
VerChecker.GetRedirect(strUpdateRedirect);
278+
fSuccess = client.isSuccess();
300279
}
301-
Application.InteractiveThread.RemoveProc(&VerChecker);
302280
}
303281
if (pScreen && C4GUI::IsGUIValid()) delete pWaitDlg;
304282
// User abort
305283
if (fAborted)
306284
{
307285
return false;
308286
}
309-
// Error during update check
287+
310288
if (!fSuccess)
311289
{
312-
if (pScreen)
313-
{
314-
StdStrBuf sError; sError.Copy(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED));
315-
const char *szErrMsg = VerChecker.GetError();
316-
if (szErrMsg)
317-
{
318-
sError.Append(": ");
319-
sError.Append(szErrMsg);
320-
}
321-
pScreen->ShowMessage(sError.getData(), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
322-
}
323-
return false;
290+
return showErrorDialog(client.GetError(), client.getServerName());
324291
}
325292

326-
// UpdateServer Redirection
327-
if ((strUpdateRedirect.getLength() > 0) && !SEqual(strUpdateRedirect.getData(), Config.Network.UpdateServerAddress))
293+
std::tm timePoint{};
294+
std::int64_t assetId{0};
295+
296+
try
328297
{
329-
// this is a new redirect. Inform the user and auto-change servers if desired
330-
const char *newServer = strUpdateRedirect.getData();
331-
if (pScreen)
298+
const auto parseDateTime = [](const std::string_view string)
332299
{
333-
const std::string message{LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECTMSG, newServer)};
334-
if (!pScreen->ShowMessageModal(message.c_str(), LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECT), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_OfficialServer))
300+
std::stringstream stream;
301+
stream << string;
302+
303+
std::tm timePoint;
304+
stream >> std::get_time(&timePoint, "%Y-%m-%dT%TZ");
305+
timePoint.tm_isdst = -1;
306+
return timePoint;
307+
};
308+
309+
const auto assets = boost::json::parse(client.getResultString().getData()).as_object().at("assets").as_array();
310+
311+
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())
312+
{
313+
const auto &asset = it->get_object();
314+
315+
auto updatedAt = parseDateTime(static_cast<std::string_view>(asset.at("updated_at").as_string()));
316+
auto engineBuiltAt = parseDateTime(C4UPDATE_BUILD_TIMESTAMP);
317+
318+
if (std::mktime(&updatedAt) - std::mktime(&engineBuiltAt) > 10 * 60)
335319
{
336-
// apply new server setting
337-
SCopy(newServer, Config.Network.UpdateServerAddress, CFG_MaxString);
338-
Config.Save();
339-
pScreen->ShowMessageModal(LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECTDONE), LoadResStr(C4ResStrTableKey::IDS_NET_SERVERREDIRECT), C4GUI::MessageDialog::btnOK, C4GUI::Ico_OfficialServer);
340-
// abort the update check - user should try again
341-
return false;
320+
timePoint = updatedAt;
321+
assetId = asset.at("id").to_number<std::int64_t>();
342322
}
343323
}
344-
else
345-
{
346-
SCopy(newServer, Config.Network.UpdateServerAddress, CFG_MaxString);
347-
Config.Save();
348-
return false;
349-
}
324+
}
325+
catch (const std::exception &e)
326+
{
327+
return showErrorDialog(e.what(), client.getServerName());
350328
}
351329

352330
if (!pScreen)
353331
{
354-
return C4UpdateDlg::IsValidUpdate(UpdateVersion);
332+
return timePoint.tm_year > 0;
355333
}
356334

357335
// Applicable update available
358-
if (C4UpdateDlg::IsValidUpdate(UpdateVersion))
336+
if (timePoint.tm_year > 0)
359337
{
360338
// Prompt user, then apply update
361-
const std::string message{LoadResStr(C4ResStrTableKey::IDS_MSG_ANUPDATETOVERSIONISAVAILA, UpdateVersion.GetString())};
362-
if (pScreen->ShowMessageModal(message.c_str(), Config.Network.UpdateServerAddress, C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Ex_Update))
363-
if (!DoUpdate(UpdateVersion, pScreen))
364-
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
339+
std::ostringstream stream;
340+
stream << std::put_time(&timePoint, "%c");
341+
const std::string msg{LoadResStr(C4ResStrTableKey::IDS_MSG_ANUPDATETOVERSIONISAVAILA, stream.str())};
342+
if (pScreen->ShowMessageModal(msg.c_str(), client.getServerName(), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Ex_Update))
343+
if (!DoUpdate(assetId, pScreen))
344+
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_UPDATEFAILED), client.getServerName(), C4GUI::Ico_Ex_Update);
365345
else
366346
return true;
367347
}
@@ -370,72 +350,8 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
370350
{
371351
// Message (if not automatic)
372352
if (!fAutomatic)
373-
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_NOUPDATEAVAILABLEFORTHISV), Config.Network.UpdateServerAddress, C4GUI::Ico_Ex_Update);
353+
pScreen->ShowMessage(LoadResStr(C4ResStrTableKey::IDS_MSG_NOUPDATEAVAILABLEFORTHISV), client.getServerName(), C4GUI::Ico_Ex_Update);
374354
}
375355
// Done (and no update has been done)
376356
return false;
377357
}
378-
379-
// *** C4Network2VersionInfoClient
380-
381-
bool C4Network2VersionInfoClient::QueryVersion()
382-
{
383-
// Perform an Query query
384-
return Query(StdBuf{}, false);
385-
}
386-
387-
bool C4Network2VersionInfoClient::GetVersion(C4GameVersion *piVerOut)
388-
{
389-
// Sanity check
390-
if (isBusy() || !isSuccess()) return false;
391-
// Parse response
392-
piVerOut->Set("", 0, 0, 0, 0, 0);
393-
try
394-
{
395-
CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(
396-
mkNamingAdapt(
397-
mkParAdapt(*piVerOut, false),
398-
"Version"),
399-
C4ENGINENAME), getResultString());
400-
}
401-
catch (const StdCompiler::Exception &e)
402-
{
403-
SetError(e.what());
404-
return false;
405-
}
406-
// validate version
407-
if (!piVerOut->iVer[0])
408-
{
409-
SetError(LoadResStr(C4ResStrTableKey::IDS_ERR_INVALIDREPLYFROMSERVER));
410-
return false;
411-
}
412-
// done; version OK!
413-
return true;
414-
}
415-
416-
bool C4Network2VersionInfoClient::GetRedirect(StdStrBuf &rRedirect)
417-
{
418-
// Sanity check
419-
if (isBusy() || !isSuccess()) return false;
420-
StdStrBuf strUpdateRedirect;
421-
try
422-
{
423-
CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(
424-
mkNamingAdapt(mkParAdapt(strUpdateRedirect, StdCompiler::RCT_All), "UpdateServerRedirect", ""),
425-
C4ENGINENAME), getResultString());
426-
}
427-
catch (const StdCompiler::Exception &e)
428-
{
429-
SetError(e.what());
430-
return false;
431-
}
432-
// did we get something?
433-
if (strUpdateRedirect.getLength() > 0)
434-
{
435-
rRedirect.Copy(strUpdateRedirect);
436-
return true;
437-
}
438-
// no, we didn't
439-
rRedirect.Clear();
440-
return false;
441-
}

0 commit comments

Comments
 (0)