Freeciv21
Develop your civilization from humble roots to a global empire
meta.cpp
Go to the documentation of this file.
1 /*__ ___ ***************************************
2 / \ / \ Copyright (c) 1996-2020 Freeciv21 and Freeciv
3 \_ \ / __/ contributors. This file is part of Freeciv21.
4  _\ \ / /__ Freeciv21 is free software: you can redistribute it
5  \___ \____/ __/ and/or modify it under the terms of the GNU General
6  \_ _/ Public License as published by the Free Software
7  | @ @ \_ Foundation, either version 3 of the License,
8  | or (at your option) any later version.
9  _/ /\ You should have received a copy of the GNU
10  /o) (o/\ \_ General Public License along with Freeciv21.
11  \_____/ / If not, see https://www.gnu.org/licenses/.
12  \____/ ********************************************************/
13 
14 #include <fc_config.h>
15 
16 // Qt
17 #include <QEventLoop>
18 #include <QNetworkAccessManager>
19 #include <QNetworkReply>
20 #include <QNetworkRequest>
21 #include <QUrl>
22 #include <QUrlQuery>
23 
24 // utility
25 #include "fcintl.h"
26 #include "fcthread.h"
27 #include "log.h"
28 #include "support.h"
29 #include "timing.h"
30 
31 // common
32 #include "capstr.h"
33 #include "connection.h"
34 #include "game.h"
35 #include "nation.h"
36 #include "version.h"
37 
38 // server
39 #include "console.h"
40 #include "plrhand.h"
41 #include "settings.h"
42 #include "srv_main.h"
43 
44 #include "meta.h"
45 
46 static bool server_is_open = false;
47 static bool persistent_meta_connection = false;
48 static int meta_retry_wait = 0;
49 
50 static char meta_patches[256] = "";
51 static char meta_message[256] = "";
52 
53 Q_GLOBAL_STATIC(fcThread, meta_srv_thread);
54 
58 const char *default_meta_patches_string() { return "none"; }
59 
64 {
65 #if IS_DEVEL_VERSION
66  return "development version: beware";
67 #else // IS_DEVEL_VERSION
68  return "-";
69 #endif // IS_DEVEL_VERSION
70 }
71 
75 const char *get_meta_patches_string() { return meta_patches; }
76 
80 const char *get_meta_message_string() { return meta_message; }
81 
85 static const char *get_meta_type_string()
86 {
87  if (game.server.meta_info.type[0] != '\0') {
88  return game.server.meta_info.type;
89  }
90 
91  return nullptr;
92 }
93 
98 {
99  if (game.server.meta_info.user_message[0] != '\0') {
100  return game.server.meta_info.user_message;
101  }
102 
103  return nullptr;
104 }
105 
112 void maybe_automatic_meta_message(const char *automatic)
113 {
114  const char *user_message;
115 
116  user_message = get_user_meta_message_string();
117 
118  if (user_message == nullptr) {
119  // No user message
120  if (automatic != nullptr) {
121  set_meta_message_string(automatic);
122  }
123  return;
124  }
125 
126  set_meta_message_string(user_message);
127 }
128 
132 void set_meta_patches_string(const char *string)
133 {
134  sz_strlcpy(meta_patches, string);
135 }
136 
140 void set_meta_message_string(const char *string)
141 {
142  sz_strlcpy(meta_message, string);
143 }
144 
148 void set_user_meta_message_string(const char *string)
149 {
150  if (string != nullptr && string[0] != '\0') {
151  sz_strlcpy(game.server.meta_info.user_message, string);
152  set_meta_message_string(string);
153  } else {
154  // Remove user meta message. We will use automatic messages instead
155  game.server.meta_info.user_message[0] = '\0';
157  }
158 }
159 
163 QString meta_addr_port() { return srvarg.metaserver_addr; }
164 
168 static void metaserver_failed()
169 {
172  _("Not reporting to the metaserver in this game."));
173  con_flush();
174 
176  } else {
177  con_puts(C_METAERROR, _("Metaserver connection currently failing."));
178  meta_retry_wait = 1;
179  }
180 }
181 
185 static inline bool meta_insert_setting(QUrlQuery *query,
186  const char *set_name)
187 {
188  const struct setting *pset = setting_by_name(set_name);
189  char buf[256];
190 
191  fc_assert_ret_val_msg(nullptr != pset, false, "Setting \"%s\" not found!",
192  set_name);
193  query->addQueryItem(QStringLiteral("vn[]"),
194  QString::fromUtf8(setting_name(pset)));
195  query->addQueryItem(
196  QStringLiteral("vv[]"),
197  QString::fromUtf8(setting_value_name(pset, false, buf, sizeof(buf))));
198  return true;
199 }
200 
204 static void send_metaserver_post(void *arg)
205 {
206  // Create a network manager
207  auto *manager = new QNetworkAccessManager;
208 
209  // Post the request
210  auto *post = static_cast<QUrlQuery *>(arg);
211 
212  QNetworkRequest request(QUrl(srvarg.metaserver_addr));
213  request.setHeader(QNetworkRequest::UserAgentHeader,
214  QLatin1String("Freeciv21/") + freeciv21_version());
215  request.setHeader(QNetworkRequest::ContentTypeHeader,
216  QLatin1String("application/x-www-form-urlencoded"));
217  auto *reply =
218  manager->post(request, post->toString(QUrl::FullyEncoded).toUtf8());
219 
220  // Wait for the reply
221  QEventLoop loop;
222 
223  QObject::connect(reply, &QNetworkReply::finished, [&] {
224  if (reply->error() != QNetworkReply::NoError) {
225  con_puts(C_METAERROR, _("Error connecting to metaserver"));
226  qCritical(_("Error message: %s"),
227  qUtf8Printable(reply->errorString()));
228  metaserver_failed();
229  }
230 
231  // Clean up
232  reply->deleteLater();
233  manager->deleteLater();
234  loop.exit();
235  });
236 
237  delete post;
238  loop.exec();
239 }
240 
244 static bool send_to_metaserver(enum meta_flag flag)
245 {
246  int players = 0;
247  int humans = 0;
248  char host[512];
249  char state[20];
250  char rs[256];
251 
252  switch (server_state()) {
253  case S_S_INITIAL:
254  sz_strlcpy(state, "Pregame");
255  break;
256  case S_S_RUNNING:
257  sz_strlcpy(state, "Running");
258  break;
259  case S_S_OVER:
260  sz_strlcpy(state, "Game Ended");
261  break;
262  }
263 
264  // get hostname
265  if (!srvarg.identity_name.isEmpty()) {
266  sz_strlcpy(host, qUtf8Printable(srvarg.identity_name));
267  } else if (fc_gethostname(host, sizeof(host)) != 0) {
268  sz_strlcpy(host, "unknown");
269  }
270 
271  if (game.control.version[0] != '\0') {
272  fc_snprintf(rs, sizeof(rs), "%s %s", game.control.name,
273  game.control.version);
274  } else {
275  sz_strlcpy(rs, game.control.name);
276  }
277 
278  QUrlQuery *post = new QUrlQuery;
279 
280  post->addQueryItem(QStringLiteral("host"), QString::fromUtf8(host));
281  post->addQueryItem(QStringLiteral("port"),
282  QStringLiteral("%1").arg(srvarg.port));
283  post->addQueryItem(QStringLiteral("state"), QString::fromUtf8(state));
284  post->addQueryItem(QStringLiteral("ruleset"), QString::fromUtf8(rs));
285 
286  if (flag == META_GOODBYE) {
287  post->addQueryItem(QStringLiteral("bye"), QStringLiteral("1"));
288  } else {
289  const char *srvtype = get_meta_type_string();
290 
291  if (srvtype != nullptr) {
292  post->addQueryItem(QStringLiteral("type"), QString::fromUtf8(srvtype));
293  }
294  post->addQueryItem(QStringLiteral("version"), freeciv21_version());
295  post->addQueryItem(QStringLiteral("patches"),
296  QString::fromUtf8(get_meta_patches_string()));
297  post->addQueryItem(QStringLiteral("capability"),
298  QString::fromUtf8(our_capability));
299 
300  post->addQueryItem(QStringLiteral("serverid"), srvarg.serverid);
301  post->addQueryItem(QStringLiteral("message"),
302  QString::fromUtf8(get_meta_message_string()));
303 
304  // NOTE: send info for ALL players or none at all.
305  if (normal_player_count() == 0) {
306  post->addQueryItem(QStringLiteral("dropplrs"), QStringLiteral("1"));
307  } else {
308  players = 0; // a counter for players_available
309  humans = 0;
310 
311  players_iterate(plr)
312  {
313  bool is_player_available = true;
314  struct connection *pconn = conn_by_user(plr->username);
315 
316  QLatin1String type;
317  if (!plr->is_alive) {
318  type = QLatin1String("Dead");
319  } else if (is_barbarian(plr)) {
320  type = QLatin1String("Barbarian");
321  } else if (is_ai(plr)) {
322  type = QLatin1String("A.I.");
323  } else if (is_human(plr)) {
324  type = QLatin1String("Human");
325  } else {
326  type = QLatin1String("-");
327  }
328 
329  post->addQueryItem(QStringLiteral("plu[]"),
330  QString::fromUtf8(plr->username));
331  post->addQueryItem(QStringLiteral("plt[]"), type);
332  post->addQueryItem(QStringLiteral("pll[]"),
333  QString::fromUtf8(player_name(plr)));
334  post->addQueryItem(
335  QStringLiteral("pln[]"),
336  QString::fromUtf8(plr->nation != NO_NATION_SELECTED
338  : "none"));
339  post->addQueryItem(
340  QStringLiteral("plf[]"),
341  QString::fromUtf8(plr->nation != NO_NATION_SELECTED
342  ? nation_of_player(plr)->flag_graphic_str
343  : "none"));
344  post->addQueryItem(QStringLiteral("plh[]"),
345  pconn ? pconn->addr : QLatin1String(""));
346 
347  /* is this player available to take?
348  * TODO: there's some duplication here with
349  * stdinhand.c:is_allowed_to_take() */
350  if (is_barbarian(plr) && !strchr(game.server.allow_take, 'b')) {
351  is_player_available = false;
352  } else if (!plr->is_alive && !strchr(game.server.allow_take, 'd')) {
353  is_player_available = false;
354  } else if (is_ai(plr)
355  && !strchr(game.server.allow_take,
356  (game.info.is_new_game ? 'A' : 'a'))) {
357  is_player_available = false;
358  } else if (is_human(plr)
359  && !strchr(game.server.allow_take,
360  (game.info.is_new_game ? 'H' : 'h'))) {
361  is_player_available = false;
362  }
363 
364  if (pconn) {
365  is_player_available = false;
366  }
367 
368  if (is_player_available) {
369  players++;
370  }
371 
372  if (is_human(plr) && plr->is_alive) {
373  humans++;
374  }
375  }
377 
378  // send the number of available players.
379  post->addQueryItem(QStringLiteral("available"),
380  QStringLiteral("%1").arg(players));
381  post->addQueryItem(QStringLiteral("humans"),
382  QStringLiteral("%1").arg(humans));
383  }
384 
385  // Send some variables: should be listed in inverted order?
386  {
387  static const char *settings[] = {"timeout", "endturn", "minplayers",
388  "maxplayers", "aifill", "allowtake",
389  "generator"};
390  int i;
391 
392  for (i = 0; i < ARRAY_SIZE(settings); i++) {
393  meta_insert_setting(post, settings[i]);
394  }
395 
396  // HACK: send the most determinant setting for the map size.
397  switch (wld.map.server.mapsize) {
398  case MAPSIZE_FULLSIZE:
399  meta_insert_setting(post, "size");
400  break;
401  case MAPSIZE_PLAYER:
402  meta_insert_setting(post, "tilesperplayer");
403  break;
404  case MAPSIZE_XYSIZE:
405  meta_insert_setting(post, "xsize");
406  meta_insert_setting(post, "ysize");
407  break;
408  }
409  }
410 
411  // Turn and year.
412  post->addQueryItem(QStringLiteral("vn[]"), QStringLiteral("turn"));
413  post->addQueryItem(QStringLiteral("vv[]"),
414  QStringLiteral("%1").arg(game.info.turn));
415  post->addQueryItem(QStringLiteral("vn[]"), QStringLiteral("year"));
416 
417  if (server_state() != S_S_INITIAL) {
418  post->addQueryItem(QStringLiteral("vv[]"),
419  QStringLiteral("%1").arg(game.info.year));
420  } else {
421  post->addQueryItem(QStringLiteral("vv[]"),
422  QStringLiteral("Calendar not set up"));
423  }
424  }
425 
426  // Send POST in new thread
427  meta_srv_thread->set_func(send_metaserver_post, post);
428  meta_srv_thread->start(QThread::NormalPriority);
429 
430  return true;
431 }
432 
437 {
438  server_is_open = false;
440 }
441 
445 bool server_open_meta(bool persistent)
446 {
447  if (meta_patches[0] == '\0') {
449  }
450  if (meta_message[0] == '\0') {
452  }
453 
454  server_is_open = true;
455  persistent_meta_connection = persistent;
456  meta_retry_wait = 0;
457 
458  return true;
459 }
460 
465 
470 {
471  static civtimer *last_send_timer = nullptr;
472  static bool want_update;
473 
474  if (!server_is_open) {
475  return false;
476  }
477 
478  // Persistent connection temporary failures handling
479  if (meta_retry_wait > 0) {
480  if (meta_retry_wait++ > 5) {
481  meta_retry_wait = 0;
482  } else {
483  return false;
484  }
485  }
486 
487  // if we're bidding farewell, ignore all timers
488  if (flag == META_GOODBYE) {
489  if (last_send_timer) {
490  timer_destroy(last_send_timer);
491  last_send_timer = nullptr;
492  }
493  send_to_metaserver(flag);
494 
495  meta_srv_thread->wait();
496  meta_srv_thread->quit();
497 
498  return true;
499  }
500 
501  // don't allow the user to spam the metaserver with updates
502  if (last_send_timer
503  && (timer_read_seconds(last_send_timer)
505  if (flag == META_INFO) {
506  want_update = true; // we couldn't update now, but update a.s.a.p.
507  }
508  return false;
509  }
510 
511  /* if we're asking for a refresh, only do so if
512  * we've exceeded the refresh interval */
513  if ((flag == META_REFRESH) && !want_update && last_send_timer
514  && (timer_read_seconds(last_send_timer)
516  return false;
517  }
518 
519  // start a new timer if we haven't already
520  if (!last_send_timer) {
521  last_send_timer = timer_new(TIMER_USER, TIMER_ACTIVE);
522  }
523 
524  timer_clear(last_send_timer);
525  timer_start(last_send_timer);
526  want_update = false;
527 
528  return send_to_metaserver(flag);
529 }
const char *const our_capability
Definition: capstr.cpp:28
struct connection * conn_by_user(const char *user_name)
Find connection by exact user name, from game.all_connections, case-insensitve.
Definition: connection.cpp:329
void con_flush()
Ensure timely update.
Definition: console.cpp:183
void con_puts(enum rfc_status rfc_status, const char *str)
Write to console and add line-break, and show prompt if required.
Definition: console.cpp:162
@ C_METAERROR
Definition: console.h:44
#define _(String)
Definition: fcintl.h:50
struct civ_game game
Definition: game.cpp:47
struct world wld
Definition: game.cpp:48
#define fc_assert_ret_val_msg(condition, val, message,...)
Definition: log.h:132
@ MAPSIZE_FULLSIZE
Definition: map_types.h:35
@ MAPSIZE_PLAYER
Definition: map_types.h:36
@ MAPSIZE_XYSIZE
Definition: map_types.h:39
Q_GLOBAL_STATIC(fcThread, meta_srv_thread)
void maybe_automatic_meta_message(const char *automatic)
Update meta message.
Definition: meta.cpp:112
const char * get_meta_message_string()
The metaserver message.
Definition: meta.cpp:80
void set_meta_patches_string(const char *string)
Set the metaserver patches string.
Definition: meta.cpp:132
bool is_metaserver_open()
Are we sending info to the metaserver?
Definition: meta.cpp:464
const char * default_meta_message_string()
Return static string with default info line to send to metaserver.
Definition: meta.cpp:63
const char * get_meta_patches_string()
The metaserver patches.
Definition: meta.cpp:75
void server_close_meta()
Stop sending updates to metaserver.
Definition: meta.cpp:436
static void metaserver_failed()
We couldn't find or connect to the metaserver.
Definition: meta.cpp:168
static bool persistent_meta_connection
Definition: meta.cpp:47
void set_meta_message_string(const char *string)
Set the metaserver message string.
Definition: meta.cpp:140
static char meta_message[256]
Definition: meta.cpp:51
static void send_metaserver_post(void *arg)
Send POST to metaserver.
Definition: meta.cpp:204
const char * default_meta_patches_string()
The default metaserver patches for this server.
Definition: meta.cpp:58
static const char * get_meta_type_string()
The server metaserver type.
Definition: meta.cpp:85
QString meta_addr_port()
Return string describing both metaserver name and port.
Definition: meta.cpp:163
bool send_server_info_to_metaserver(enum meta_flag flag)
Control when we send info to the metaserver.
Definition: meta.cpp:469
static int meta_retry_wait
Definition: meta.cpp:48
const char * get_user_meta_message_string()
The metaserver message set by user.
Definition: meta.cpp:97
static bool server_is_open
Definition: meta.cpp:46
bool server_open_meta(bool persistent)
Lookup the correct address for the metaserver.
Definition: meta.cpp:445
static bool meta_insert_setting(QUrlQuery *query, const char *set_name)
Insert a setting in the metaserver message.
Definition: meta.cpp:185
static bool send_to_metaserver(enum meta_flag flag)
Construct the POST message and send info to metaserver.
Definition: meta.cpp:244
void set_user_meta_message_string(const char *string)
Set user defined metaserver message string.
Definition: meta.cpp:148
static char meta_patches[256]
Definition: meta.cpp:50
#define METASERVER_REFRESH_INTERVAL
Definition: meta.h:22
#define METASERVER_MIN_UPDATE_INTERVAL
Definition: meta.h:23
meta_flag
Definition: meta.h:25
@ META_GOODBYE
Definition: meta.h:25
@ META_INFO
Definition: meta.h:25
@ META_REFRESH
Definition: meta.h:25
const char * nation_plural_for_player(const struct player *pplayer)
Return the (translated) plural noun of the given nation of a player.
Definition: nation.cpp:155
struct nation_type * nation_of_player(const struct player *pplayer)
Return the nation of a player.
Definition: nation.cpp:419
#define NO_NATION_SELECTED
Definition: nation.h:21
const char * player_name(const struct player *pplayer)
Return the leader name of the player.
Definition: player.cpp:816
#define players_iterate_end
Definition: player.h:520
#define players_iterate(_pplayer)
Definition: player.h:514
static bool is_barbarian(const struct player *pplayer)
Definition: player.h:474
#define is_ai(plr)
Definition: player.h:227
#define is_human(plr)
Definition: player.h:226
int normal_player_count()
Return the number of non-barbarian players.
Definition: plrhand.cpp:3140
static struct setting settings[]
Definition: settings.cpp:1338
struct setting * setting_by_name(const char *name)
Returns the setting to the given name.
Definition: settings.cpp:3039
const char * setting_name(const struct setting *pset)
Access function for the setting name.
Definition: settings.cpp:3065
const char * setting_value_name(const struct setting *pset, bool pretty, char *buf, size_t buf_len)
Compute the name of the current value of the setting.
Definition: settings.cpp:3946
#define ARRAY_SIZE(x)
Definition: shared.h:79
struct server_arguments srvarg
Definition: srv_main.cpp:118
enum server_states server_state()
Return current server state.
Definition: srv_main.cpp:238
struct civ_game::@28::@32 server
struct packet_ruleset_control control
Definition: game.h:74
struct packet_game_info info
Definition: game.h:80
struct civ_map::@39::@41 server
QString addr
Definition: connection.h:152
QString identity_name
Definition: srv_main.h:33
QString serverid
Definition: srv_main.h:52
QString metaserver_addr
Definition: srv_main.h:31
struct civ_map map
Definition: world_object.h:21
int fc_snprintf(char *str, size_t n, const char *format,...)
See also fc_utf8_snprintf_trunc(), fc_utf8_snprintf_rep().
Definition: support.cpp:537
int fc_gethostname(char *buf, size_t len)
Call gethostname() if supported, else just returns -1.
Definition: support.cpp:586
#define sz_strlcpy(dest, src)
Definition: support.h:140
void timer_destroy(civtimer *t)
Deletes timer.
Definition: timing.cpp:66
double timer_read_seconds(civtimer *t)
Read value from timer.
Definition: timing.cpp:137
civtimer * timer_new(enum timer_timetype type, enum timer_use use)
Allocate a new timer with specified "type" and "use".
Definition: timing.cpp:43
void timer_start(civtimer *t)
Start timing, adding to previous accumulated time if timer has not been cleared.
Definition: timing.cpp:95
void timer_clear(civtimer *t)
Reset accumulated time to zero, and stop timer if going.
Definition: timing.cpp:83
@ TIMER_ACTIVE
Definition: timing.h:25
@ TIMER_USER
Definition: timing.h:21
const char * freeciv21_version()
Returns the raw version string.
Definition: version.cpp:29