1538 lines
34 KiB
C
1538 lines
34 KiB
C
/* XQF - Quake server browser and launcher Copyright (C) 1998-2000 Roman
|
|
* Pozlevich <roma@botik.ru>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h> /* strtol */
|
|
#include <string.h> /* strchr, strcmp, strstr */
|
|
#include <sys/stat.h> /* stat */
|
|
#include <unistd.h> /* stat */
|
|
#include <sys/socket.h> /* inet_aton, inet_ntoa */
|
|
#include <netinet/in.h> /* inet_aton, inet_ntoa */
|
|
#include <arpa/inet.h> /* inet_aton, inet_ntoa */
|
|
#include <errno.h> /* errno */
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "xqf.h"
|
|
#include "xqf-ui.h"
|
|
#include "source.h"
|
|
#include "server.h"
|
|
#include "host.h"
|
|
#include "pref.h"
|
|
#include "dialogs.h"
|
|
#include "game.h"
|
|
#include "utils.h"
|
|
#include "config.h"
|
|
#include "dns.h"
|
|
#include "zipped.h"
|
|
#include "stat.h"
|
|
|
|
#include "masters.c"
|
|
|
|
static gboolean master_options_compare(QFMasterOptions* lhs, QFMasterOptions* rhs);
|
|
static QFMasterOptions parse_master_options(const char* str);
|
|
static void master_options_release(QFMasterOptions* o, gboolean doit);
|
|
|
|
struct master *favorites = NULL;
|
|
GSList *master_groups = NULL;
|
|
|
|
char* master_prefixes[MASTER_NUM_QUERY_TYPES] = {
|
|
"master://",
|
|
"gmaster://",
|
|
"http://",
|
|
"lan://",
|
|
"file://",
|
|
"gslist://",
|
|
};
|
|
|
|
char* master_designation[MASTER_NUM_QUERY_TYPES] = {
|
|
N_("Standard"),
|
|
N_("Gamespy"),
|
|
N_("http"),
|
|
N_("LAN"),
|
|
N_("File"),
|
|
"gslist",
|
|
};
|
|
|
|
static GSList *all_masters = NULL;
|
|
|
|
|
|
static void save_list (FILE *f, struct master *m) {
|
|
GSList *srv;
|
|
struct server *s;
|
|
|
|
if (!m->servers)
|
|
return;
|
|
debug (6, "save_list() -- Saving Server List \"%s\"", m->name);
|
|
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "Saving server list \"%s\"...\n", m->name);
|
|
#endif
|
|
|
|
if (m == favorites) {
|
|
fprintf (f, "[%s]\n", m->name);
|
|
}
|
|
else {
|
|
char* str = master_to_url(m);
|
|
fputc('[', f);
|
|
fputs(games[m->type].id, f);
|
|
fputc(' ', f);
|
|
fputs(str, f);
|
|
fputs("]\n", f);
|
|
g_free(str);
|
|
}
|
|
|
|
for (srv = m->servers; srv; srv = srv->next) {
|
|
s = (struct server *) srv->data;
|
|
fprintf (f, "%s%s%s %s:%d\n",
|
|
games[s->type].id,
|
|
s->server_query_type != UNKNOWN_SERVER? "|":"",
|
|
s->server_query_type != UNKNOWN_SERVER? games[s->server_query_type].id : "",
|
|
inet_ntoa (s->host->ip),
|
|
s->port);
|
|
}
|
|
|
|
fprintf (f, "\n");
|
|
}
|
|
|
|
|
|
void save_favorites (void) {
|
|
FILE *f;
|
|
char *realname;
|
|
|
|
debug (6, "save_favorites() --");
|
|
realname = file_in_dir (user_rcdir, FILENAME_FAVORITES);
|
|
|
|
while (1) {
|
|
f = fopen (realname, "w");
|
|
|
|
if (!f) {
|
|
if (dialog_yesno (NULL, 0, _("Retry"), _("Skip"),
|
|
_("Cannot write to file %s\nSystem Error: %s\n"),
|
|
realname, g_strerror (errno))) {
|
|
continue;
|
|
}
|
|
g_free (realname);
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
save_list (f, favorites);
|
|
fclose (f);
|
|
g_free (realname);
|
|
}
|
|
|
|
|
|
static void save_lists (GSList *list, const char *filename) {
|
|
char *realname;
|
|
struct zstream z;
|
|
|
|
debug (6, "save_lists() -- %s", filename);
|
|
realname = file_in_dir (user_rcdir, filename);
|
|
|
|
if (!list) {
|
|
zstream_unlink (realname);
|
|
g_free (realname);
|
|
return;
|
|
}
|
|
|
|
zstream_open_w (&z, realname);
|
|
|
|
if (z.f) {
|
|
while (list) {
|
|
save_list (z.f, (struct master *) list->data);
|
|
list = list->next;
|
|
}
|
|
zstream_close (&z);
|
|
}
|
|
|
|
g_free (realname);
|
|
}
|
|
|
|
|
|
static void save_server_info (const char *filename, GSList *servers) {
|
|
char *realname;
|
|
struct zstream z;
|
|
struct server *s;
|
|
|
|
realname = file_in_dir (user_rcdir, filename);
|
|
|
|
if (!servers) {
|
|
zstream_unlink (realname);
|
|
g_free (realname);
|
|
return;
|
|
}
|
|
|
|
zstream_open_w (&z, realname);
|
|
|
|
if (z.f) {
|
|
while (servers) {
|
|
s = (struct server *) servers->data;
|
|
|
|
if (s->ref_count > 0 && games[s->type].save_info)
|
|
(*games[s->type].save_info) (z.f, s);
|
|
|
|
servers = servers->next;
|
|
}
|
|
zstream_close (&z);
|
|
}
|
|
|
|
g_free (realname);
|
|
}
|
|
|
|
|
|
static struct master *find_master_server (char *addr, unsigned short port, char *str, QFMasterOptions* options) {
|
|
GSList *list;
|
|
struct master *m = NULL;
|
|
struct in_addr ip;
|
|
|
|
if (!addr || !port)
|
|
return NULL;
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
if (m->port && m->port == port)
|
|
{
|
|
if (m->hostname)
|
|
{
|
|
if (!g_ascii_strcasecmp (m->hostname, addr)) {
|
|
if ((!str || // pre 0.9.4e list file
|
|
!g_ascii_strcasecmp (games[m->type].id, str)) // list file in new format
|
|
&& master_options_compare(&m->options, options))
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m->host && inet_aton (addr, &ip) && ip.s_addr == m->host->ip.s_addr) {
|
|
if ((!str || // pre 0.9.4e list file
|
|
!g_ascii_strcasecmp (games[m->type].id, str))
|
|
&& master_options_compare(&m->options, options))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
m = NULL;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
/*
|
|
* This is internal function to make compare_urls() function work.
|
|
* Don't use it for any other purposes.
|
|
*
|
|
* Unification is
|
|
* 1) lower case protocol and hostname
|
|
* 2) remove port reference if it's standard port for the protocol
|
|
* 3) remove trailing slashes from the end of the file location
|
|
*/
|
|
|
|
static char *unify_url (const char *url) {
|
|
char *unified, *tmp;
|
|
char *ptr, *ptr2, *ptr3;
|
|
long port;
|
|
int len;
|
|
|
|
if (!url)
|
|
return NULL;
|
|
|
|
ptr = strstr (url, "://");
|
|
if (ptr == NULL)
|
|
return NULL;
|
|
|
|
ptr += 3;
|
|
|
|
if (*ptr == '\0')
|
|
return NULL;
|
|
|
|
unified = g_malloc (strlen (url) + 1);
|
|
|
|
ptr2 = strchr (ptr, ':');
|
|
if (!ptr2) {
|
|
ptr2 = strchr (ptr, '/');
|
|
if (!ptr2)
|
|
ptr2 = ptr + strlen (ptr);
|
|
ptr3 = ptr2;
|
|
}
|
|
else {
|
|
port = strtol (ptr2 + 1, &ptr3, 10);
|
|
if (port != 80)
|
|
ptr2 = ptr3;
|
|
}
|
|
|
|
strncpy (unified, url, ptr2 - url);
|
|
unified[ptr2 - url] = '\0';
|
|
tmp = g_ascii_strdown (unified, -1); /* g_ascii_strdown does not modify string in place */
|
|
strcpy(unified, tmp); /* so recopy returned string at same address */
|
|
g_free(tmp);
|
|
|
|
strcpy (unified + (ptr2 - url), ptr3);
|
|
|
|
while ((len = strlen (unified)) > 0 && unified[len - 1] == '/')
|
|
unified[len - 1] = '\0';
|
|
|
|
#ifdef DEBUG
|
|
fprintf (stderr, "URL unification %s -> %s\n", url, unified);
|
|
#endif
|
|
|
|
return unified;
|
|
}
|
|
|
|
|
|
static int _compare_urls (const char *url1, const char *url2, gboolean withopts) {
|
|
char *uni1;
|
|
char *uni2;
|
|
int res = 1;
|
|
|
|
uni1 = unify_url (url1);
|
|
uni2 = unify_url (url2);
|
|
|
|
/*
|
|
* It's error if any URL is NULL
|
|
* Don't take care of situation when both of URLs are NULL
|
|
*/
|
|
|
|
if (uni1 != NULL && uni2 != NULL) {
|
|
if (!withopts) {
|
|
char* p = NULL;
|
|
p = strchr(uni1, ';');
|
|
if (p) *p='\0';
|
|
p = strchr(uni2, ';');
|
|
if (p) *p='\0';
|
|
}
|
|
res = strcmp (uni1, uni2);
|
|
}
|
|
|
|
if (uni1) g_free (uni1);
|
|
if (uni2) g_free (uni2);
|
|
return res;
|
|
}
|
|
|
|
#if 0
|
|
static int compare_urls (const char *url1, const char *url2) {
|
|
return _compare_urls(url1, url2, TRUE);
|
|
}
|
|
#endif
|
|
|
|
static int compare_urls_noopts (const char *url1, const char *url2) {
|
|
return _compare_urls(url1, url2, FALSE);
|
|
}
|
|
|
|
static struct master *find_master_url (char *url, QFMasterOptions* options) {
|
|
GSList *list;
|
|
struct master *m = NULL;
|
|
|
|
if (!url)
|
|
return NULL;
|
|
|
|
debug(5, "%s %p", url, options);
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
if (m->url && compare_urls_noopts (m->url, url) == 0
|
|
&& master_options_compare(&m->options, options))
|
|
break;
|
|
else
|
|
m = NULL;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
/**
|
|
@param str type string or favorites->name
|
|
@param url url string, NULL for favorites
|
|
*/
|
|
static struct master *read_list_parse_master (char *str, char *url) {
|
|
char *addr;
|
|
unsigned short port;
|
|
struct master *m = NULL;
|
|
enum master_query_type query_type;
|
|
QFMasterOptions options = { 0, NULL };
|
|
char* optstr = NULL;
|
|
|
|
if (favorites && !g_ascii_strcasecmp (str, favorites->name))
|
|
return favorites;
|
|
|
|
debug(5, "%s %s", str, url);
|
|
|
|
query_type = get_master_query_type_from_address(str);
|
|
|
|
optstr = strchr(str + strlen(master_prefixes[query_type]), ';');
|
|
if (optstr) {
|
|
*optstr++ = '\0';
|
|
options = parse_master_options(optstr);
|
|
}
|
|
|
|
switch(query_type) {
|
|
case MASTER_NATIVE:
|
|
case MASTER_GAMESPY:
|
|
case MASTER_LAN:
|
|
if (parse_address (str + strlen(master_prefixes[query_type]), &addr, &port)) {
|
|
m = find_master_server (addr, port, url, &options);
|
|
g_free (addr);
|
|
}
|
|
break;
|
|
case MASTER_HTTP:
|
|
case MASTER_FILE:
|
|
case MASTER_GSLIST:
|
|
m = find_master_url (str, &options);
|
|
break;
|
|
case MASTER_NUM_QUERY_TYPES:
|
|
case MASTER_INVALID_TYPE:
|
|
break;
|
|
}
|
|
|
|
master_options_release(&options, TRUE);
|
|
return m;
|
|
}
|
|
|
|
static gint server_sorting_helper (const struct server *s1,
|
|
const struct server *s2) {
|
|
if (s1 == s2)
|
|
return 0;
|
|
else if (s1<s2)
|
|
return -1;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static void master_add_server (struct master *m, char *str, enum server_type type) {
|
|
gchar *addr, *tmp;
|
|
unsigned short port;
|
|
struct host *h;
|
|
struct server *s;
|
|
struct userver *us;
|
|
|
|
debug (6, "master_add_server() -- Master %lx", m);
|
|
if (parse_address (str, &addr, &port)) {
|
|
h = host_add (addr);
|
|
if (h) { /* IP address */
|
|
host_ref (h);
|
|
if ((s = server_add (h, port, type)) != NULL) {
|
|
m->servers = server_list_prepend_ndp (m->servers, s);
|
|
/* Since the server_add increments the ref count, and
|
|
server_list_prepend_ndp ups the ref_count, we should
|
|
unref it once because we are only keeping it in
|
|
one list after this function.
|
|
*/
|
|
server_unref (s);
|
|
}
|
|
host_unref (h);
|
|
}
|
|
else { /* hostname */
|
|
tmp = g_ascii_strdown (addr, -1); /* g_ascii_strdown does not modify string in place */
|
|
strcpy(addr, tmp); /* so recopy returned string at same address */
|
|
g_free(tmp);
|
|
if ((us = userver_add (addr, port, type)) != NULL)
|
|
m->uservers = userver_list_add (m->uservers, us);
|
|
}
|
|
g_free (addr);
|
|
}
|
|
}
|
|
|
|
static void read_lists (const char *filename) {
|
|
struct zstream z;
|
|
char *realname;
|
|
char buf[4096];
|
|
char *ch;
|
|
struct master *m = NULL;
|
|
char *token[8], *pipe;
|
|
int n;
|
|
enum server_type type;
|
|
|
|
realname = file_in_dir (user_rcdir, filename);
|
|
zstream_open_r (&z, realname);
|
|
|
|
if (z.f == NULL) {
|
|
g_free (realname);
|
|
return;
|
|
}
|
|
|
|
while (fgets (buf, 4096, z.f)) {
|
|
n = tokenize (buf, token, 8, " \t\n\r");
|
|
if (n < 1) {
|
|
continue;
|
|
}
|
|
|
|
if (token[0][0] == '[') { // line is a master
|
|
ch = strchr (token[0] + 1, ']'); // does token 0 have a ]?
|
|
if (ch) { // it's a favorites or pre 0.9.4e lists file
|
|
*ch = '\0';
|
|
|
|
if (m && m->servers) {
|
|
m->servers = g_slist_reverse (m->servers);
|
|
}
|
|
|
|
m = read_list_parse_master (token[0] + 1, NULL);
|
|
}
|
|
else {
|
|
ch = strchr (token[1], ']'); // does token 1 have a ]?
|
|
if (ch) {
|
|
|
|
*ch = '\0';
|
|
|
|
if (m && m->servers) {
|
|
m->servers = g_slist_reverse (m->servers);
|
|
}
|
|
|
|
m = read_list_parse_master (token[1], token[0] + 1); // master, type
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (!m || n < 2) {
|
|
continue;
|
|
}
|
|
|
|
pipe = strchr(token[0], '|');
|
|
|
|
if (pipe != NULL) {
|
|
// DESTRUCTIVE
|
|
*pipe = '\0';
|
|
}
|
|
|
|
type = id2type (token[0]);
|
|
|
|
if (type != UNKNOWN_SERVER) {
|
|
master_add_server (m, token[1], type);
|
|
|
|
if (pipe != NULL) {
|
|
m->server_query_type = id2type(++pipe);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m && m->servers) {
|
|
m->servers = g_slist_reverse (m->servers);
|
|
}
|
|
|
|
zstream_close (&z);
|
|
g_free (realname);
|
|
}
|
|
|
|
|
|
static void read_server_info (const char *filename) {
|
|
struct zstream z;
|
|
char *realname;
|
|
char *buf;
|
|
char *pos;
|
|
GSList *strings;
|
|
int line = 1;
|
|
|
|
debug(3,"read_server_info(%s)",filename);
|
|
|
|
realname = file_in_dir (user_rcdir, filename);
|
|
zstream_open_r (&z, realname);
|
|
|
|
if (z.f == NULL) {
|
|
g_free (realname);
|
|
return;
|
|
}
|
|
|
|
buf = g_malloc (1024*16);
|
|
pos = buf;
|
|
strings = NULL;
|
|
|
|
while (1) {
|
|
if (pos - buf > 1024*16 - 16) {
|
|
xqf_error("server record is too large in %s, line %d\n", realname, line);
|
|
g_slist_free (strings);
|
|
break;
|
|
}
|
|
|
|
if (!fgets (pos, 1024*16 - (pos - buf), z.f)) {
|
|
|
|
/* EOF || Error */
|
|
|
|
if (strings) {
|
|
parse_saved_server (strings);
|
|
g_slist_free (strings);
|
|
}
|
|
break;
|
|
|
|
}
|
|
else {
|
|
|
|
if (pos[0] == '\n') { /* empty line */
|
|
++line;
|
|
parse_saved_server (strings);
|
|
g_slist_free (strings);
|
|
strings = NULL;
|
|
pos = buf;
|
|
}
|
|
else {
|
|
strings = g_slist_append (strings, pos);
|
|
|
|
while (*pos) {
|
|
if (*pos == '\n') {
|
|
++line;
|
|
*pos = '\0';
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
pos++;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
g_free (buf);
|
|
|
|
zstream_close (&z);
|
|
g_free (realname);
|
|
}
|
|
|
|
|
|
static void read_favorites_old_format (char *fn) {
|
|
FILE *f;
|
|
char buf[1024];
|
|
char *token[3];
|
|
int n;
|
|
enum server_type type;
|
|
|
|
f = fopen (fn, "r");
|
|
if (!f)
|
|
return;
|
|
|
|
while (fgets (buf, 1024, f)) {
|
|
n = tokenize_bychar (buf, token, 3, '|');
|
|
if (n < 2)
|
|
continue;
|
|
|
|
type = id2type (token[0]);
|
|
|
|
if (type != UNKNOWN_SERVER)
|
|
master_add_server (favorites, token[1], type);
|
|
}
|
|
|
|
if (favorites->servers)
|
|
favorites->servers = g_slist_reverse (favorites->servers);
|
|
|
|
fclose (f);
|
|
}
|
|
|
|
|
|
static void compat_convert_favorites (void) {
|
|
struct stat statbuf;
|
|
char *fn;
|
|
|
|
fn = file_in_dir (user_rcdir, FILENAME_FAVORITES);
|
|
if (stat (fn, &statbuf) == 0) {
|
|
g_free (fn);
|
|
return;
|
|
}
|
|
g_free (fn);
|
|
|
|
fn = file_in_dir (user_rcdir, "Favorites");
|
|
read_favorites_old_format (fn);
|
|
g_free (fn);
|
|
}
|
|
|
|
|
|
static struct master *create_master (char *name, enum server_type type,
|
|
int group) {
|
|
struct master *m;
|
|
|
|
m = g_malloc0 (sizeof (struct master));
|
|
m->name = g_strdup (name);
|
|
m->type = type;
|
|
m->state = SOURCE_NA;
|
|
m->isgroup = group;
|
|
|
|
if (!group)
|
|
all_masters = g_slist_append (all_masters, m);
|
|
|
|
return m;
|
|
}
|
|
|
|
|
|
char* master_qstat_option(struct master* m) {
|
|
g_return_val_if_fail(m!=NULL,NULL);
|
|
|
|
if (m->_qstat_master_option)
|
|
return m->_qstat_master_option;
|
|
else
|
|
return games[m->type].qstat_master_option;
|
|
}
|
|
|
|
void master_set_qstat_option(struct master* m, const char* opt) {
|
|
g_return_if_fail(m!=NULL);
|
|
g_free(m->_qstat_master_option);
|
|
m->_qstat_master_option = opt?g_strdup(opt):NULL;
|
|
}
|
|
|
|
/** no deep copy! */
|
|
static QFMasterOptions parse_master_options(const char* str) {
|
|
const char* semicolon = NULL;
|
|
const char* val = NULL;
|
|
QFMasterOptions options = {0, NULL};
|
|
|
|
if (!str)
|
|
return options;
|
|
|
|
while (str && *str) {
|
|
semicolon = strchr(str, ';');
|
|
if (!semicolon) semicolon = str+strlen(str);
|
|
|
|
// find next = before ;
|
|
for (val=str; *val && *val != ';' && *val != '='; ++val);
|
|
if (*val != '=')
|
|
val = NULL;
|
|
else
|
|
++val;
|
|
|
|
if (val && semicolon-val && !strncmp(str, "gsmtype", 7))
|
|
options.gsmtype = g_strndup(val, semicolon-val);
|
|
if (val && semicolon-val && !strncmp(str, "portadjust", 7))
|
|
options.portadjust = atoi(val);
|
|
|
|
if (*semicolon)
|
|
str = semicolon+1;
|
|
else
|
|
str = semicolon;
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
static gboolean master_options_compare(QFMasterOptions* lhs, QFMasterOptions* rhs) {
|
|
if (!lhs || !rhs)
|
|
return FALSE;
|
|
|
|
if (lhs->portadjust != rhs->portadjust)
|
|
return FALSE;
|
|
|
|
if (lhs->gsmtype && rhs->gsmtype && strcmp(lhs->gsmtype, rhs->gsmtype))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0
|
|
char* master_to_string(QFMaster* m) {
|
|
char buf[1024] = {0};
|
|
|
|
switch(m->master_type) {
|
|
case MASTER_NATIVE:
|
|
case MASTER_GAMESPY:
|
|
case MASTER_LAN:
|
|
snprintf (buf, sizeof(buf), "[%s %s%s:%d",
|
|
games[m->type].id,
|
|
master_prefixes[m->master_type],
|
|
(m->hostname)? m->hostname : inet_ntoa (m->host->ip),
|
|
m->port
|
|
);
|
|
break;
|
|
case MASTER_HTTP:
|
|
case MASTER_FILE:
|
|
case MASTER_GSLIST:
|
|
snprintf (buf, sizeof(buf), "[%s %s", games[m->type].id, m->url);
|
|
break;
|
|
case MASTER_NUM_QUERY_TYPES:
|
|
case MASTER_INVALID_TYPE:
|
|
break;
|
|
}
|
|
|
|
if (m->options.portadjust)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), ";portadjust=%d", m->options.portadjust);
|
|
if (m->options.gsmtype)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), ";gsmtype=%s", m->options.gsmtype);
|
|
|
|
snprintf(buf, sizeof(buf)-strlen(buf), "]\n");
|
|
|
|
if (*buf)
|
|
return g_strdup(buf);
|
|
else
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
char *master_to_url(QFMaster* m) {
|
|
char *query_type;
|
|
char *address;
|
|
char buf[1024] = {0};
|
|
|
|
if (m->master_type >= MASTER_NATIVE
|
|
&& m->master_type < MASTER_NUM_QUERY_TYPES) {
|
|
query_type = master_prefixes[m->master_type];
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
switch(m->master_type) {
|
|
case MASTER_NATIVE:
|
|
case MASTER_GAMESPY:
|
|
case MASTER_LAN:
|
|
if (m->hostname) {
|
|
address = m->hostname;
|
|
}
|
|
else if (m->host) {
|
|
address = inet_ntoa(m->host->ip);
|
|
}
|
|
else {
|
|
xqf_error("master %s has neither hostname nor ip address", m->name);
|
|
return NULL;
|
|
}
|
|
snprintf (buf, sizeof(buf), "%s%s:%d", query_type, address, m->port);
|
|
break;
|
|
case MASTER_HTTP:
|
|
case MASTER_FILE:
|
|
case MASTER_GSLIST:
|
|
snprintf (buf, sizeof(buf), "%s", m->url);
|
|
break;
|
|
case MASTER_NUM_QUERY_TYPES:
|
|
case MASTER_INVALID_TYPE:
|
|
break;
|
|
}
|
|
|
|
if (m->options.portadjust)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), ";portadjust=%d", m->options.portadjust);
|
|
if (m->options.gsmtype)
|
|
snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), ";gsmtype=%s", m->options.gsmtype);
|
|
|
|
if (*buf)
|
|
return g_strdup(buf);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/** only free content, not pointer itself */
|
|
static void master_options_release(QFMasterOptions* o, gboolean doit) {
|
|
if (!doit || !o) return;
|
|
g_free(o->gsmtype);
|
|
}
|
|
|
|
struct master *add_master (char *path, char *name, enum server_type type, enum server_type server_query_type, const char* qstat_query_arg, int user, int lookup_only) {
|
|
char *addr = NULL;
|
|
unsigned short port = 0;
|
|
struct master *m = NULL;
|
|
struct host *h = NULL;
|
|
struct master *group = NULL;
|
|
enum master_query_type query_type;
|
|
char* optstr = NULL;
|
|
QFMasterOptions options = {0, NULL};
|
|
gboolean freeoptions = FALSE;
|
|
|
|
debug(6,"%s,%s,%d,%d,%d",path,name,type,user,lookup_only);
|
|
|
|
query_type = get_master_query_type_from_address(path);
|
|
if (query_type == MASTER_INVALID_TYPE) {
|
|
debug(1,"Invalid Master %s",path);
|
|
return NULL;
|
|
}
|
|
|
|
optstr = strchr(path + strlen(master_prefixes[query_type]), ';');
|
|
if (optstr) {
|
|
*optstr++ = '\0';
|
|
options = parse_master_options(optstr);
|
|
freeoptions = TRUE;
|
|
}
|
|
|
|
switch(query_type) {
|
|
case MASTER_NATIVE:
|
|
case MASTER_GAMESPY:
|
|
case MASTER_LAN:
|
|
if (query_type == MASTER_LAN) {
|
|
// override master “LAN” name with game name
|
|
name = games[type].name;
|
|
}
|
|
|
|
// check for valid hostname/ip
|
|
if (parse_address (path + strlen(master_prefixes[query_type]), &addr, &port)) {
|
|
// if no port was specified, add default master port if available or fail
|
|
if (!port) {
|
|
// use default_port instead of default_master_port for lan broadcasts
|
|
if (query_type == MASTER_LAN) {
|
|
port = games[type].default_port;
|
|
/* no longer necessary as of qstat 2.5c
|
|
// unreal needs one port higher
|
|
if (!strcmp(games[type].qstat_option,"-uns")) {
|
|
port++;
|
|
type=GPS_SERVER;
|
|
}
|
|
*/
|
|
}
|
|
// do not use default for gamespy
|
|
else if (query_type != MASTER_GAMESPY && games[type].default_master_port) {
|
|
port = games[type].default_master_port;
|
|
}
|
|
else {
|
|
master_options_release(&options, freeoptions);
|
|
g_free (addr);
|
|
// translator: %s == url, eg gmaster://bla.blub.org
|
|
dialog_ok (NULL, _("You have to specify a port number for %s."),path);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
m = find_master_server (addr, port, games[type].id, &options);
|
|
|
|
}
|
|
break;
|
|
|
|
case MASTER_GSLIST:
|
|
if (!options.gsmtype) {
|
|
master_options_release(&options, freeoptions);
|
|
dialog_ok (NULL, _("'gsmtype' options missing for master %s."), name);
|
|
return NULL;
|
|
}
|
|
case MASTER_HTTP:
|
|
case MASTER_FILE:
|
|
m = find_master_url (path, &options);
|
|
break;
|
|
case MASTER_NUM_QUERY_TYPES:
|
|
case MASTER_INVALID_TYPE:
|
|
master_options_release(&options, freeoptions);
|
|
return NULL;
|
|
}
|
|
|
|
if (lookup_only) {
|
|
master_options_release(&options, freeoptions);
|
|
g_free (addr);
|
|
return m;
|
|
}
|
|
|
|
// we don't update qstat_query_arg for existing masters -- ln
|
|
if (m) {
|
|
if (user) { // Master renaming is forced by user
|
|
g_free (m->name);
|
|
m->name = g_strdup (name);
|
|
m->user = TRUE;
|
|
master_options_release(&m->options, TRUE);
|
|
m->options = options;
|
|
freeoptions = FALSE;
|
|
}
|
|
else { // Automatically rename masters that are not edited by user
|
|
if (!m->user) {
|
|
g_free (m->name);
|
|
m->name = g_strdup (name);
|
|
master_options_release(&m->options, TRUE);
|
|
m->options = options;
|
|
freeoptions = FALSE;
|
|
}
|
|
}
|
|
master_options_release(&options, freeoptions);
|
|
g_free (addr);
|
|
return m;
|
|
}
|
|
|
|
// master was not known already, create new
|
|
switch(query_type) {
|
|
case MASTER_NATIVE:
|
|
case MASTER_GAMESPY:
|
|
case MASTER_LAN:
|
|
m = create_master (name, type, FALSE);
|
|
|
|
master_set_qstat_option(m, qstat_query_arg);
|
|
|
|
h = host_add (addr);
|
|
if (h) {
|
|
m->host = h;
|
|
host_ref (h);
|
|
g_free (addr);
|
|
addr = NULL;
|
|
}
|
|
else {
|
|
m->hostname = addr;
|
|
}
|
|
m->port = port;
|
|
break;
|
|
case MASTER_HTTP:
|
|
case MASTER_FILE:
|
|
case MASTER_GSLIST:
|
|
m = create_master (name, type, FALSE);
|
|
m->url = g_strdup (path);
|
|
break;
|
|
case MASTER_NUM_QUERY_TYPES:
|
|
case MASTER_INVALID_TYPE:
|
|
master_options_release(&options, freeoptions);
|
|
return NULL;
|
|
}
|
|
|
|
m->server_query_type = server_query_type;
|
|
m->master_type = query_type;
|
|
m->options = options;
|
|
freeoptions = FALSE;
|
|
|
|
if (m) {
|
|
type = query_type == MASTER_LAN? LAN_SERVER : type;
|
|
group = (struct master *) g_slist_nth_data (master_groups, type);
|
|
group->masters = g_slist_append (group->masters, m);
|
|
m->user = user;
|
|
}
|
|
|
|
master_options_release(&options, freeoptions);
|
|
return m;
|
|
}
|
|
|
|
void free_master (struct master *m) {
|
|
struct master *group;
|
|
|
|
if (!m)
|
|
return;
|
|
|
|
if (m->isgroup) {
|
|
while (m->masters)
|
|
free_master ((struct master *) m->masters->data);
|
|
}
|
|
else {
|
|
if (m->type != UNKNOWN_SERVER) {
|
|
if (m->master_type == MASTER_LAN) {
|
|
group = (struct master *) g_slist_nth_data (master_groups, LAN_SERVER);
|
|
}
|
|
else{
|
|
group = (struct master *) g_slist_nth_data (master_groups, m->type);
|
|
}
|
|
group->masters = g_slist_remove (group->masters, m);
|
|
}
|
|
all_masters = g_slist_remove (all_masters, m);
|
|
}
|
|
|
|
host_unref (m->host);
|
|
|
|
g_free (m->hostname);
|
|
g_free (m->name);
|
|
g_free (m->url);
|
|
g_free (m->_qstat_master_option);
|
|
g_free (m->options.gsmtype);
|
|
|
|
server_list_free (m->servers);
|
|
userver_list_free (m->uservers);
|
|
|
|
g_free (m);
|
|
}
|
|
|
|
/** parse type of the form <TYPE>[,QSTAT_MASTER_OPTION]
|
|
* DESTRUCTIVE
|
|
* @return qstat_master_option or NULL
|
|
*/
|
|
char* master_id2type(char* token, enum server_type* type, enum server_type* server_query_type) {
|
|
char* ret = NULL;
|
|
char* comma = strchr(token, ',');
|
|
char* pipe = strchr(token, '|');
|
|
|
|
if (comma != NULL) {
|
|
*comma = '\0';
|
|
ret = ++comma;
|
|
}
|
|
|
|
if (pipe != NULL) {
|
|
*pipe = '\0';
|
|
*server_query_type = id2type(++pipe);
|
|
}
|
|
else {
|
|
*server_query_type = UNKNOWN_SERVER;
|
|
}
|
|
|
|
*type = id2type(token);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Format: Action ServerType URL Description
|
|
*/
|
|
|
|
static void update_master_list_action (const char *action) {
|
|
char *str;
|
|
char *token[4];
|
|
int n;
|
|
enum server_type type;
|
|
enum server_type server_query_type;
|
|
struct master *m;
|
|
|
|
str = strdup_strip (action);
|
|
|
|
if (!str || !*str)
|
|
return;
|
|
|
|
n = tokenize (str, token, 4, " \t\n\r");
|
|
|
|
if (n == 4) {
|
|
char* qstat_query_arg = master_id2type (token[1], &type, &server_query_type);
|
|
if (type != UNKNOWN_SERVER) {
|
|
|
|
if (g_ascii_strcasecmp (token[0], ACTION_ADD) == 0) {
|
|
m = add_master (token[2], token[3], type, server_query_type, qstat_query_arg, FALSE, FALSE);
|
|
if (m && source_ctree != NULL)
|
|
source_ctree_add_master (source_ctree, m);
|
|
}
|
|
else if (g_ascii_strcasecmp (token[0], ACTION_DELETE) == 0) {
|
|
m = add_master (token[2], token[3], type, server_query_type, qstat_query_arg, FALSE, TRUE);
|
|
if (m) {
|
|
if (source_ctree != NULL)
|
|
source_ctree_delete_master (source_ctree, m);
|
|
free_master (m);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
g_free (str);
|
|
}
|
|
|
|
|
|
void update_master_list_builtin (void) {
|
|
char **ptr;
|
|
|
|
for (ptr = builtin_masters_update_info; *ptr; ptr++)
|
|
update_master_list_action (*ptr);
|
|
}
|
|
|
|
void update_master_gslist_builtin (void) {
|
|
char **ptr;
|
|
|
|
for (ptr = builtin_gslist_masters_update_info; *ptr; ptr++)
|
|
update_master_list_action (*ptr);
|
|
}
|
|
|
|
// Refresh list of games and masters in left pane
|
|
void refresh_source_list (void) {
|
|
GtkCTreeNode *node;
|
|
GSList *list;
|
|
struct master *m;
|
|
struct master *group = NULL;
|
|
|
|
// Re-add masters to the list, or remove them if game is not
|
|
// configured and set to show only configured games.
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
|
|
if (m == favorites || m->isgroup)
|
|
continue;
|
|
|
|
// Look for existing entry in ctree so we only re-add if needed,
|
|
// otherwise everthing gets re-expanded
|
|
node = gtk_ctree_find_by_row_data (GTK_CTREE (source_ctree), NULL, m);
|
|
|
|
// If it's not there check to see if we should add it
|
|
if (!node) // It's not in the list so maybe we should add it
|
|
{
|
|
if (!default_show_only_configured_games || games[m->type].cmd)
|
|
source_ctree_add_master (source_ctree, m);
|
|
}
|
|
else // It's in the list so maybe we should delete it
|
|
if (default_show_only_configured_games && !games[m->type].cmd)
|
|
source_ctree_delete_master (source_ctree, m);
|
|
}
|
|
// Remove master group if set to show only configured games.
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
|
|
if (m == favorites)
|
|
continue;
|
|
|
|
if (default_show_only_configured_games && !games[m->type].cmd) {
|
|
group = (struct master *) g_slist_nth_data (master_groups, m->type);
|
|
source_ctree_remove_master_group (source_ctree, group);
|
|
}
|
|
}
|
|
}
|
|
|
|
void update_master_list_web (void) {
|
|
char **ptr;
|
|
|
|
for (ptr = builtin_masters_update_info; *ptr; ptr++)
|
|
update_master_list_action (*ptr);
|
|
}
|
|
|
|
|
|
static void load_master_list (void) {
|
|
struct master *m;
|
|
enum server_type type;
|
|
enum server_type server_query_type;
|
|
char* qstat_query_arg = NULL;
|
|
char conf[64];
|
|
char *token[3];
|
|
char *str;
|
|
char *tmp;
|
|
int user;
|
|
int i = 0;
|
|
int n;
|
|
|
|
config_push_prefix ("/" CONFIG_FILE "/Master List");
|
|
|
|
while (1) {
|
|
g_snprintf (conf, 64, "master%d", i);
|
|
str = config_get_string (conf);
|
|
|
|
if (!str)
|
|
break;
|
|
|
|
n = tokenize (str, token, 3, " \t\n\r");
|
|
|
|
if (n == 3) {
|
|
|
|
user = FALSE;
|
|
tmp = strrchr (token[0], ',');
|
|
if (tmp) {
|
|
user = (g_ascii_strcasecmp (tmp+1, "USER") == 0);
|
|
if (user) {
|
|
*tmp = '\0';
|
|
}
|
|
}
|
|
qstat_query_arg = master_id2type (token[0], &type, &server_query_type);
|
|
|
|
if (type != UNKNOWN_SERVER) {
|
|
|
|
/* Use 'user' mode to insert master to the master list
|
|
* and fix m->user after that.
|
|
*/
|
|
|
|
m = add_master (token[1], token[2], type, server_query_type, qstat_query_arg, TRUE, FALSE);
|
|
if (m)
|
|
m->user = user;
|
|
}
|
|
|
|
}
|
|
|
|
g_free (str);
|
|
i++;
|
|
}
|
|
|
|
config_pop_prefix ();
|
|
}
|
|
|
|
|
|
static void save_master_list (void) {
|
|
GSList *list;
|
|
struct master *m;
|
|
char conf[64];
|
|
char typeid[128] = {0};
|
|
char *confstr;
|
|
char *str;
|
|
int n = 0;
|
|
|
|
config_clean_section ("/" CONFIG_FILE "/Master List");
|
|
config_push_prefix ("/" CONFIG_FILE "/Master List");
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
|
|
if (m == favorites || m->isgroup)
|
|
continue;
|
|
|
|
g_snprintf (conf, 64, "master%d", n);
|
|
|
|
g_snprintf (typeid, sizeof(typeid), "%s%s%s%s%s%s",
|
|
type2id (m->type),
|
|
m->server_query_type != UNKNOWN_SERVER? "|" : "",
|
|
m->server_query_type != UNKNOWN_SERVER? type2id (m->server_query_type) : "",
|
|
m->_qstat_master_option? "," : "",
|
|
m->_qstat_master_option? m->_qstat_master_option : "",
|
|
m->user? ",USER" : "");
|
|
|
|
str = master_to_url(m);
|
|
confstr = g_strjoin (" ", typeid, str, m->name, NULL);
|
|
|
|
config_set_string (conf, confstr);
|
|
|
|
g_free (str);
|
|
g_free (confstr);
|
|
n++;
|
|
}
|
|
|
|
config_pop_prefix ();
|
|
}
|
|
|
|
void init_masters (int update) {
|
|
typedef void (*server_unref_void)(void*);
|
|
struct master *m;
|
|
struct master *m2;
|
|
int i;
|
|
GSList *list;
|
|
|
|
favorites = create_master (N_("Favorites"), UNKNOWN_SERVER, FALSE);
|
|
|
|
for (i = LAN_SERVER; i < UNKNOWN_SERVER; i++) {
|
|
m = create_master (games[i].name, i, TRUE);
|
|
|
|
if (i == LAN_SERVER) {
|
|
m->master_type = MASTER_LAN;
|
|
g_free(m->name);
|
|
m->name = g_strdup(N_("Local network"));
|
|
// HACK: make LAN server name translatable
|
|
games[i].name = m->name;
|
|
}
|
|
|
|
master_groups = g_slist_append (master_groups, m);
|
|
}
|
|
|
|
load_master_list ();
|
|
|
|
if (update) {
|
|
update_master_list_builtin ();
|
|
if (have_gslist_masters())
|
|
update_master_gslist_builtin();
|
|
config_sync ();
|
|
}
|
|
|
|
compat_convert_favorites ();
|
|
|
|
read_lists (FILENAME_FAVORITES);
|
|
debug (2, "starting to read server list");
|
|
read_lists (FILENAME_LISTS);
|
|
debug (2, "finished reading server list");
|
|
|
|
// Go through all servers, sort them and then remove duplicates
|
|
// master_add_server now uses server_list_prepend_ndp which does not
|
|
// search for duplicates while adding servers. This is much faster.
|
|
// Because of this, duplicate checking must be done here.
|
|
// Only working on servers, not uservers. Lists file only contains IPs.
|
|
debug (2, "Searching for duplicate server entries");
|
|
|
|
// for each master
|
|
for (list = all_masters; list; list = list->next) {
|
|
m2 = (struct master *) list->data;
|
|
if (!m2)
|
|
continue;
|
|
debug (2, " Working on master: %s",m2->name);
|
|
|
|
m2->servers = slist_sort_remove_dups(m2->servers,(GCompareFunc)server_sorting_helper,(server_unref_void)server_unref);
|
|
|
|
}
|
|
|
|
read_server_info (FILENAME_SRVINFO);
|
|
}
|
|
|
|
|
|
void free_masters (void) {
|
|
GSList *tmp;
|
|
|
|
debug (6, "free_masters() --");
|
|
save_favorites ();
|
|
|
|
if (default_save_lists) {
|
|
tmp = g_slist_copy (all_masters);
|
|
tmp = g_slist_remove (tmp, favorites);
|
|
save_lists (tmp, FILENAME_LISTS);
|
|
g_slist_free (tmp);
|
|
|
|
if (default_save_srvinfo) {
|
|
tmp = all_servers (); /* free done in two lines */
|
|
save_server_info (FILENAME_SRVINFO, tmp);
|
|
server_list_free (tmp);
|
|
}
|
|
}
|
|
else {
|
|
save_lists (NULL, FILENAME_LISTS);
|
|
if (default_save_srvinfo) {
|
|
save_server_info (FILENAME_SRVINFO, favorites->servers);
|
|
}
|
|
}
|
|
|
|
save_master_list ();
|
|
|
|
free_master (favorites);
|
|
favorites = NULL;
|
|
|
|
g_slist_foreach (master_groups, (GFunc) free_master, NULL);
|
|
g_slist_free (master_groups);
|
|
master_groups = NULL;
|
|
}
|
|
|
|
|
|
static inline GSList *master_list_add_sigle (GSList *list, struct master *m) {
|
|
if (g_slist_find (list, m) == NULL)
|
|
list = g_slist_append (list, m);
|
|
return list;
|
|
}
|
|
|
|
|
|
static void master_list_add (GSList **masters, GSList **servers,
|
|
GSList **uservers, struct master *m) {
|
|
GSList *tmp;
|
|
|
|
debug (6, "master_list_add() -- master '%s'", m->name);
|
|
if (m->isgroup || m == favorites) {
|
|
if (servers) {
|
|
*servers = server_list_append_list (*servers, favorites->servers,
|
|
m->type);
|
|
}
|
|
if (uservers) {
|
|
*uservers = userver_list_append_list (*uservers, favorites->uservers,
|
|
m->type);
|
|
}
|
|
}
|
|
|
|
if (masters) {
|
|
if (m->isgroup) {
|
|
for (tmp = m->masters; tmp; tmp = tmp->next)
|
|
*masters = master_list_add_sigle (*masters,
|
|
(struct master *) tmp->data);
|
|
}
|
|
else {
|
|
if (m != favorites)
|
|
*masters = master_list_add_sigle (*masters, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void collate_server_lists (GSList *masters, GSList **servers,
|
|
GSList **uservers) {
|
|
struct master *m;
|
|
GSList *tmp;
|
|
|
|
for (tmp = masters; tmp; tmp = tmp->next) {
|
|
m = (struct master *) tmp->data;
|
|
if (servers) {
|
|
*servers = server_list_append_list (*servers, m->servers,
|
|
UNKNOWN_SERVER);
|
|
}
|
|
if (uservers) {
|
|
*uservers = userver_list_append_list (*uservers, m->uservers,
|
|
UNKNOWN_SERVER);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void master_selection_to_lists (GSList *list, GSList **masters,
|
|
GSList **servers, GSList **uservers) {
|
|
struct master *m;
|
|
GSList *tmp;
|
|
debug (6, "master_selection_to_lists() --");
|
|
for (tmp = list; tmp; tmp = tmp->next) {
|
|
m = (struct master *) tmp->data;
|
|
uservers_to_servers (&m->uservers, &m->servers);
|
|
master_list_add (masters, servers, uservers, m);
|
|
}
|
|
}
|
|
|
|
|
|
int source_has_masters_to_update (GSList *source) {
|
|
GSList *tmp;
|
|
struct master *m;
|
|
|
|
for (tmp = source; tmp; tmp = tmp->next) {
|
|
m = (struct master *) tmp->data;
|
|
if (m->isgroup) {
|
|
if (source_has_masters_to_update (m->masters))
|
|
return TRUE;
|
|
}
|
|
else {
|
|
if (m != favorites)
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int source_has_masters_to_delete (GSList *source) {
|
|
GSList *tmp;
|
|
struct master *m;
|
|
|
|
for (tmp = source; tmp; tmp = tmp->next) {
|
|
m = (struct master *) tmp->data;
|
|
if (m != favorites && !m->isgroup)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
GSList *references_to_server (struct server *s) {
|
|
GSList *list;
|
|
GSList *res = NULL;
|
|
struct master *m;
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
if (g_slist_find (m->servers, s))
|
|
res = g_slist_append (res, m);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
enum master_query_type get_master_query_type_from_address(const gchar* address) {
|
|
enum master_query_type type;
|
|
// check for known master prefix
|
|
debug(6,"get_master_query_type_from_address(%s)",address);
|
|
for (type=MASTER_NATIVE;type<MASTER_NUM_QUERY_TYPES;type++) {
|
|
if (!g_ascii_strncasecmp(address, master_prefixes[type],
|
|
strlen(master_prefixes[type]))) {
|
|
debug(6,"get_master_query_type_from_address: found %s",master_prefixes[type]);
|
|
return type;
|
|
}
|
|
}
|
|
// only accept if there is no :// part at all
|
|
if (lowcasestrstr(address,"://")) {
|
|
debug(6,"get_master_query_type_from_address: invalid");
|
|
return MASTER_INVALID_TYPE;
|
|
}
|
|
else {
|
|
debug(6,"get_master_query_type_from_address: default native");
|
|
return MASTER_NATIVE;
|
|
}
|
|
}
|
|
|
|
gboolean have_gslist_masters() {
|
|
GSList *list;
|
|
struct master *m;
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
if (m->master_type == MASTER_GSLIST)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean have_gslist_installed() {
|
|
char* gslist = find_file_in_path("gslist");
|
|
g_free(gslist);
|
|
return (gslist != NULL);
|
|
}
|
|
|
|
void master_remove_server(struct master* m, struct server* s) {
|
|
if (!m || !s)
|
|
return;
|
|
|
|
m->servers = server_list_remove(m->servers, s);
|
|
}
|
|
|
|
void server_remove_from_all(struct server *s) {
|
|
GSList *list;
|
|
struct master *m;
|
|
|
|
for (list = all_masters; list; list = list->next) {
|
|
m = (struct master *) list->data;
|
|
|
|
if (m == favorites)
|
|
continue;
|
|
|
|
m->servers = server_list_remove(m->servers, s);
|
|
}
|
|
}
|