mirror of
https://github.com/irssi/irssi.git
synced 2026-08-10 20:33:46 +02:00
Config file: ircnets -> chatnets, added type = "chat protocol" to
chatnet config. Moved reading chatnets to core. Lots of other multiprotocol updates. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@1237 dbcabf3a-b0e7-0310-adc4-f8d773084564
This commit is contained in:
parent
513e140bce
commit
be6ba53fa4
35 changed files with 539 additions and 448 deletions
|
|
@ -27,6 +27,8 @@
|
|||
#include "misc.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "chat-protocols.h"
|
||||
#include "chatnets.h"
|
||||
#include "channels.h"
|
||||
#include "channels-setup.h"
|
||||
#include "nicklist.h"
|
||||
|
|
@ -232,6 +234,7 @@ static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
|
|||
static void cmd_channel_add(const char *data)
|
||||
{
|
||||
GHashTable *optlist;
|
||||
CHATNET_REC *chatnetrec;
|
||||
CHANNEL_SETUP_REC *rec;
|
||||
char *botarg, *botcmdarg, *chatnet, *channel, *password;
|
||||
void *free_arg;
|
||||
|
|
@ -240,14 +243,23 @@ static void cmd_channel_add(const char *data)
|
|||
"channel add", &optlist, &channel, &chatnet, &password))
|
||||
return;
|
||||
|
||||
if (*chatnet == '\0' || *channel == '\0')
|
||||
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
|
||||
|
||||
chatnetrec = chatnet_find(chatnet);
|
||||
if (chatnetrec == NULL) {
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
|
||||
TXT_UNKNOWN_CHATNET, chatnet);
|
||||
cmd_params_free(free_arg);
|
||||
return;
|
||||
}
|
||||
|
||||
botarg = g_hash_table_lookup(optlist, "bots");
|
||||
botcmdarg = g_hash_table_lookup(optlist, "botcmd");
|
||||
|
||||
if (*chatnet == '\0' || *channel == '\0')
|
||||
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
|
||||
rec = channels_setup_find(channel, chatnet);
|
||||
rec = channel_setup_find(channel, chatnet);
|
||||
if (rec == NULL) {
|
||||
rec = g_new0(CHANNEL_SETUP_REC, 1);
|
||||
rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
|
||||
rec->name = g_strdup(channel);
|
||||
rec->chatnet = g_strdup(chatnet);
|
||||
} else {
|
||||
|
|
@ -260,8 +272,12 @@ static void cmd_channel_add(const char *data)
|
|||
if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg);
|
||||
if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg);
|
||||
if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password);
|
||||
channels_setup_create(rec);
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_ADDED, channel, chatnet);
|
||||
|
||||
signal_emit("channel add fill", 2, rec, optlist);
|
||||
|
||||
channel_setup_create(rec);
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
|
||||
TXT_CHANSETUP_ADDED, channel, chatnet);
|
||||
|
||||
cmd_params_free(free_arg);
|
||||
}
|
||||
|
|
@ -278,12 +294,12 @@ static void cmd_channel_remove(const char *data)
|
|||
if (*chatnet == '\0' || *channel == '\0')
|
||||
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
|
||||
|
||||
rec = channels_setup_find(channel, chatnet);
|
||||
rec = channel_setup_find(channel, chatnet);
|
||||
if (rec == NULL)
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
|
||||
else {
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet);
|
||||
channels_setup_destroy(rec);
|
||||
channel_setup_remove(rec);
|
||||
}
|
||||
cmd_params_free(free_arg);
|
||||
}
|
||||
|
|
@ -317,6 +333,10 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
|
|||
g_free(stripped);
|
||||
g_free(format);
|
||||
|
||||
if (settings_get_int("names_max_width") > 0 &&
|
||||
max_width > settings_get_int("names_max_width"))
|
||||
max_width = settings_get_int("names_max_width");
|
||||
|
||||
/* remove width of timestamp from max_width */
|
||||
format_create_dest(&dest, channel->server, channel->name,
|
||||
MSGLEVEL_CLIENTCRAP, NULL);
|
||||
|
|
@ -329,10 +349,6 @@ static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
|
|||
}
|
||||
|
||||
/* calculate columns */
|
||||
if (settings_get_int("names_max_width") > 0 &&
|
||||
max_width > settings_get_int("names_max_width"))
|
||||
max_width = settings_get_int("names_max_width");
|
||||
|
||||
cols = get_max_column_count(nicklist, get_nick_length, max_width,
|
||||
settings_get_int("names_max_columns"),
|
||||
item_extra, 3, &columns, &rows);
|
||||
|
|
|
|||
|
|
@ -212,8 +212,12 @@ void glog_func(const char *log_domain, GLogLevelFlags log_level,
|
|||
break;
|
||||
}
|
||||
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
|
||||
TXT_GLIB_ERROR, reason, message);
|
||||
if (windows == NULL)
|
||||
fprintf(stderr, "GLib %s: %s", reason, message);
|
||||
else {
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
|
||||
TXT_GLIB_ERROR, reason, message);
|
||||
}
|
||||
}
|
||||
|
||||
void fe_common_core_finish_init(void)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ static THEME_REC *log_theme;
|
|||
static int skip_next_printtext;
|
||||
static const char *log_theme_name;
|
||||
|
||||
static void log_add_targets(LOG_REC *log, const char *targets)
|
||||
static void log_add_targets(LOG_REC *log, const char *targets, const char *tag)
|
||||
{
|
||||
char **tmp, **items;
|
||||
|
||||
|
|
@ -58,15 +58,16 @@ static void log_add_targets(LOG_REC *log, const char *targets)
|
|||
items = g_strsplit(targets, " ", -1);
|
||||
|
||||
for (tmp = items; *tmp != NULL; tmp++)
|
||||
log_item_add(log, LOG_ITEM_TARGET, *tmp, NULL);
|
||||
log_item_add(log, LOG_ITEM_TARGET, *tmp, tag);
|
||||
|
||||
g_strfreev(items);
|
||||
}
|
||||
|
||||
/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-targets <targets>]
|
||||
[-window] <fname> [<levels>] */
|
||||
/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-window] [-<server tag>]
|
||||
[-targets <targets>] <fname> [<levels>] */
|
||||
static void cmd_log_open(const char *data)
|
||||
{
|
||||
SERVER_REC *server;
|
||||
GHashTable *optlist;
|
||||
char *targetarg, *fname, *levels;
|
||||
void *free_arg;
|
||||
|
|
@ -74,7 +75,8 @@ static void cmd_log_open(const char *data)
|
|||
LOG_REC *log;
|
||||
int level;
|
||||
|
||||
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST,
|
||||
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
|
||||
PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_OPTIONS,
|
||||
"log open", &optlist, &fname, &levels))
|
||||
return;
|
||||
if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
|
||||
|
|
@ -82,14 +84,17 @@ static void cmd_log_open(const char *data)
|
|||
level = level2bits(levels);
|
||||
log = log_create_rec(fname, level != 0 ? level : MSGLEVEL_ALL);
|
||||
|
||||
/* -<server tag> */
|
||||
server = cmd_options_get_server("join", optlist, NULL);
|
||||
|
||||
if (g_hash_table_lookup(optlist, "window")) {
|
||||
/* log by window ref# */
|
||||
ltoa(window, active_win->refnum);
|
||||
log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
|
||||
log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, server->tag);
|
||||
} else {
|
||||
targetarg = g_hash_table_lookup(optlist, "targets");
|
||||
if (targetarg != NULL && *targetarg != '\0')
|
||||
log_add_targets(log, targetarg);
|
||||
log_add_targets(log, targetarg, server->tag);
|
||||
}
|
||||
|
||||
if (g_hash_table_lookup(optlist, "autoopen"))
|
||||
|
|
@ -217,7 +222,7 @@ static void cmd_log(const char *data, SERVER_REC *server, void *item)
|
|||
}
|
||||
|
||||
static LOG_REC *logs_find_item(int type, const char *item,
|
||||
SERVER_REC *server, LOG_ITEM_REC **ret_item)
|
||||
const char *servertag, LOG_ITEM_REC **ret_item)
|
||||
{
|
||||
LOG_ITEM_REC *logitem;
|
||||
GSList *tmp;
|
||||
|
|
@ -225,7 +230,7 @@ static LOG_REC *logs_find_item(int type, const char *item,
|
|||
for (tmp = logs; tmp != NULL; tmp = tmp->next) {
|
||||
LOG_REC *log = tmp->data;
|
||||
|
||||
logitem = log_item_find(log, type, item, server);
|
||||
logitem = log_item_find(log, type, item, servertag);
|
||||
if (logitem != NULL) {
|
||||
if (ret_item != NULL) *ret_item = logitem;
|
||||
return log;
|
||||
|
|
@ -338,12 +343,13 @@ static void autologs_close_all(void)
|
|||
}
|
||||
}
|
||||
|
||||
static void autolog_open(void *server, const char *target)
|
||||
static void autolog_open(SERVER_REC *server, const char *target)
|
||||
{
|
||||
LOG_REC *log;
|
||||
char *fname, *dir, *fixed_target;
|
||||
char *fname, *dir, *fixed_target, *tag;
|
||||
|
||||
log = logs_find_item(LOG_ITEM_TARGET, target, server, NULL);
|
||||
tag = server == NULL ? NULL : server->tag;
|
||||
log = logs_find_item(LOG_ITEM_TARGET, target, tag, NULL);
|
||||
if (log != NULL && !log->failed) {
|
||||
log_start_logging(log);
|
||||
return;
|
||||
|
|
@ -359,7 +365,7 @@ static void autolog_open(void *server, const char *target)
|
|||
|
||||
if (log_find(fname) == NULL) {
|
||||
log = log_create_rec(fname, autolog_level);
|
||||
log_item_add(log, LOG_ITEM_TARGET, target, server);
|
||||
log_item_add(log, LOG_ITEM_TARGET, target, tag);
|
||||
|
||||
dir = g_dirname(log->real_fname);
|
||||
mkpath(dir, LOG_DIR_CREATE_MODE);
|
||||
|
|
@ -372,7 +378,8 @@ static void autolog_open(void *server, const char *target)
|
|||
g_free(fname);
|
||||
}
|
||||
|
||||
static void autolog_open_check(void *server, const char *target, int level)
|
||||
static void autolog_open_check(SERVER_REC *server, const char *target,
|
||||
int level)
|
||||
{
|
||||
char **targets, **tmp;
|
||||
|
||||
|
|
@ -410,7 +417,7 @@ static void log_single_line(WINDOW_REC *window, void *server,
|
|||
}
|
||||
}
|
||||
|
||||
static void log_line(WINDOW_REC *window, void *server,
|
||||
static void log_line(WINDOW_REC *window, SERVER_REC *server,
|
||||
const char *target, int level, const char *text)
|
||||
{
|
||||
char **lines, **tmp;
|
||||
|
|
@ -513,7 +520,9 @@ static void sig_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item)
|
|||
{
|
||||
LOG_REC *log;
|
||||
|
||||
log = logs_find_item(LOG_ITEM_TARGET, item->name, item->server, NULL);
|
||||
log = logs_find_item(LOG_ITEM_TARGET, item->name,
|
||||
item->server == NULL ? NULL :
|
||||
item->server->tag, NULL);
|
||||
if (log != NULL && log->temp)
|
||||
log_close(log);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@
|
|||
#include "modules.h"
|
||||
#include "signals.h"
|
||||
#include "commands.h"
|
||||
#include "levels.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "levels.h"
|
||||
#include "chat-protocols.h"
|
||||
#include "queries.h"
|
||||
|
||||
#include "fe-windows.h"
|
||||
|
|
@ -46,8 +47,8 @@ QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
|
|||
query = query_find(server, nick);
|
||||
if (query == NULL && (querycreate_level & level) != 0 &&
|
||||
(!own || settings_get_bool("autocreate_own_query"))) {
|
||||
query = query_create(server->chat_type, server->tag,
|
||||
nick, TRUE);
|
||||
query = CHAT_PROTOCOL(server)->
|
||||
query_create(server->tag, nick, TRUE);
|
||||
}
|
||||
|
||||
return query;
|
||||
|
|
@ -228,7 +229,7 @@ static void cmd_query(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
|
|||
|
||||
query = query_find(server, nick);
|
||||
if (query == NULL)
|
||||
query_create(server->chat_type, server->tag, nick, FALSE);
|
||||
CHAT_PROTOCOL(server)->query_create(server->tag, nick, FALSE);
|
||||
else {
|
||||
/* query already existed - set query active / move it to this
|
||||
window */
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
fe-server.c : irssi
|
||||
|
||||
Copyright (C) 1999 Timo Sirainen
|
||||
Copyright (C) 1999-2001 Timo Sirainen
|
||||
|
||||
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
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
#include "settings.h"
|
||||
|
||||
#include "chat-protocols.h"
|
||||
#include "chatnets.h"
|
||||
#include "servers.h"
|
||||
#include "servers-setup.h"
|
||||
#include "servers-reconnect.h"
|
||||
|
|
@ -80,9 +81,29 @@ static void print_reconnects(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* SYNTAX: SERVER ADD [-auto | -noauto] [-host <hostname>]
|
||||
[-cmdspeed <ms>] [-cmdmax <count>] [-port <port>]
|
||||
<address> [<port> [<password>]] */
|
||||
static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist)
|
||||
{
|
||||
CHAT_PROTOCOL_REC *rec;
|
||||
SERVER_SETUP_REC *server;
|
||||
char *chatnet;
|
||||
|
||||
rec = chat_protocol_find_net(optlist);
|
||||
if (rec == NULL)
|
||||
rec = chat_protocol_get_default();
|
||||
else {
|
||||
chatnet = g_hash_table_lookup(optlist, rec->chatnet);
|
||||
if (chatnet_find(chatnet) == NULL) {
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
|
||||
TXT_UNKNOWN_CHATNET, chatnet);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
server = rec->create_server_setup();
|
||||
server->chat_type = rec->id;
|
||||
return server;
|
||||
}
|
||||
|
||||
static void cmd_server_add(const char *data)
|
||||
{
|
||||
GHashTable *optlist;
|
||||
|
|
@ -100,16 +121,10 @@ static void cmd_server_add(const char *data)
|
|||
|
||||
rec = server_setup_find_port(addr, port);
|
||||
if (rec == NULL) {
|
||||
signal_emit("server add create", 2, &rec, optlist);
|
||||
rec = create_server_setup(optlist);
|
||||
if (rec == NULL) {
|
||||
/* no chatnet option specified, use the first. */
|
||||
g_hash_table_insert(optlist, chat_protocol_find_id(1)->name, "");
|
||||
signal_emit("server add create", 2, &rec, optlist);
|
||||
if (rec == NULL) {
|
||||
/* bug? */
|
||||
cmd_params_free(free_arg);
|
||||
return;
|
||||
}
|
||||
cmd_params_free(free_arg);
|
||||
return;
|
||||
}
|
||||
rec->address = g_strdup(addr);
|
||||
rec->port = port;
|
||||
|
|
@ -137,7 +152,8 @@ static void cmd_server_add(const char *data)
|
|||
signal_emit("server add fill", 2, rec, optlist);
|
||||
|
||||
server_setup_add(rec);
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_ADDED, addr, port);
|
||||
printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
|
||||
TXT_SETUPSERVER_ADDED, addr, port);
|
||||
|
||||
cmd_params_free(free_arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ FORMAT_REC fecommon_core_formats[] = {
|
|||
/* ---- */
|
||||
{ NULL, "Misc", 0 },
|
||||
|
||||
{ "unknown_chatnet", "Unknown chat network: $0", 1, { 0 } },
|
||||
{ "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 },
|
||||
{ "perl_error", "Perl error: $0", 1, { 0 } },
|
||||
{ "bind_key", "$[10]0 $1 $2", 3, { 0, 0, 0 } },
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ enum {
|
|||
|
||||
TXT_FILL_13,
|
||||
|
||||
TXT_UNKNOWN_CHATNET,
|
||||
TXT_NOT_TOGGLE,
|
||||
TXT_PERL_ERROR,
|
||||
TXT_BIND_KEY,
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ static void sig_window_restore_item(WINDOW_REC *window, const char *type,
|
|||
rec->sticky = TRUE;
|
||||
} else if (g_strcasecmp(type, "QUERY") == 0 && chat_type != NULL) {
|
||||
/* create query immediately */
|
||||
query_create(chat_protocol_lookup(chat_type),
|
||||
tag, name, TRUE);
|
||||
chat_protocol_find(chat_type)->query_create(tag, name, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,20 +34,9 @@
|
|||
#include "fe-windows.h"
|
||||
#include "printtext.h"
|
||||
|
||||
static void sig_server_add_create(IRC_SERVER_SETUP_REC **rec,
|
||||
GHashTable *optlist)
|
||||
{
|
||||
char *ircnet;
|
||||
|
||||
ircnet = g_hash_table_lookup(optlist, "ircnet");
|
||||
if (ircnet == NULL)
|
||||
return;
|
||||
|
||||
*rec = g_new0(IRC_SERVER_SETUP_REC, 1);
|
||||
(*rec)->chat_type = chat_protocol_lookup("IRC");
|
||||
signal_stop();
|
||||
}
|
||||
|
||||
/* SYNTAX: SERVER ADD [-auto | -noauto] [-ircnet <ircnet>] [-host <hostname>]
|
||||
[-cmdspeed <ms>] [-cmdmax <count>] [-port <port>]
|
||||
<address> [<port> [<password>]] */
|
||||
static void sig_server_add_fill(IRC_SERVER_SETUP_REC *rec,
|
||||
GHashTable *optlist)
|
||||
{
|
||||
|
|
@ -107,7 +96,6 @@ static void cmd_server_list(const char *data)
|
|||
|
||||
void fe_irc_server_init(void)
|
||||
{
|
||||
signal_add("server add create", (SIGNAL_FUNC) sig_server_add_create);
|
||||
signal_add("server add fill", (SIGNAL_FUNC) sig_server_add_fill);
|
||||
command_bind("server list", NULL, (SIGNAL_FUNC) cmd_server_list);
|
||||
|
||||
|
|
@ -116,7 +104,6 @@ void fe_irc_server_init(void)
|
|||
|
||||
void fe_irc_server_deinit(void)
|
||||
{
|
||||
signal_remove("server add create", (SIGNAL_FUNC) sig_server_add_create);
|
||||
signal_remove("server add fill", (SIGNAL_FUNC) sig_server_add_fill);
|
||||
command_unbind("server list", (SIGNAL_FUNC) cmd_server_list);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,11 +79,11 @@ static void cmd_ircnet_list(void)
|
|||
printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_IRCNET_FOOTER);
|
||||
}
|
||||
|
||||
/* SYNTAX: IRCNET ADD [-kicks <count>] [-msgs <count>] [-modes <count>]
|
||||
[-whois <count>] [-cmdspeed <ms>] [-cmdmax <count>]
|
||||
[-querychans <count>] [-nick <nick>] [-user <user>]
|
||||
[-realname <name>] [-host <host>] [-autosendcmd <cmd>]
|
||||
<name> */
|
||||
/* SYNTAX: IRCNET ADD [-nick <nick>] [-user <user>] [-realname <name>]
|
||||
[-host <host>] [-autosendcmd <cmd>]
|
||||
[-querychans <count>] [-whois <count>] [-msgs <count>]
|
||||
[-kicks <count>] [-modes <count>]
|
||||
[-cmdspeed <ms>] [-cmdmax <count>] <name> */
|
||||
static void cmd_ircnet_add(const char *data)
|
||||
{
|
||||
GHashTable *optlist;
|
||||
|
|
@ -176,6 +176,7 @@ static void cmd_ircnet(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *it
|
|||
void fe_ircnet_init(void)
|
||||
{
|
||||
command_bind("ircnet", NULL, (SIGNAL_FUNC) cmd_ircnet);
|
||||
command_bind("ircnet list", NULL, (SIGNAL_FUNC) cmd_ircnet_list);
|
||||
command_bind("ircnet add", NULL, (SIGNAL_FUNC) cmd_ircnet_add);
|
||||
command_bind("ircnet remove", NULL, (SIGNAL_FUNC) cmd_ircnet_remove);
|
||||
|
||||
|
|
@ -185,6 +186,7 @@ void fe_ircnet_init(void)
|
|||
void fe_ircnet_deinit(void)
|
||||
{
|
||||
command_unbind("ircnet", (SIGNAL_FUNC) cmd_ircnet);
|
||||
command_unbind("ircnet list", (SIGNAL_FUNC) cmd_ircnet_list);
|
||||
command_unbind("ircnet add", (SIGNAL_FUNC) cmd_ircnet_add);
|
||||
command_unbind("ircnet remove", (SIGNAL_FUNC) cmd_ircnet_remove);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue