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");
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;
}
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)
{
#warning this needs looking at (controller downloads)
int size, b;
unsigned int percent;
char buffer[2048];
@ -1393,7 +1394,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (tv->downloadfile)
fclose(tv->downloadfile);
tv->downloadfile = NULL;
tv->drop = true;
tv->errored = ERR_PERMANENT;
QW_StreamPrint(tv->cluster, tv, NULL, "Map download failed\n");
return;
}
@ -1404,7 +1405,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (!tv->downloadfile)
{
Sys_Printf(tv->cluster, "Not downloading anything\n");
tv->drop = true;
tv->errored = ERR_PERMANENT;
return;
}
fwrite(buffer, 1, size, tv->downloadfile);
@ -1423,7 +1424,7 @@ void ParseDownload(sv_t *tv, netmsg_t *m)
if (!tv->bsp)
{
Sys_Printf(tv->cluster, "Failed to read BSP\n");
tv->drop = true;
tv->errored = ERR_PERMANENT;
}
else
{

View file

@ -478,6 +478,14 @@ typedef enum {
SRC_TCP
} 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)
char connectpassword[64]; //password given to server
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;
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)
{
QW_PrintfToViewer(v, "Proxy was unable to check your map version\n");
qtv->drop = true;
#warning do we still actually need to do this ourselves? Or can we just forward what the user stated?
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))
{
QW_PrintfToViewer(v, "Your map (%s) does not match the servers\n", qtv->modellist[1].name);
qtv->drop = true;
QW_PrintfToViewer(v, "QTV's map (%s) does not match the servers\n", qtv->modellist[1].name);
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->disconnectwhennooneiswatching)
viewer->server->drop = true;
viewer->server->errored = ERR_DROP;
else
viewer->server->controller = NULL;
}

View file

@ -351,7 +351,7 @@ void Cmd_Hostname(cmdctxt_t *ctx)
if (Cmd_Argc(ctx) < 2)
{
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
Cmd_Printf(ctx, "No master server is currently set.\n");
}
@ -429,7 +429,7 @@ void Cmd_AdminPassword(cmdctxt_t *ctx)
if (*ctx->cluster->adminpassword)
Cmd_Printf(ctx, "An admin password is currently set\n");
else
Cmd_Printf(ctx, "No admin passsword is currently set\n");
Cmd_Printf(ctx, "No admin password is currently set\n");
}
else
{
@ -472,6 +472,7 @@ void Cmd_QTVDemoList(cmdctxt_t *ctx)
void Cmd_GenericConnect(cmdctxt_t *ctx, char *method)
{
sv_t *sv;
char *address, *password;
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)));
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, "Source registered \"%s\"\n", address);
else
Cmd_Printf(ctx, "Source registered \"%s\" as stream %i\n", address, sv->streamid);
}
void Cmd_QTVConnect(cmdctxt_t *ctx)
@ -591,11 +593,12 @@ void Cmd_Say(cmdctxt_t *ctx)
void Cmd_Status(cmdctxt_t *ctx)
{
Cmd_Printf(ctx, "%i sources\n", ctx->cluster->numservers);
Cmd_Printf(ctx, "%i viewers\n", ctx->cluster->numviewers);
Cmd_Printf(ctx, "%i proxies\n", ctx->cluster->numproxies);
Cmd_Printf(ctx, "QTV Status:\n");
Cmd_Printf(ctx, " %i sources\n", ctx->cluster->numservers);
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);
if (ctx->cluster->chokeonnotupdated)
@ -622,41 +625,44 @@ void Cmd_Status(cmdctxt_t *ctx)
{
Cmd_Printf(ctx, "Selected server: %s\n", ctx->qtv->server);
if (ctx->qtv->sourcefile)
Cmd_Printf(ctx, "Playing from file\n");
Cmd_Printf(ctx, " Playing from file\n");
if (ctx->qtv->sourcesock != INVALID_SOCKET)
Cmd_Printf(ctx, "Connected\n");
Cmd_Printf(ctx, " Connected\n");
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)
{
Cmd_Printf(ctx, "QuakeWorld protocols\n");
Cmd_Printf(ctx, " QuakeWorld protocols\n");
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)
Cmd_Printf(ctx, "Connection not established\n");
Cmd_Printf(ctx, " Connection not established\n");
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)
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)
Cmd_Printf(ctx, "Stream is temporary\n");
Cmd_Printf(ctx, " Stream is temporary\n");
/* 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)
{
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)
{
sv_t *qtv;
char *status;
Cmd_Printf(ctx, "Streams:\n");
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");
}
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)
{
char *fname = Cmd_Argv(ctx, 1);
@ -1000,10 +1054,10 @@ void Cmd_MuteStream(cmdctxt_t *ctx)
if (*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
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
@ -1028,71 +1082,91 @@ void Cmd_Watch(cmdctxt_t *ctx)
#endif
void Cmd_Commands(cmdctxt_t *ctx)
{
Cmd_Printf(ctx, "fixme\n");
}
typedef struct rconcommands_s {
char *name;
qboolean serverspecific; //works within a qtv context
qboolean clusterspecific; //works without a qtv context (ignores context)
consolecommand_t func;
char *description;
} 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[] =
{
{"exec", 1, 1, Cmd_Exec},
{"status", 1, 1, Cmd_Status},
{"say", 1, 1, Cmd_Say},
{"exec", 1, 1, Cmd_Exec, "executes a config file"},
{"status", 1, 1, Cmd_Status, "prints proxy/stream status" },
{"say", 1, 1, Cmd_Say, "says to a stream"},
{"help", 0, 1, Cmd_Help},
{"commands", 0, 1, Cmd_Commands},
{"hostname", 0, 1, Cmd_Hostname},
{"master", 0, 1, Cmd_Master},
{"udpport", 0, 1, Cmd_UDPPort},
{"help", 0, 1, Cmd_Help, "shows the brief intro help text"},
{"commands", 0, 1, Cmd_Commands, "prints the list of commands"},
{"hostname", 0, 1, Cmd_Hostname, "changes the hostname seen in server browsers"},
{"master", 0, 1, Cmd_Master, "specifies which master server to use"},
{"udpport", 0, 1, Cmd_UDPPort, "specifies to listen on a provided udp port for regular qw clients"},
{"port", 0, 1, Cmd_UDPPort},
{"adminpassword",0, 1, Cmd_AdminPassword},
{"rconpassword",0, 1, Cmd_AdminPassword},
{"qtvlist", 0, 1, Cmd_QTVList},
{"qtvdemolist", 0, 1, Cmd_QTVDemoList},
{"qtv", 0, 1, Cmd_QTVConnect},
{"adminpassword", 0, 1, Cmd_AdminPassword,"specifies the password for qtv administrators"},
{"rconpassword", 0, 1, Cmd_AdminPassword},
{"qtvlist", 0, 1, Cmd_QTVList, "queries a seperate proxy for a list of available streams"},
{"qtvdemolist", 0, 1, Cmd_QTVDemoList, "queries a seperate proxy for a list of available demos"},
{"qtv", 0, 1, Cmd_QTVConnect, "adds a new tcp/qtv stream"},
{"addserver", 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},
{"demos", 0, 1, Cmd_DemoList},
{"demo", 0, 1, Cmd_MVDConnect},
{"demos", 0, 1, Cmd_DemoList, "shows the list of demos available on this proxy"},
{"demo", 0, 1, Cmd_MVDConnect, "adds a demo as a new stream"},
{"playdemo", 0, 1, Cmd_MVDConnect},
{"choke", 0, 1, Cmd_Choke},
{"late", 0, 1, Cmd_Late},
{"talking", 0, 1, Cmd_Talking},
{"nobsp", 0, 1, Cmd_NoBSP},
{"userconnects", 0, 1, Cmd_UserConnects},
{"maxviewers", 0, 1, Cmd_MaxViewers},
{"maxproxies", 0, 1, Cmd_MaxProxies},
{"demodir", 0, 1, Cmd_DemoDir},
{"basedir", 0, 1, Cmd_BaseDir},
{"ping", 0, 1, Cmd_Ping},
{"reconnect", 0, 1, Cmd_Reconnect},
{"echo", 0, 1, Cmd_Echo},
{"quit", 0, 1, Cmd_Quit},
{"choke", 0, 1, Cmd_Choke, "chokes packets to the data rate in the stream, disables proxy-side interpolation"},
{"late", 0, 1, Cmd_Late, "enforces a time delay on packets sent through this proxy"},
{"talking", 0, 1, Cmd_Talking, "permits viewers to talk to each other"},
{"nobsp", 0, 1, Cmd_NoBSP, "disables loading of bsp files"},
{"userconnects", 0, 1, Cmd_UserConnects, "prevents users from creating thier own streams"},
{"maxviewers", 0, 1, Cmd_MaxViewers, "sets a limit on udp/qw client connections"},
{"maxproxies", 0, 1, Cmd_MaxProxies, "sets a limit on tcp/qtv client connections"},
{"demodir", 0, 1, Cmd_DemoDir, "specifies where to get the demo list from"},
{"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, "sends a udp ping to a qtv proxy or server"},
{"reconnect", 0, 1, Cmd_Reconnect, "forces a stream to reconnect to its server (restarts demos)"},
{"echo", 0, 1, Cmd_Echo, "a useless command that echos a string"},
{"quit", 0, 1, Cmd_Quit, "closes the qtv"},
{"exit", 0, 1, Cmd_Quit},
{"streams", 0, 1, Cmd_Streams},
{"allownq", 0, 1, Cmd_AllowNQ},
{"streams", 0, 1, Cmd_Streams, "shows a list of active streams"},
{"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},
{"disconnect", 1, 0, Cmd_Disconnect},
{"record", 1, 0, Cmd_Record},
{"stop", 1, 0, Cmd_Stop},
{"demospeed", 1, 0, Cmd_DemoSpeed},
{"tcpport", 0, 1, Cmd_MVDPort},
{"disconnect", 1, 0, Cmd_Disconnect, "fully closes a stream"},
{"record", 1, 0, Cmd_Record, "records a stream to a demo"},
{"stop", 1, 0, Cmd_Stop, "stops recording of a demo"},
{"demospeed", 1, 0, Cmd_DemoSpeed, "changes the rate the demo is played at"},
{"tcpport", 0, 1, Cmd_MVDPort, "specifies which port to listen on for tcp/qtv connections"},
{"mvdport", 0, 1, Cmd_MVDPort},
#ifdef VIEWER
{"watch", 1, 0, Cmd_Watch},
{"watch", 1, 0, Cmd_Watch, "specifies to watch that stream in the built-in viewer"},
#endif
{NULL}
@ -1137,6 +1211,7 @@ void Cmd_ExecuteNow(cmdctxt_t *ctx, char *command)
cmdname = Cmd_Argv(ctx, 0);
//if there's only one stream, set that as the selected stream
if (!ctx->qtv && ctx->cluster->numservers==1)
ctx->qtv = ctx->cluster->servers;

View file

@ -364,7 +364,7 @@ void Net_SendQTVConnectionRequest(sv_t *qtv, char *authmethod, char *challenge)
}
else
{
qtv->drop = true;
qtv->errored = ERR_PERMANENT;
qtv->upstreambuffersize = 0;
Sys_Printf(qtv->cluster, "Auth method %s was not usable\n", authmethod);
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
Net_SendQTVConnectionRequest(qtv, NULL, NULL);
return true;
@ -615,7 +620,7 @@ void Net_QueueUpstream(sv_t *qtv, int size, char *buffer)
if (qtv->upstreambuffersize + size > sizeof(qtv->upstreambuffer))
{
Sys_Printf(qtv->cluster, "Stream %i: Upstream queue overflowed for %s\n", qtv->streamid, qtv->server);
qtv->drop = true;
qtv->errored = ERR_RECONNECT;
return;
}
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);
else
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;
}
@ -1034,16 +1039,32 @@ qboolean QTV_Connect(sv_t *qtv, char *serverurl)
return true;
}
void QTV_Shutdown(sv_t *qtv)
{
oproxy_t *prox;
oproxy_t *old;
void QTV_Cleanup(sv_t *qtv, qboolean leaveadmins)
{ //disconnects the stream
viewer_t *v;
sv_t *peer;
cluster_t *cluster;
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->usequakeworldprotocols)
@ -1061,17 +1082,57 @@ void QTV_Shutdown(sv_t *qtv)
fclose(qtv->sourcefile);
qtv->sourcefile = NULL;
}
//cancel downloads
if (qtv->downloadfile)
{
fclose(qtv->downloadfile);
qtv->downloadfile = NULL;
unlink(qtv->downloadname);
*qtv->downloadname = '\0';
}
// if (qtv->tcpsocket != INVALID_SOCKET)
// closesocket(qtv->tcpsocket);
//free the bsp
BSP_Free(qtv->bsp);
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;
if (cluster->servers == qtv)
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);
cluster->numservers--;
}
@ -1457,13 +1483,30 @@ void QTV_Run(sv_t *qtv)
if (qtv->disconnectwhennooneiswatching == 1 && qtv->numviewers == 0 && qtv->proxies == NULL)
{
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)
{
//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
@ -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)
@ -1498,11 +1546,27 @@ void QTV_Run(sv_t *qtv)
qtv->simtime = qtv->nextpackettime; //too old
if (!qtv->isconnected && (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - UDPRECONNECT_TIME*2))
{
if (qtv->errored == ERR_DISABLED)
{
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;
}
if (qtv->sourcesock == INVALID_SOCKET && !qtv->sourcefile)
return;
QTV_ParseQWStream(qtv);
if (qtv->isconnected)
@ -1637,15 +1701,19 @@ void QTV_Run(sv_t *qtv)
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->disconnectwhennooneiswatching == 2)
if (qtv->disconnectwhennooneiswatching == 2) //2 means a reverse connection
{
qtv->drop = true;
qtv->errored = ERR_DROP;
return;
}
if (!QTV_Connect(qtv, qtv->server))
if (!QTV_Connect(qtv, qtv->server)) //reconnect it
{
qtv->errored = ERR_PERMANENT;
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);
//printf("%i, %s\n", qtv->buffersize, qtv->buffer);
qtv->drop = true;
qtv->errored = ERR_PERMANENT;
return;
}
if (length < 6)
@ -1712,7 +1780,7 @@ void QTV_Run(sv_t *qtv)
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));
qtv->drop = true;
qtv->errored = ERR_PERMANENT;
return;
}
@ -1752,13 +1820,13 @@ void QTV_Run(sv_t *qtv)
else if (!strcmp(start, "COMPRESSION"))
{ //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);
qtv->drop = true;
qtv->errored = ERR_PERMANENT;
return;
}
else if (!strcmp(start, "PERROR"))
{
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->forwardpoint = 0;
return;
@ -1770,7 +1838,7 @@ void QTV_Run(sv_t *qtv)
qtv->forwardpoint = 0;
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
{ //otherwise close the socket (this will result in a timeout and reconnect)
if (qtv->sourcesock != INVALID_SOCKET)
@ -1823,7 +1891,7 @@ void QTV_Run(sv_t *qtv)
if (qtv->serverquery)
{
Sys_Printf(qtv->cluster, "End of list\n");
qtv->drop = true;
qtv->errored = ERR_DROP;
qtv->buffersize = 0;
qtv->forwardpoint = 0;
return;
@ -1836,7 +1904,7 @@ void QTV_Run(sv_t *qtv)
else if (qtv->parsingqtvheader)
{
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->forwardpoint = 0;
return;
@ -2008,9 +2076,17 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password,
for (qtv = cluster->servers; qtv; qtv = qtv->next)
{
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;
}
}
}
if (autoclose)
if (cluster->nouserconnects)
return NULL;
@ -2043,6 +2119,7 @@ sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password,
{
if (!QTV_Connect(qtv, server) && !force)
{
QTV_Cleanup(qtv, false);
free(qtv);
return NULL;
}