partial autodownload rewrite
* own source file for autodownload * use cleaned up zip functions * use new http get function (no more curl) * enforce strict file naming rules and file size limits * ping all package servers on every game start to determine ping times * support media file update lists (also kept on package servers) * allow users to manually prioritise package servers * automatically select server with newest media file revision new or changed commands: addpckserver hostname [priority] Adds a host name to the list of package servers. If a priority is given, it influences the sorting of servers. Servers with higher priority are queried first. If servers have the same priority, they are sorted by ping. Default priority is zero. getpackageserver Returns a table with four columns of all configured package servers. The columns are: host name, priority, ping and resolved. "resolved" is a flag, indicating, if the server answered the ping during game start. removed cubescript idents: sortpckservers gamestarts DOCREF addpckserver getpckserver
This commit is contained in:
+1
-301
@@ -796,8 +796,7 @@ void getmap(char *name, char *callback)
|
||||
}
|
||||
else
|
||||
{
|
||||
defformatstring(package)("packages/maps/%s.cgz", name);
|
||||
requirepackage(PCK_MAP, package);
|
||||
requirepackage(PCK_MAP, name);
|
||||
if(downloadpackages())
|
||||
{
|
||||
if(callback && *callback) execute(callback);
|
||||
@@ -883,302 +882,3 @@ COMMAND(listdemos, "");
|
||||
COMMANDN(setmr, setminutesremaining, "i");
|
||||
COMMANDN(rewind, rewinddemo, "i");
|
||||
|
||||
// packages auto - downloader
|
||||
|
||||
// arrays
|
||||
vector<pckserver *> pckservers;
|
||||
hashtable<const char *, package *> pendingpackages;
|
||||
|
||||
// cubescript
|
||||
VARP(autodownload, 0, 1, 1);
|
||||
|
||||
void resetpckservers()
|
||||
{
|
||||
pckservers.deletecontents();
|
||||
delfile(findfile("config" PATHDIVS "pcksources.cfg", "w"));
|
||||
conoutf("\f3Restart AssaultCube to take effect after resetting the list of packages source servers");
|
||||
}
|
||||
|
||||
void addpckserver(char *addr)
|
||||
{
|
||||
pckserver *srcserver = new pckserver();
|
||||
srcserver->addr = newstring(addr);
|
||||
srcserver->pending = true;
|
||||
pckservers.add(srcserver);
|
||||
}
|
||||
|
||||
COMMAND(resetpckservers, "");
|
||||
COMMAND(addpckserver, "s");
|
||||
|
||||
// cURL / Network
|
||||
|
||||
bool havecurl = false, canceldownloads = false;
|
||||
|
||||
void setupcurl()
|
||||
{
|
||||
if(curl_global_init(CURL_GLOBAL_NOTHING)) conoutf("\f3could not init cURL, content downloads not available");
|
||||
else
|
||||
{
|
||||
havecurl = true;
|
||||
execfile("config/pcksources.cfg");
|
||||
}
|
||||
}
|
||||
|
||||
// detect the "nearest" server
|
||||
int pckserversort(pckserver **a, pckserver **b)
|
||||
{
|
||||
if((*a)->ping < 0) return ((*b)->ping < 0) ? 0 : 1;
|
||||
if((*b)->ping < 0) return -1;
|
||||
|
||||
return (*a)->ping == (*b)->ping ? 0 : ((*a)->ping < (*b)->ping ? -1 : 1);
|
||||
}
|
||||
|
||||
SDL_Thread* pingpcksrvthread = NULL;
|
||||
SDL_mutex *pingpcksrvlock = NULL;
|
||||
int pingpckservers(void *data)
|
||||
{
|
||||
SDL_mutexP(pingpcksrvlock);
|
||||
vector<pckserver> serverstoping;
|
||||
loopv(pckservers) serverstoping.add(*pckservers[i]);
|
||||
SDL_mutexV(pingpcksrvlock);
|
||||
// measure the time it took to receive each's server response
|
||||
// not very accurate but it should do the trick
|
||||
loopv(serverstoping)
|
||||
{
|
||||
double ping = 0, namelookup = 0;
|
||||
pckserver *serv = &serverstoping[i];
|
||||
CURL *cu = curl_easy_init();
|
||||
curl_easy_setopt(cu, CURLOPT_URL, serv->addr);
|
||||
curl_easy_setopt(cu, CURLOPT_NOSIGNAL, 1); // Fixes crashbug for some buggy libcurl versions (Linux)
|
||||
curl_easy_setopt(cu, CURLOPT_NOPROGRESS, 1);
|
||||
curl_easy_setopt(cu, CURLOPT_NOBODY, 1); // don't download response body (as its size may vary a lot from one server to another)
|
||||
curl_easy_setopt(cu, CURLOPT_CONNECTTIMEOUT, 10); // the timeout should be large here
|
||||
int result = curl_easy_perform(cu);
|
||||
curl_easy_getinfo(cu, CURLINFO_TOTAL_TIME, &ping);
|
||||
curl_easy_getinfo(cu, CURLINFO_NAMELOOKUP_TIME, &namelookup);
|
||||
ping -= namelookup; // ignore DNS lookup time as it should be performed only once
|
||||
curl_easy_cleanup(cu);
|
||||
cu = NULL;
|
||||
if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
|
||||
serv->responsive = false;
|
||||
else
|
||||
serv->ping = (int)(ping*1000);
|
||||
}
|
||||
|
||||
SDL_mutexP(pingpcksrvlock);
|
||||
loopv(serverstoping) loopvj(pckservers) if(!strcmp(serverstoping[i].addr, pckservers[j]->addr)) *pckservers[j] = serverstoping[i];
|
||||
pckservers.sort(pckserversort);
|
||||
// print the results. we should make it silent once tested enough
|
||||
loopv(pckservers)
|
||||
{
|
||||
pckserver *serv = pckservers[i];
|
||||
if(serv->ping > 0) clientlogf("%d. %s (%d ms)", i+1, serv->addr, serv->ping);
|
||||
else clientlogf("%d. %s (did not reply)", i+1, serv->addr);
|
||||
}
|
||||
SDL_mutexV(pingpcksrvlock);
|
||||
|
||||
SDL_DestroyMutex(pingpcksrvlock);
|
||||
pingpcksrvthread = NULL;
|
||||
pingpcksrvlock = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sortpckservers()
|
||||
{
|
||||
if(pingpcksrvthread || !havecurl) return;
|
||||
pingpcksrvlock = SDL_CreateMutex();
|
||||
pingpcksrvthread = SDL_CreateThread(pingpckservers, NULL);
|
||||
}
|
||||
COMMAND(sortpckservers, "");
|
||||
|
||||
size_t write_callback(void *ptr, size_t size, size_t nmemb, FILE *stream)
|
||||
{
|
||||
return fwrite(ptr, size, nmemb, stream);
|
||||
}
|
||||
|
||||
int progress_callback_dlpackage(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
package *pck = (package *)clientp;
|
||||
loadingscreen("downloading package %d out of %d...\n%s %.0f/%.0f KB (%.1f%%)\n(ESC to cancel)", pck->number + 1, pck->number + pendingpackages.numelems,
|
||||
pck->name, dlnow/double(1024.0), dltotal/double(1024.0), dltotal == 0 ? 0 : (dlnow/dltotal * double(100.0)));
|
||||
if(interceptkey(SDLK_ESCAPE))
|
||||
{
|
||||
canceldownloads = true;
|
||||
loadingscreen();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
VAR(rereadtexturelists, 0, 1, 1); // flag to indicate additional texture files to trigger a menu rebuild (defaults to "1" to build the menus after startup)
|
||||
VAR(rereadsoundlists, 0, 1, 1); // same for map sounds
|
||||
|
||||
int processdownload(package *pck)
|
||||
{
|
||||
string tmpname = "";
|
||||
copystring(tmpname, findfile("tmp", "rb"));
|
||||
if(!pck->pending)
|
||||
{
|
||||
switch(pck->type)
|
||||
{
|
||||
case PCK_TEXTURE: case PCK_AUDIO:
|
||||
{
|
||||
const char *pckname = findfile(path(pck->name, true), "w+");
|
||||
// with textures/sounds, the image/audio file itself is sent. Just need to copy it from the temporary file
|
||||
if(rename(tmpname, pckname)) conoutf("\f3failed to install %s", pckname);
|
||||
if(pck->type == PCK_TEXTURE) rereadtexturelists = 1;
|
||||
else rereadsoundlists = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case PCK_MAP: case PCK_MAPMODEL:
|
||||
{
|
||||
// addzip("tmp", pck->name, NULL, true, pck->type);
|
||||
break;
|
||||
}
|
||||
|
||||
case PCK_SKYBOX:
|
||||
{
|
||||
char *fname = newstring(pck->name), *ls = strrchr(fname, '/');
|
||||
if(ls) *ls = '\0';
|
||||
// addzip("tmp", fname, NULL, true, pck->type);
|
||||
rereadtexturelists = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
conoutf("could not install package %s", pck->name);
|
||||
break;
|
||||
}
|
||||
delfile(tmpname);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// download a package
|
||||
double dlpackage(package *pck)
|
||||
{
|
||||
if(!pck || !pck->source) return false;
|
||||
const char *tmpname = findfile("tmp", "wb");
|
||||
FILE *outfile = fopen(tmpname, "wb");
|
||||
string req, pckname = "";
|
||||
formatstring(req)("%s/%s%s", pck->source->addr, strreplace(pckname, pck->name, " ", "%20"), (pck->type==PCK_MAP || pck->type==PCK_MAPMODEL || pck->type==PCK_SKYBOX) ? ".zip" : "");
|
||||
conoutf("downloading %s from %s ...", pck->name, pck->source->addr);
|
||||
if(!outfile)
|
||||
{
|
||||
pck->pending = false;
|
||||
conoutf("\f3failed to write temporary file \"%s\"", tmpname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result, httpresult = 0;
|
||||
double dlsize;
|
||||
pck->curl = curl_easy_init();
|
||||
curl_easy_setopt(pck->curl, CURLOPT_URL, req);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_NOSIGNAL, 1); // Fixes crashbug for some buggy libcurl versions (Linux)
|
||||
curl_easy_setopt(pck->curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_WRITEDATA, outfile);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_NOPROGRESS, 0);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_PROGRESSFUNCTION, progress_callback_dlpackage);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_PROGRESSDATA, pck);
|
||||
curl_easy_setopt(pck->curl, CURLOPT_CONNECTTIMEOUT, 10); // generous timeout for Bukz ;)
|
||||
result = curl_easy_perform(pck->curl);
|
||||
curl_easy_getinfo(pck->curl, CURLINFO_RESPONSE_CODE, &httpresult);
|
||||
curl_easy_getinfo(pck->curl, CURLINFO_SIZE_DOWNLOAD, &dlsize);
|
||||
curl_easy_cleanup(pck->curl);
|
||||
pck->curl = NULL;
|
||||
fclose(outfile);
|
||||
|
||||
pck->pending = false;
|
||||
if(result == CURLE_OPERATION_TIMEDOUT || result == CURLE_COULDNT_RESOLVE_HOST)
|
||||
{
|
||||
// mark source unresponsive
|
||||
pck->source->responsive = false;
|
||||
conoutf("\f3could not connect to %s", pck->source->addr);
|
||||
|
||||
// try to find another source
|
||||
pckserver *source = NULL;
|
||||
loopv(pckservers) if(pckservers[i]->responsive) { source = pckservers[i]; break; }
|
||||
if(!source)
|
||||
{
|
||||
conoutf("\f3no more servers to connect to");
|
||||
canceldownloads = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// update all packages
|
||||
enumerate(pendingpackages, package *, pack,
|
||||
{
|
||||
pack->source = source;
|
||||
});
|
||||
|
||||
pck->pending = true;
|
||||
|
||||
return dlpackage(pck); // retry current
|
||||
}
|
||||
if(!result && httpresult == 200) processdownload(pck);
|
||||
else if(result == CURLE_ABORTED_BY_CALLBACK) conoutf("\f3download cancelled");
|
||||
else conoutf("\f2request for %s failed (cURL %d, HTTP %d)", req, result, httpresult);
|
||||
return (!result && httpresult == 200) ? dlsize : 0;
|
||||
}
|
||||
|
||||
int lastpackagesdownload = 0;
|
||||
int downloadpackages()
|
||||
{
|
||||
if(!pendingpackages.numelems) return 0;
|
||||
else if(totalmillis - lastpackagesdownload < 2000)
|
||||
{
|
||||
conoutf("\f3quick download attempt aborted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
double total = 0;
|
||||
int downloaded = 0;
|
||||
enumerate(pendingpackages, package *, pck,
|
||||
{
|
||||
if(!canceldownloads)
|
||||
{
|
||||
if(connected) c2skeepalive(); // try to avoid time out
|
||||
pck->number = downloaded++;
|
||||
total += dlpackage(pck);
|
||||
}
|
||||
pendingpackages.remove(pck->name);
|
||||
delete pck;
|
||||
});
|
||||
canceldownloads = false;
|
||||
lastpackagesdownload = lastmillis;
|
||||
return (int)total;
|
||||
}
|
||||
|
||||
bool requirepackage(int type, const char *path)
|
||||
{
|
||||
pckserver *source = NULL;
|
||||
loopv(pckservers) if(pckservers[i]->responsive) { source = pckservers[i]; break; }
|
||||
if(!source) { conoutf("\f3no responsive source server found, can't download"); return false; }
|
||||
|
||||
char *upath = unixpath(newstring(path)), *cleanpath = newstring(path);
|
||||
filtertext(cleanpath, cleanpath, FTXT__MEDIAFILEPATH);
|
||||
if(strcmp(upath, cleanpath)) { conoutf("refuse to download \"%s\": illegal characters", path); delstring(upath); delstring(cleanpath); return false; }
|
||||
if(!havecurl || canceldownloads || type < 0 || type >= PCK_NUM || pendingpackages.access(cleanpath)) { delstring(cleanpath); return false; }
|
||||
|
||||
package *pck = new package;
|
||||
pck->name = cleanpath;
|
||||
pck->type = type;
|
||||
pck->source = source;
|
||||
pck->pending = true;
|
||||
pendingpackages.access(pck->name, pck);
|
||||
delstring(upath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void writepcksourcecfg()
|
||||
{
|
||||
if(pckservers.length())
|
||||
{
|
||||
stream *f = openfile("config" PATHDIVS "pcksources.cfg", "w");
|
||||
if(!f) return;
|
||||
f->printf("// list of package source servers (only add servers you trust!)\n\n");
|
||||
loopv(pckservers) f->printf("addpckserver %s\n", pckservers[i]->addr);
|
||||
delete f;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user