Knocked up some commands and stuff to make qtv not forget about streams that failed. Finally implemented the commands command. Added commands to enable/disable streams.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2812 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2007-12-12 11:17:18 +00:00
parent 81b91a422d
commit 6006580d8a
5 changed files with 303 additions and 141 deletions

View file

@ -344,7 +344,7 @@ static void ParseStufftext(sv_t *tv, netmsg_t *m, int to, unsigned int mask)
SendClientCommand(tv, "new\n"); SendClientCommand(tv, "new\n");
return; return;
} }
tv->drop = true; //this shouldn't ever happen Sys_Printf(tv->cluster, "packet stuffcmd in an mvd\n"); //shouldn't ever happen, try ignoring it.
return; return;
} }
else if (tv->usequakeworldprotocols && !strncmp(text, "setinfo ", 8)) else if (tv->usequakeworldprotocols && !strncmp(text, "setinfo ", 8))
@ -1380,6 +1380,7 @@ void ParseNails(sv_t *tv, netmsg_t *m, qboolean nails2)
void ParseDownload(sv_t *tv, netmsg_t *m) void ParseDownload(sv_t *tv, netmsg_t *m)
{ {
#warning this needs looking at (controller downloads)
int size, b; int size, b;
unsigned int percent; unsigned int percent;
char buffer[2048]; char buffer[2048];
@ -1393,7 +1394,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (tv->downloadfile) if (tv->downloadfile)
fclose(tv->downloadfile); fclose(tv->downloadfile);
tv->downloadfile = NULL; tv->downloadfile = NULL;
tv->drop = true; tv->errored = ERR_PERMANENT;
QW_StreamPrint(tv->cluster, tv, NULL, "Map download failed\n"); QW_StreamPrint(tv->cluster, tv, NULL, "Map download failed\n");
return; return;
} }
@ -1404,7 +1405,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (!tv->downloadfile) if (!tv->downloadfile)
{ {
Sys_Printf(tv->cluster, "Not downloading anything\n"); Sys_Printf(tv->cluster, "Not downloading anything\n");
tv->drop = true; tv->errored = ERR_PERMANENT;
return; return;
} }
fwrite(buffer, 1, size, tv->downloadfile); fwrite(buffer, 1, size, tv->downloadfile);
@ -1423,7 +1424,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (!tv->bsp) if (!tv->bsp)
{ {
Sys_Printf(tv->cluster, "Failed to read BSP\n"); Sys_Printf(tv->cluster, "Failed to read BSP\n");
tv->drop = true; tv->errored = ERR_PERMANENT;
} }
else else
{ {

View file

@ -478,6 +478,14 @@ typedef enum {
SRC_TCP SRC_TCP
} sourcetype_t; } sourcetype_t;
typedef enum {
ERR_NONE, //stream is fine
ERR_RECONNECT, //stream needs to reconnect
ERR_PERMANENT, //permanent error, transitioning to disabled next frame
ERR_DISABLED, //stream is disabled, can be set to reconnect by admin
ERR_DROP //stream _will_ be forgotten about next frame
} errorstate_t;
struct sv_s { //details about a server connection (also known as stream) struct sv_s { //details about a server connection (also known as stream)
char connectpassword[64]; //password given to server char connectpassword[64]; //password given to server
netadr_t serveraddress; netadr_t serveraddress;
@ -580,7 +588,7 @@ struct sv_s { //details about a server connection (also known as stream)
qboolean drop; errorstate_t errored;
qboolean disconnectwhennooneiswatching; qboolean disconnectwhennooneiswatching;
unsigned int numviewers; unsigned int numviewers;

View file

@ -3644,13 +3644,14 @@ void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m)
{ {
if (!qtv->bsp) if (!qtv->bsp)
{ {
QW_PrintfToViewer(v, "Proxy was unable to check your map version\n"); #warning do we still actually need to do this ourselves? Or can we just forward what the user stated?
qtv->drop = true; QW_PrintfToViewer(v, "QTV doesn't have that map (%s), sorry.\n", qtv->modellist[1].name);
qtv->errored = ERR_DROP;
} }
else if (crc != BSP_Checksum(qtv->bsp)) else if (crc != BSP_Checksum(qtv->bsp))
{ {
QW_PrintfToViewer(v, "Your map (%s) does not match the servers\n", qtv->modellist[1].name); QW_PrintfToViewer(v, "QTV's map (%s) does not match the servers\n", qtv->modellist[1].name);
qtv->drop = true; qtv->errored = ERR_DROP;
} }
} }
} }
@ -4484,7 +4485,7 @@ void QW_FreeViewer(cluster_t *cluster, viewer_t *viewer)
if (viewer->server->controller == viewer) if (viewer->server->controller == viewer)
{ {
if (viewer->server->disconnectwhennooneiswatching) if (viewer->server->disconnectwhennooneiswatching)
viewer->server->drop = true; viewer->server->errored = ERR_DROP;
else else
viewer->server->controller = NULL; viewer->server->controller = NULL;
} }

View file

@ -351,7 +351,7 @@ void Cmd_Hostname(cmdctxt_t *ctx)
if (Cmd_Argc(ctx) < 2) if (Cmd_Argc(ctx) < 2)
{ {
if (*ctx->cluster->hostname) if (*ctx->cluster->hostname)
Cmd_Printf(ctx, "Current hostname is %s\n", ctx->cluster->hostname); Cmd_Printf(ctx, "Current hostname is \"%s\"\n", ctx->cluster->hostname);
else else
Cmd_Printf(ctx, "No master server is currently set.\n"); Cmd_Printf(ctx, "No master server is currently set.\n");
} }
@ -429,7 +429,7 @@ void Cmd_AdminPassword(cmdctxt_t *ctx)
if (*ctx->cluster->adminpassword) if (*ctx->cluster->adminpassword)
Cmd_Printf(ctx, "An admin password is currently set\n"); Cmd_Printf(ctx, "An admin password is currently set\n");
else else
Cmd_Printf(ctx, "No admin passsword is currently set\n"); Cmd_Printf(ctx, "No admin password is currently set\n");
} }
else else
{ {
@ -472,6 +472,7 @@ void Cmd_QTVDemoList(cmdctxt_t *ctx)
void Cmd_GenericConnect(cmdctxt_t *ctx, char *method) void Cmd_GenericConnect(cmdctxt_t *ctx, char *method)
{ {
sv_t *sv;
char *address, *password; char *address, *password;
if (Cmd_Argc(ctx) < 2) if (Cmd_Argc(ctx) < 2)
{ {
@ -488,10 +489,11 @@ void Cmd_GenericConnect(cmdctxt_t *ctx, char *method)
memmove(address+strlen(method), address, ARG_LEN-(1+strlen(method))); memmove(address+strlen(method), address, ARG_LEN-(1+strlen(method)));
strncpy(address, method, strlen(method)); strncpy(address, method, strlen(method));
if (!QTV_NewServerConnection(ctx->cluster, address, password, false, false, false, false)) sv = QTV_NewServerConnection(ctx->cluster, address, password, false, false, false, false);
if (!sv)
Cmd_Printf(ctx, "Failed to connect to \"%s\", connection aborted\n", address); Cmd_Printf(ctx, "Failed to connect to \"%s\", connection aborted\n", address);
else
Cmd_Printf(ctx, "Source registered \"%s\"\n", address); Cmd_Printf(ctx, "Source registered \"%s\" as stream %i\n", address, sv->streamid);
} }
void Cmd_QTVConnect(cmdctxt_t *ctx) void Cmd_QTVConnect(cmdctxt_t *ctx)
@ -591,11 +593,12 @@ void Cmd_Say(cmdctxt_t *ctx)
void Cmd_Status(cmdctxt_t *ctx) void Cmd_Status(cmdctxt_t *ctx)
{ {
Cmd_Printf(ctx, "%i sources\n", ctx->cluster->numservers); Cmd_Printf(ctx, "QTV Status:\n");
Cmd_Printf(ctx, "%i viewers\n", ctx->cluster->numviewers); Cmd_Printf(ctx, " %i sources\n", ctx->cluster->numservers);
Cmd_Printf(ctx, "%i proxies\n", ctx->cluster->numproxies); Cmd_Printf(ctx, " %i viewers\n", ctx->cluster->numviewers);
Cmd_Printf(ctx, " %i proxies\n", ctx->cluster->numproxies);
Cmd_Printf(ctx, "Options:\n"); Cmd_Printf(ctx, "Common Options:\n");
Cmd_Printf(ctx, " Hostname %s\n", ctx->cluster->hostname); Cmd_Printf(ctx, " Hostname %s\n", ctx->cluster->hostname);
if (ctx->cluster->chokeonnotupdated) if (ctx->cluster->chokeonnotupdated)
@ -622,41 +625,44 @@ void Cmd_Status(cmdctxt_t *ctx)
{ {
Cmd_Printf(ctx, "Selected server: %s\n", ctx->qtv->server); Cmd_Printf(ctx, "Selected server: %s\n", ctx->qtv->server);
if (ctx->qtv->sourcefile) if (ctx->qtv->sourcefile)
Cmd_Printf(ctx, "Playing from file\n"); Cmd_Printf(ctx, " Playing from file\n");
if (ctx->qtv->sourcesock != INVALID_SOCKET) if (ctx->qtv->sourcesock != INVALID_SOCKET)
Cmd_Printf(ctx, "Connected\n"); Cmd_Printf(ctx, " Connected\n");
if (ctx->qtv->parsingqtvheader || ctx->qtv->parsingconnectiondata) if (ctx->qtv->parsingqtvheader || ctx->qtv->parsingconnectiondata)
Cmd_Printf(ctx, "Waiting for gamestate\n"); Cmd_Printf(ctx, " Waiting for gamestate\n");
if (ctx->qtv->usequakeworldprotocols) if (ctx->qtv->usequakeworldprotocols)
{ {
Cmd_Printf(ctx, "QuakeWorld protocols\n"); Cmd_Printf(ctx, " QuakeWorld protocols\n");
if (ctx->qtv->controller) if (ctx->qtv->controller)
{ {
Cmd_Printf(ctx, "Controlled by %s\n", ctx->qtv->controller->name); Cmd_Printf(ctx, " Controlled by %s\n", ctx->qtv->controller->name);
} }
} }
else if (ctx->qtv->sourcesock == INVALID_SOCKET && !ctx->qtv->sourcefile) else if (ctx->qtv->sourcesock == INVALID_SOCKET && !ctx->qtv->sourcefile)
Cmd_Printf(ctx, "Connection not established\n"); Cmd_Printf(ctx, " Connection not established\n");
if (*ctx->qtv->modellist[1].name) if (*ctx->qtv->modellist[1].name)
{ {
Cmd_Printf(ctx, "Map name %s\n", ctx->qtv->modellist[1].name); Cmd_Printf(ctx, " Map name %s\n", ctx->qtv->modellist[1].name);
} }
if (*ctx->qtv->connectpassword) if (*ctx->qtv->connectpassword)
Cmd_Printf(ctx, "Using a password\n"); Cmd_Printf(ctx, " Using a password\n");
if (ctx->qtv->errored == ERR_DISABLED)
Cmd_Printf(ctx, " Stream is disabled\n");
if (ctx->qtv->disconnectwhennooneiswatching) if (ctx->qtv->disconnectwhennooneiswatching)
Cmd_Printf(ctx, "Stream is temporary\n"); Cmd_Printf(ctx, " Stream is temporary\n");
/* if (ctx->qtv->tcpsocket != INVALID_SOCKET) /* if (ctx->qtv->tcpsocket != INVALID_SOCKET)
{ {
Cmd_Printf(ctx, "Listening for proxies (%i)\n", ctx->qtv->tcplistenportnum); Cmd_Printf(ctx, " Listening for proxies (%i)\n", ctx->qtv->tcplistenportnum);
} }
*/ */
if (ctx->qtv->bsp) if (ctx->qtv->bsp)
{ {
Cmd_Printf(ctx, "BSP (%s) is loaded\n", ctx->qtv->mapname); Cmd_Printf(ctx, " BSP (%s) is loaded\n", ctx->qtv->mapname);
} }
} }
@ -826,11 +832,38 @@ void Cmd_Quit(cmdctxt_t *ctx)
void Cmd_Streams(cmdctxt_t *ctx) void Cmd_Streams(cmdctxt_t *ctx)
{ {
sv_t *qtv; sv_t *qtv;
char *status;
Cmd_Printf(ctx, "Streams:\n"); Cmd_Printf(ctx, "Streams:\n");
for (qtv = ctx->cluster->servers; qtv; qtv = qtv->next) for (qtv = ctx->cluster->servers; qtv; qtv = qtv->next)
{ {
Cmd_Printf(ctx, "%i: %s\n", qtv->streamid, qtv->server); switch (qtv->errored)
{
case ERR_NONE:
if (qtv->controller)
status = " (player controlled)";
else if (qtv->parsingconnectiondata)
status = " (connecting)";
else
status = "";
break;
case ERR_DISABLED:
status = " (disabled)";
break;
case ERR_DROP: //a user should never normally see this, but there is a chance
status = " (dropping)";
break;
case ERR_RECONNECT: //again, rare
status = " (reconnecting)";
break;
default: //some other kind of error, transitioning
status = " (errored)";
break;
}
Cmd_Printf(ctx, "%i: %s%s\n", qtv->streamid, qtv->server, status);
if (qtv->upstreamacceptschat)
Cmd_Printf(ctx, " (dbg) can chat!\n");
} }
} }
@ -855,6 +888,27 @@ void Cmd_Disconnect(cmdctxt_t *ctx)
Cmd_Printf(ctx, "Disconnected\n"); Cmd_Printf(ctx, "Disconnected\n");
} }
void Cmd_Halt(cmdctxt_t *ctx)
{
if (ctx->qtv->errored == ERR_DISABLED || ctx->qtv->errored == ERR_PERMANENT)
{
Cmd_Printf(ctx, "Stream is already halted\n");
}
else
{
ctx->qtv->errored = ERR_PERMANENT;
Cmd_Printf(ctx, "Stream will disconnect\n");
}
}
void Cmd_Resume(cmdctxt_t *ctx)
{
if (ctx->qtv->errored == ERR_NONE)
Cmd_Printf(ctx, "Stream is already functional\n");
ctx->qtv->errored = ERR_RECONNECT;
Cmd_Printf(ctx, "Stream will attempt to reconnect\n");
}
void Cmd_Record(cmdctxt_t *ctx) void Cmd_Record(cmdctxt_t *ctx)
{ {
char *fname = Cmd_Argv(ctx, 1); char *fname = Cmd_Argv(ctx, 1);
@ -1000,10 +1054,10 @@ void Cmd_MuteStream(cmdctxt_t *ctx)
if (*val) if (*val)
{ {
ctx->qtv->silentstream = atoi(val); ctx->qtv->silentstream = atoi(val);
Cmd_Printf(ctx, "Stream is now %smuted\n", ctx->qtv->silentstream?"un":""); Cmd_Printf(ctx, "Stream is now %smuted\n", ctx->qtv->silentstream?"":"un");
} }
else else
Cmd_Printf(ctx, "Stream is currently %smuted\n", ctx->qtv->silentstream?"un":""); Cmd_Printf(ctx, "Stream is currently %smuted\n", ctx->qtv->silentstream?"":"un");
} }
#ifdef VIEWER #ifdef VIEWER
@ -1028,71 +1082,91 @@ void Cmd_Watch(cmdctxt_t *ctx)
#endif #endif
void Cmd_Commands(cmdctxt_t *ctx)
{
Cmd_Printf(ctx, "fixme\n");
}
typedef struct rconcommands_s { typedef struct rconcommands_s {
char *name; char *name;
qboolean serverspecific; //works within a qtv context qboolean serverspecific; //works within a qtv context
qboolean clusterspecific; //works without a qtv context (ignores context) qboolean clusterspecific; //works without a qtv context (ignores context)
consolecommand_t func; consolecommand_t func;
char *description;
} rconcommands_t; } rconcommands_t;
extern const rconcommands_t rconcommands[];
void Cmd_Commands(cmdctxt_t *ctx)
{
rconcommands_t *cmd;
consolecommand_t lastfunc = NULL;
Cmd_Printf(ctx, "Commands:\n");
for (cmd = rconcommands; cmd->name; cmd = cmd++)
{
if (cmd->func == lastfunc)
continue; //no spamming alternative command names
Cmd_Printf(ctx, "%s: %s\n", cmd->name, cmd->description?cmd->description:"no description available");
lastfunc = cmd->func;
}
}
const rconcommands_t rconcommands[] = const rconcommands_t rconcommands[] =
{ {
{"exec", 1, 1, Cmd_Exec}, {"exec", 1, 1, Cmd_Exec, "executes a config file"},
{"status", 1, 1, Cmd_Status}, {"status", 1, 1, Cmd_Status, "prints proxy/stream status" },
{"say", 1, 1, Cmd_Say}, {"say", 1, 1, Cmd_Say, "says to a stream"},
{"help", 0, 1, Cmd_Help}, {"help", 0, 1, Cmd_Help, "shows the brief intro help text"},
{"commands", 0, 1, Cmd_Commands}, {"commands", 0, 1, Cmd_Commands, "prints the list of commands"},
{"hostname", 0, 1, Cmd_Hostname}, {"hostname", 0, 1, Cmd_Hostname, "changes the hostname seen in server browsers"},
{"master", 0, 1, Cmd_Master}, {"master", 0, 1, Cmd_Master, "specifies which master server to use"},
{"udpport", 0, 1, Cmd_UDPPort}, {"udpport", 0, 1, Cmd_UDPPort, "specifies to listen on a provided udp port for regular qw clients"},
{"port", 0, 1, Cmd_UDPPort}, {"port", 0, 1, Cmd_UDPPort},
{"adminpassword",0, 1, Cmd_AdminPassword}, {"adminpassword", 0, 1, Cmd_AdminPassword,"specifies the password for qtv administrators"},
{"rconpassword",0, 1, Cmd_AdminPassword}, {"rconpassword", 0, 1, Cmd_AdminPassword},
{"qtvlist", 0, 1, Cmd_QTVList}, {"qtvlist", 0, 1, Cmd_QTVList, "queries a seperate proxy for a list of available streams"},
{"qtvdemolist", 0, 1, Cmd_QTVDemoList}, {"qtvdemolist", 0, 1, Cmd_QTVDemoList, "queries a seperate proxy for a list of available demos"},
{"qtv", 0, 1, Cmd_QTVConnect}, {"qtv", 0, 1, Cmd_QTVConnect, "adds a new tcp/qtv stream"},
{"addserver", 0, 1, Cmd_QTVConnect}, {"addserver", 0, 1, Cmd_QTVConnect},
{"connect", 0, 1, Cmd_QTVConnect}, {"connect", 0, 1, Cmd_QTVConnect},
{"qw", 0, 1, Cmd_QWConnect}, {"qw", 0, 1, Cmd_QWConnect, "adds a new udp/qw stream"},
{"observe", 0, 1, Cmd_QWConnect}, {"observe", 0, 1, Cmd_QWConnect},
{"demos", 0, 1, Cmd_DemoList}, {"demos", 0, 1, Cmd_DemoList, "shows the list of demos available on this proxy"},
{"demo", 0, 1, Cmd_MVDConnect}, {"demo", 0, 1, Cmd_MVDConnect, "adds a demo as a new stream"},
{"playdemo", 0, 1, Cmd_MVDConnect}, {"playdemo", 0, 1, Cmd_MVDConnect},
{"choke", 0, 1, Cmd_Choke}, {"choke", 0, 1, Cmd_Choke, "chokes packets to the data rate in the stream, disables proxy-side interpolation"},
{"late", 0, 1, Cmd_Late}, {"late", 0, 1, Cmd_Late, "enforces a time delay on packets sent through this proxy"},
{"talking", 0, 1, Cmd_Talking}, {"talking", 0, 1, Cmd_Talking, "permits viewers to talk to each other"},
{"nobsp", 0, 1, Cmd_NoBSP}, {"nobsp", 0, 1, Cmd_NoBSP, "disables loading of bsp files"},
{"userconnects", 0, 1, Cmd_UserConnects}, {"userconnects", 0, 1, Cmd_UserConnects, "prevents users from creating thier own streams"},
{"maxviewers", 0, 1, Cmd_MaxViewers}, {"maxviewers", 0, 1, Cmd_MaxViewers, "sets a limit on udp/qw client connections"},
{"maxproxies", 0, 1, Cmd_MaxProxies}, {"maxproxies", 0, 1, Cmd_MaxProxies, "sets a limit on tcp/qtv client connections"},
{"demodir", 0, 1, Cmd_DemoDir}, {"demodir", 0, 1, Cmd_DemoDir, "specifies where to get the demo list from"},
{"basedir", 0, 1, Cmd_BaseDir}, {"basedir", 0, 1, Cmd_BaseDir, "specifies where to get any files required by the game. this is prefixed to the server-specified game dir."},
{"ping", 0, 1, Cmd_Ping}, {"ping", 0, 1, Cmd_Ping, "sends a udp ping to a qtv proxy or server"},
{"reconnect", 0, 1, Cmd_Reconnect}, {"reconnect", 0, 1, Cmd_Reconnect, "forces a stream to reconnect to its server (restarts demos)"},
{"echo", 0, 1, Cmd_Echo}, {"echo", 0, 1, Cmd_Echo, "a useless command that echos a string"},
{"quit", 0, 1, Cmd_Quit}, {"quit", 0, 1, Cmd_Quit, "closes the qtv"},
{"exit", 0, 1, Cmd_Quit}, {"exit", 0, 1, Cmd_Quit},
{"streams", 0, 1, Cmd_Streams}, {"streams", 0, 1, Cmd_Streams, "shows a list of active streams"},
{"allownq", 0, 1, Cmd_AllowNQ}, {"allownq", 0, 1, Cmd_AllowNQ, "permits nq clients to connect. This can be disabled as this code is less tested than the rest"},
{"halt", 1, 0, Cmd_Halt, "disables a stream, preventing it from reconnecting until someone tries watching it anew"},
{"disable", 1, 0, Cmd_Halt},
{"resume", 1, 0, Cmd_Resume, "reactivates a stream, allowing it to reconnect"},
{"enable", 1, 0, Cmd_Resume},
{"mute", 1, 0, Cmd_MuteStream, "hides prints that come from the game server"},
{"mutestream", 1, 0, Cmd_MuteStream}, {"mutestream", 1, 0, Cmd_MuteStream},
{"disconnect", 1, 0, Cmd_Disconnect}, {"disconnect", 1, 0, Cmd_Disconnect, "fully closes a stream"},
{"record", 1, 0, Cmd_Record}, {"record", 1, 0, Cmd_Record, "records a stream to a demo"},
{"stop", 1, 0, Cmd_Stop}, {"stop", 1, 0, Cmd_Stop, "stops recording of a demo"},
{"demospeed", 1, 0, Cmd_DemoSpeed}, {"demospeed", 1, 0, Cmd_DemoSpeed, "changes the rate the demo is played at"},
{"tcpport", 0, 1, Cmd_MVDPort}, {"tcpport", 0, 1, Cmd_MVDPort, "specifies which port to listen on for tcp/qtv connections"},
{"mvdport", 0, 1, Cmd_MVDPort}, {"mvdport", 0, 1, Cmd_MVDPort},
#ifdef VIEWER #ifdef VIEWER
{"watch", 1, 0, Cmd_Watch}, {"watch", 1, 0, Cmd_Watch, "specifies to watch that stream in the built-in viewer"},
#endif #endif
{NULL} {NULL}
@ -1137,6 +1211,7 @@ void Cmd_ExecuteNow(cmdctxt_t *ctx, char *command)
cmdname = Cmd_Argv(ctx, 0); cmdname = Cmd_Argv(ctx, 0);
//if there's only one stream, set that as the selected stream
if (!ctx->qtv && ctx->cluster->numservers==1) if (!ctx->qtv && ctx->cluster->numservers==1)
ctx->qtv = ctx->cluster->servers; ctx->qtv = ctx->cluster->servers;

View file

@ -364,7 +364,7 @@ void Net_SendQTVConnectionRequest(sv_t *qtv, char *authmethod, char *challenge)
} }
else else
{ {
qtv->drop = true; qtv->errored = ERR_PERMANENT;
qtv->upstreambuffersize = 0; qtv->upstreambuffersize = 0;
Sys_Printf(qtv->cluster, "Auth method %s was not usable\n", authmethod); Sys_Printf(qtv->cluster, "Auth method %s was not usable\n", authmethod);
return; return;
@ -424,6 +424,11 @@ qboolean Net_ConnectToTCPServer(sv_t *qtv, char *ip)
} }
} }
//make sure the buffers are empty. we could have disconnected prematurly
qtv->upstreambuffersize = 0;
qtv->buffersize = 0;
qtv->forwardpoint = 0;
//read the notes at the start of this file for what these text strings mean //read the notes at the start of this file for what these text strings mean
Net_SendQTVConnectionRequest(qtv, NULL, NULL); Net_SendQTVConnectionRequest(qtv, NULL, NULL);
return true; return true;
@ -615,7 +620,7 @@ void Net_QueueUpstream(sv_t *qtv, int size, char *buffer)
if (qtv->upstreambuffersize + size > sizeof(qtv->upstreambuffer)) if (qtv->upstreambuffersize + size > sizeof(qtv->upstreambuffer))
{ {
Sys_Printf(qtv->cluster, "Stream %i: Upstream queue overflowed for %s\n", qtv->streamid, qtv->server); Sys_Printf(qtv->cluster, "Stream %i: Upstream queue overflowed for %s\n", qtv->streamid, qtv->server);
qtv->drop = true; qtv->errored = ERR_RECONNECT;
return; return;
} }
memcpy(qtv->upstreambuffer + qtv->upstreambuffersize, buffer, size); memcpy(qtv->upstreambuffer + qtv->upstreambuffersize, buffer, size);
@ -642,7 +647,7 @@ qboolean Net_WriteUpstream(sv_t *qtv)
Sys_Printf(qtv->cluster, "Stream %i: Error: source socket error %i\n", qtv->streamid, qerrno); Sys_Printf(qtv->cluster, "Stream %i: Error: source socket error %i\n", qtv->streamid, qerrno);
else else
Sys_Printf(qtv->cluster, "Stream %i: Error: server %s disconnected\n", qtv->streamid, qtv->server); Sys_Printf(qtv->cluster, "Stream %i: Error: server %s disconnected\n", qtv->streamid, qtv->server);
qtv->drop = true; qtv->errored = ERR_RECONNECT; //if the server is down, we'll detect it on reconnect
} }
return false; return false;
} }
@ -1034,16 +1039,32 @@ qboolean QTV_Connect(sv_t *qtv, char *serverurl)
return true; return true;
} }
void QTV_Shutdown(sv_t *qtv) void QTV_Cleanup(sv_t *qtv, qboolean leaveadmins)
{ { //disconnects the stream
oproxy_t *prox;
oproxy_t *old;
viewer_t *v; viewer_t *v;
sv_t *peer;
cluster_t *cluster; cluster_t *cluster;
int i; int i;
Sys_Printf(qtv->cluster, "Stream %i: Closing source %s\n", qtv->streamid, qtv->server); oproxy_t *prox;
oproxy_t *old;
cluster = qtv->cluster;
//set connected viewers to a different stream
if (cluster->viewserver == qtv)
cluster->viewserver = NULL;
for (v = cluster->viewers; v; v = v->next)
{
#warning fixme: honour leaveadmins
if (v->server == qtv)
{ //they were watching this one
QW_SetViewersServer(qtv->cluster, v, NULL);
QW_SetMenu(v, MENU_NONE);
QTV_SayCommand(cluster, v->server, v, "menu");
QW_PrintfToViewer(v, "Stream %s is closing\n", qtv->server);
}
}
// close the source handle
if (qtv->sourcesock != INVALID_SOCKET) if (qtv->sourcesock != INVALID_SOCKET)
{ {
if (qtv->usequakeworldprotocols) if (qtv->usequakeworldprotocols)
@ -1061,17 +1082,57 @@ void QTV_Shutdown(sv_t *qtv)
fclose(qtv->sourcefile); fclose(qtv->sourcefile);
qtv->sourcefile = NULL; qtv->sourcefile = NULL;
} }
//cancel downloads
if (qtv->downloadfile) if (qtv->downloadfile)
{ {
fclose(qtv->downloadfile); fclose(qtv->downloadfile);
qtv->downloadfile = NULL; qtv->downloadfile = NULL;
unlink(qtv->downloadname); unlink(qtv->downloadname);
*qtv->downloadname = '\0';
} }
// if (qtv->tcpsocket != INVALID_SOCKET) //free the bsp
// closesocket(qtv->tcpsocket);
BSP_Free(qtv->bsp); BSP_Free(qtv->bsp);
qtv->bsp = NULL; qtv->bsp = NULL;
//clean up entity state
for (i = 0; i < ENTITY_FRAMES; i++)
{
if (qtv->frame[i].ents)
{
free(qtv->frame[i].ents);
qtv->frame[i].ents = NULL;
}
if (qtv->frame[i].entnums)
{
free(qtv->frame[i].entnums);
qtv->frame[i].entnums = NULL;
}
}
//boot connected downstream proxies
for (prox = qtv->proxies; prox; )
{
if (prox->file)
fclose(prox->file);
if (prox->sock != INVALID_SOCKET)
closesocket(prox->sock);
old = prox;
prox = prox->next;
free(old);
cluster->numproxies--;
}
}
void QTV_Shutdown(sv_t *qtv)
{
sv_t *peer;
cluster_t *cluster;
Sys_Printf(qtv->cluster, "Stream %i: Closing source %s\n", qtv->streamid, qtv->server);
QTV_Cleanup(qtv, false);
//unlink it
cluster = qtv->cluster; cluster = qtv->cluster;
if (cluster->servers == qtv) if (cluster->servers == qtv)
cluster->servers = qtv->next; cluster->servers = qtv->next;
@ -1087,41 +1148,6 @@ void QTV_Shutdown(sv_t *qtv)
} }
} }
if (cluster->viewserver == qtv)
cluster->viewserver = NULL;
for (v = cluster->viewers; v; v = v->next)
{
if (v->server == qtv)
{
QW_SetViewersServer(qtv->cluster, v, NULL);
QW_SetMenu(v, MENU_NONE);
QTV_SayCommand(cluster, v->server, v, "menu");
QW_PrintfToViewer(v, "Stream %s is closing\n", qtv->server);
}
}
for (i = 0; i < ENTITY_FRAMES; i++)
{
if (qtv->frame[i].ents)
free(qtv->frame[i].ents);
if (qtv->frame[i].entnums)
free(qtv->frame[i].entnums);
}
for (prox = qtv->proxies; prox; )
{
if (prox->file)
fclose(prox->file);
if (prox->sock != INVALID_SOCKET)
closesocket(prox->sock);
old = prox;
prox = prox->next;
free(old);
cluster->numproxies--;
}
free(qtv); free(qtv);
cluster->numservers--; cluster->numservers--;
} }
@ -1457,15 +1483,32 @@ void QTV_Run(sv_t *qtv)
if (qtv->disconnectwhennooneiswatching == 1 && qtv->numviewers == 0 && qtv->proxies == NULL) if (qtv->disconnectwhennooneiswatching == 1 && qtv->numviewers == 0 && qtv->proxies == NULL)
{ {
Sys_Printf(qtv->cluster, "Stream %i: %s became inactive\n", qtv->streamid, qtv->server); Sys_Printf(qtv->cluster, "Stream %i: %s became inactive\n", qtv->streamid, qtv->server);
qtv->drop = true; qtv->errored = ERR_DROP;
} }
if (qtv->drop) if (qtv->errored)
{ {
QTV_Shutdown(qtv); if (qtv->errored == ERR_DISABLED)
return; {
//this keeps any connected proxies ticking over.
//probably we should drop them instead - the connection will only be revived if one of them reconnects.
SV_ForwardStream(qtv, NULL, 0);
return;
}
else if (qtv->errored == ERR_PERMANENT)
{
QTV_Cleanup(qtv, false); //frees various pieces of context
qtv->errored = ERR_DISABLED;
return;
}
else if (qtv->errored == ERR_DROP)
{
QTV_Shutdown(qtv); //destroys the stream
return;
}
} }
//we will read out as many packets as we can until we're up to date //we will read out as many packets as we can until we're up to date
//note: this can cause real issues when we're overloaded for any length of time //note: this can cause real issues when we're overloaded for any length of time
//each new packet comes with a leading msec byte (msecs from last packet) //each new packet comes with a leading msec byte (msecs from last packet)
@ -1488,6 +1531,11 @@ void QTV_Run(sv_t *qtv)
} }
if (qtv->errored == ERR_RECONNECT)
{
qtv->errored = ERR_NONE;
qtv->nextconnectattempt = qtv->curtime; //make the reconnect happen _now_
}
if (qtv->sourcetype == SRC_UDP) if (qtv->sourcetype == SRC_UDP)
@ -1499,10 +1547,26 @@ void QTV_Run(sv_t *qtv)
if (!qtv->isconnected && (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - UDPRECONNECT_TIME*2)) if (!qtv->isconnected && (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - UDPRECONNECT_TIME*2))
{ {
strcpy(qtv->status, "Attemping challenge\n"); if (qtv->errored == ERR_DISABLED)
Netchan_OutOfBand(qtv->cluster, qtv->sourcesock, qtv->serveraddress, 13, "getchallenge\n"); {
strcpy(qtv->status, "Given up connecting\n");
}
else
{
strcpy(qtv->status, "Attemping challenge\n");
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
{
if (!QTV_Connect(qtv, qtv->server)) //reconnect it
qtv->errored = ERR_PERMANENT;
}
if (qtv->errored == ERR_NONE)
Netchan_OutOfBand(qtv->cluster, qtv->sourcesock, qtv->serveraddress, 13, "getchallenge\n");
}
qtv->nextconnectattempt = qtv->curtime + UDPRECONNECT_TIME; qtv->nextconnectattempt = qtv->curtime + UDPRECONNECT_TIME;
} }
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
return;
QTV_ParseQWStream(qtv); QTV_ParseQWStream(qtv);
if (qtv->isconnected) if (qtv->isconnected)
@ -1637,15 +1701,19 @@ void QTV_Run(sv_t *qtv)
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile) if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
{ {
if (qtv->errored == ERR_DISABLED)
return;
if (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - RECONNECT_TIME*2) if (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - RECONNECT_TIME*2)
{ {
if (qtv->disconnectwhennooneiswatching == 2) if (qtv->disconnectwhennooneiswatching == 2) //2 means a reverse connection
{ {
qtv->drop = true; qtv->errored = ERR_DROP;
return; return;
} }
if (!QTV_Connect(qtv, qtv->server)) if (!QTV_Connect(qtv, qtv->server)) //reconnect it
{ {
qtv->errored = ERR_PERMANENT;
return; return;
} }
} }
@ -1692,7 +1760,7 @@ void QTV_Run(sv_t *qtv)
{ {
Sys_Printf(qtv->cluster, "Stream %i: Server is not a QTV server (or is incompatible)\n", qtv->streamid); Sys_Printf(qtv->cluster, "Stream %i: Server is not a QTV server (or is incompatible)\n", qtv->streamid);
//printf("%i, %s\n", qtv->buffersize, qtv->buffer); //printf("%i, %s\n", qtv->buffersize, qtv->buffer);
qtv->drop = true; qtv->errored = ERR_PERMANENT;
return; return;
} }
if (length < 6) if (length < 6)
@ -1712,7 +1780,7 @@ void QTV_Run(sv_t *qtv)
if ((int)svversion != 1) if ((int)svversion != 1)
{ {
Sys_Printf(qtv->cluster, "Stream %i: QTV server doesn't support a compatible protocol version (returned %i)\n", qtv->streamid, atoi((char*)qtv->buffer + 6)); Sys_Printf(qtv->cluster, "Stream %i: QTV server doesn't support a compatible protocol version (returned %i)\n", qtv->streamid, atoi((char*)qtv->buffer + 6));
qtv->drop = true; qtv->errored = ERR_PERMANENT;
return; return;
} }
@ -1752,13 +1820,13 @@ void QTV_Run(sv_t *qtv)
else if (!strcmp(start, "COMPRESSION")) else if (!strcmp(start, "COMPRESSION"))
{ //we don't support compression, we didn't ask for it. { //we don't support compression, we didn't ask for it.
Sys_Printf(qtv->cluster, "Stream %i: QTV server wrongly used compression\n", qtv->streamid); Sys_Printf(qtv->cluster, "Stream %i: QTV server wrongly used compression\n", qtv->streamid);
qtv->drop = true; qtv->errored = ERR_PERMANENT;
return; return;
} }
else if (!strcmp(start, "PERROR")) else if (!strcmp(start, "PERROR"))
{ {
Sys_Printf(qtv->cluster, "\nStream %i: Server PERROR from %s: %s\n\n", qtv->streamid, qtv->server, colon); Sys_Printf(qtv->cluster, "\nStream %i: Server PERROR from %s: %s\n\n", qtv->streamid, qtv->server, colon);
qtv->drop = true; qtv->errored = ERR_PERMANENT;
qtv->buffersize = 0; qtv->buffersize = 0;
qtv->forwardpoint = 0; qtv->forwardpoint = 0;
return; return;
@ -1770,7 +1838,7 @@ void QTV_Run(sv_t *qtv)
qtv->forwardpoint = 0; qtv->forwardpoint = 0;
if (qtv->disconnectwhennooneiswatching) if (qtv->disconnectwhennooneiswatching)
qtv->drop = true; //if its a user registered stream, drop it immediatly qtv->errored = ERR_DROP; //if its a user registered stream, drop it immediatly
else else
{ //otherwise close the socket (this will result in a timeout and reconnect) { //otherwise close the socket (this will result in a timeout and reconnect)
if (qtv->sourcesock != INVALID_SOCKET) if (qtv->sourcesock != INVALID_SOCKET)
@ -1823,7 +1891,7 @@ void QTV_Run(sv_t *qtv)
if (qtv->serverquery) if (qtv->serverquery)
{ {
Sys_Printf(qtv->cluster, "End of list\n"); Sys_Printf(qtv->cluster, "End of list\n");
qtv->drop = true; qtv->errored = ERR_DROP;
qtv->buffersize = 0; qtv->buffersize = 0;
qtv->forwardpoint = 0; qtv->forwardpoint = 0;
return; return;
@ -1836,7 +1904,7 @@ void QTV_Run(sv_t *qtv)
else if (qtv->parsingqtvheader) else if (qtv->parsingqtvheader)
{ {
Sys_Printf(qtv->cluster, "Stream %i: QTV server sent no begin command - assuming incompatible\n\n", qtv->streamid); Sys_Printf(qtv->cluster, "Stream %i: QTV server sent no begin command - assuming incompatible\n\n", qtv->streamid);
qtv->drop = true; qtv->errored = ERR_PERMANENT;
qtv->buffersize = 0; qtv->buffersize = 0;
qtv->forwardpoint = 0; qtv->forwardpoint = 0;
return; return;
@ -2008,7 +2076,15 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password,
for (qtv = cluster->servers; qtv; qtv = qtv->next) for (qtv = cluster->servers; qtv; qtv = qtv->next)
{ {
if (!strcmp(qtv->server, server)) if (!strcmp(qtv->server, server))
{ //if the stream detected some permanent/config error, try reconnecting again (of course this only happens when someone tries using the stream)
#warning review this logic
if (qtv->errored == ERR_DISABLED)
{
if (!(!QTV_Connect(qtv, server) && !force)) //try and wake it up
qtv->errored = ERR_NONE;
}
return qtv; return qtv;
}
} }
} }
if (autoclose) if (autoclose)
@ -2043,6 +2119,7 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password,
{ {
if (!QTV_Connect(qtv, server) && !force) if (!QTV_Connect(qtv, server) && !force)
{ {
QTV_Cleanup(qtv, false);
free(qtv); free(qtv);
return NULL; return NULL;
} }