diff --git a/fteqtv/parse.c b/fteqtv/parse.c index 229c7d180..550a376cf 100644 --- a/fteqtv/parse.c +++ b/fteqtv/parse.c @@ -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 { diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index 3c79cd4c3..554c71c1a 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -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; diff --git a/fteqtv/qw.c b/fteqtv/qw.c index 88703ff11..d86b4e573 100644 --- a/fteqtv/qw.c +++ b/fteqtv/qw.c @@ -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; } diff --git a/fteqtv/rcon.c b/fteqtv/rcon.c index ce468c99a..06764eccb 100644 --- a/fteqtv/rcon.c +++ b/fteqtv/rcon.c @@ -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}, - {"addserver", 0, 1, Cmd_QTVConnect}, - {"connect", 0, 1, Cmd_QTVConnect}, - {"qw", 0, 1, Cmd_QWConnect}, - {"observe", 0, 1, Cmd_QWConnect}, - {"demos", 0, 1, Cmd_DemoList}, - {"demo", 0, 1, Cmd_MVDConnect}, - {"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}, + {"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, "adds a new udp/qw stream"}, + {"observe", 0, 1, Cmd_QWConnect}, + {"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, "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}, - {"mvdport", 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; diff --git a/fteqtv/source.c b/fteqtv/source.c index 2a66a2e22..fa8459a0f 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -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,15 +1483,32 @@ 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); - return; + 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 //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) @@ -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) @@ -1499,10 +1547,26 @@ void QTV_Run(sv_t *qtv) if (!qtv->isconnected && (qtv->curtime >= qtv->nextconnectattempt || qtv->curtime < qtv->nextconnectattempt - UDPRECONNECT_TIME*2)) { - strcpy(qtv->status, "Attemping challenge\n"); - Netchan_OutOfBand(qtv->cluster, qtv->sourcesock, qtv->serveraddress, 13, "getchallenge\n"); + 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,7 +2076,15 @@ 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) @@ -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; }