23
23
24
24
#include < C4Log.h>
25
25
26
+ #include < ctime>
26
27
#include < format>
28
+ #include < iomanip>
29
+ #include < sstream>
27
30
28
31
#ifdef _WIN32
29
32
#include < shellapi.h>
36
39
#include < arpa/inet.h>
37
40
#endif
38
41
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
+
39
48
int C4UpdateDlg::pid;
40
49
int C4UpdateDlg::c4group_output[2 ];
41
50
bool C4UpdateDlg::succeeded;
@@ -125,48 +134,15 @@ void C4UpdateDlg::UpdateText()
125
134
126
135
// static update application function
127
136
128
- bool C4UpdateDlg::DoUpdate (const C4GameVersion &rUpdateVersion , C4GUI::Screen *pScreen)
137
+ bool C4UpdateDlg::DoUpdate (const std:: int64_t assetId , C4GUI::Screen *pScreen)
129
138
{
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)};
164
140
// 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)))
166
142
// Download failed (return success, because error message has already been shown)
167
143
return true ;
168
144
// Apply downloaded update
169
- return ApplyUpdate (strLocalFilename. getData (), true , pScreen);
145
+ return ApplyUpdate (filePath. c_str (), true , pScreen);
170
146
}
171
147
172
148
bool C4UpdateDlg::ApplyUpdate (const char *strUpdateFile, bool fDeleteUpdate , C4GUI::Screen *pScreen)
@@ -243,22 +219,6 @@ bool C4UpdateDlg::ApplyUpdate(const char *strUpdateFile, bool fDeleteUpdate, C4G
243
219
return succeeded;
244
220
}
245
221
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
-
262
222
bool C4UpdateDlg::CheckForUpdates (C4GUI::Screen *pScreen, bool fAutomatic )
263
223
{
264
224
// Automatic update only once a day
@@ -268,23 +228,43 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
268
228
// Store the time of this update check (whether it's automatic or not or successful or not)
269
229
Config.Network .LastUpdateTime = static_cast <int32_t >(time (nullptr ));
270
230
// Get current update version from server
271
- C4GameVersion UpdateVersion;
272
231
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
+
273
256
if (pScreen && C4GUI::IsGUIValid ())
274
257
{
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);
276
259
pWaitDlg->SetDelOnClose (false );
277
260
pScreen->ShowDialog (pWaitDlg, false );
278
261
}
279
- C4Network2VersionInfoClient VerChecker;
262
+
280
263
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 ))
284
265
{
285
- Application.InteractiveThread .AddProc (&VerChecker);
286
266
// wait for version check to terminate
287
- while (VerChecker .isBusy ())
267
+ while (client .isBusy ())
288
268
{
289
269
C4AppHandleResult hr;
290
270
while ((hr = Application.HandleMessage ()) == HR_Message) {}
@@ -295,73 +275,73 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
295
275
}
296
276
if (!fAborted )
297
277
{
298
- fSuccess = VerChecker.GetVersion (&UpdateVersion);
299
- VerChecker.GetRedirect (strUpdateRedirect);
278
+ fSuccess = client.isSuccess ();
300
279
}
301
- Application.InteractiveThread .RemoveProc (&VerChecker);
302
280
}
303
281
if (pScreen && C4GUI::IsGUIValid ()) delete pWaitDlg;
304
282
// User abort
305
283
if (fAborted )
306
284
{
307
285
return false ;
308
286
}
309
- // Error during update check
287
+
310
288
if (!fSuccess )
311
289
{
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 ());
324
291
}
325
292
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
328
297
{
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)
332
299
{
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 )
335
319
{
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 >();
342
322
}
343
323
}
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 ());
350
328
}
351
329
352
330
if (!pScreen)
353
331
{
354
- return C4UpdateDlg::IsValidUpdate (UpdateVersion) ;
332
+ return timePoint. tm_year > 0 ;
355
333
}
356
334
357
335
// Applicable update available
358
- if (C4UpdateDlg::IsValidUpdate (UpdateVersion) )
336
+ if (timePoint. tm_year > 0 )
359
337
{
360
338
// 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);
365
345
else
366
346
return true ;
367
347
}
@@ -370,72 +350,8 @@ bool C4UpdateDlg::CheckForUpdates(C4GUI::Screen *pScreen, bool fAutomatic)
370
350
{
371
351
// Message (if not automatic)
372
352
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);
374
354
}
375
355
// Done (and no update has been done)
376
356
return false ;
377
357
}
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