# HG changeset patch # User Tero Marttila # Date 1216420732 -10800 # Node ID aa617a8b4f34d81fc7c3753cecf8c0db401b7fa1 # Parent 6d9a43c48924c02771646732a71207ce8b53ee0c a working CheckAvailableNewGRFs that does a metadata search/retreival via HTTP for a given list of GRFs diff -r 6d9a43c48924 -r aa617a8b4f34 src/debug.cpp --- a/src/debug.cpp Fri Jul 18 22:41:08 2008 +0300 +++ b/src/debug.cpp Sat Jul 19 01:38:52 2008 +0300 @@ -31,6 +31,7 @@ int _debug_sl_level; int _debug_station_level; int _debug_gamelog_level; +int _debug_grfdl_level; struct DebugLevel { @@ -56,6 +57,7 @@ DEBUG_LEVEL(sl), DEBUG_LEVEL(station), DEBUG_LEVEL(gamelog), + DEBUG_LEVEL(grfdl), }; #undef DEBUG_LEVEL diff -r 6d9a43c48924 -r aa617a8b4f34 src/debug.h --- a/src/debug.h Fri Jul 18 22:41:08 2008 +0300 +++ b/src/debug.h Sat Jul 19 01:38:52 2008 +0300 @@ -48,6 +48,7 @@ extern int _debug_sl_level; extern int _debug_station_level; extern int _debug_gamelog_level; + extern int _debug_grfdl_level; void CDECL debug(const char *dbg, ...); #endif /* NO_DEBUG_MESSAGES */ diff -r 6d9a43c48924 -r aa617a8b4f34 src/network/newgrf_download.cpp --- a/src/network/newgrf_download.cpp Fri Jul 18 22:41:08 2008 +0300 +++ b/src/network/newgrf_download.cpp Sat Jul 19 01:38:52 2008 +0300 @@ -11,17 +11,247 @@ #include "../variables.h" #include "../newgrf.h" #include "newgrf_download.h" +#include "core/config.h" #include "../functions.h" +#include "../debug.h" #include "../window_func.h" #include "../core/alloc_func.hpp" #include "../string_func.h" #include "../gfx_func.h" #include "../querystring_gui.h" #include "../sortlist_type.h" +#include "../rev.h" + +// XXX: ... +#include #include "table/strings.h" #include "../table/sprites.h" +static int _has_curl_init = 0; + +static CURLcode do_init_curl () { + CURLcode err; + + if (!_has_curl_init) { + // XXX: be smarter about what to initialize + if ((err = curl_global_init(CURL_GLOBAL_ALL))) + return err; + + _has_curl_init = 1; + } + + return CURLE_OK; +} + +struct check_write_ctx { + char *buf; + size_t len; + size_t offset; +}; + +#define QUERY_ITEM_SIZE ( \ + 4 /* the "grf=" prefix */ \ + + 8 /* the GRFID, in hex */ \ + + 1 /* the ":" */ \ + + 32 /* the md5sum, in hex */ \ + + 1 /* the "&" or '\0' */ \ + ) + +static size_t check_available_write_cb (void *ptr, size_t size, size_t nmemb, void *arg) { + struct check_write_ctx *ctx = (struct check_write_ctx *) arg; + + // total number of bytes + size_t len = size * nmemb; + + // realloc if needed + if (ctx->offset + len + 1 > ctx->len) { + ctx->len *= 2; + ctx->buf = ReallocT(ctx->buf, ctx->len); + } + + // thankfully we only have a limited amount of data.. + memcpy(ctx->buf + ctx->offset, ptr, len); + ctx->offset += len; + *(ctx->buf + ctx->offset) = '\0'; + + return len; +} + +/** Ask the master database about the availablity of the given NewGRFs, and update + * the GRFConfigs that are avilable to GCS_AVAILABLE + * @param config pointer to a linked-list of grfconfig's needed */ +static void CheckAvailableNewGRFs(GRFConfig *list) +{ + GRFConfig *c; + int i; + char md5buf[64]; + struct check_write_ctx write_ctx; + CURL *curl; + char api_url[512], useragent[512]; + char curl_errbuf[CURL_ERROR_SIZE]; + long http_code; + char *buf_ptr; + CURLcode err; + + // initialize to a safe state for error handling + curl = write_ctx.buf = NULL; + + // count how many NewGRFs in our list + for (c = list, i = 0; c != NULL; c = c->next, i++) {} + + // how many bytes these take up... + int query_size = i * QUERY_ITEM_SIZE; + + // allocate the buffer for the query data + char *query_buf = MallocT(query_size), *query_ptr = query_buf; + + // build the query + for (c = list; c != NULL; c = c->next) { + + // the "&", except not for the first entry + if (c != list) + *(query_ptr++) = '&'; + + // format the md5sum + md5sumToString(md5buf, lastof(md5buf), c->md5sum); + + // snprintf the entry into the query buffer + assert(snprintf(query_ptr, QUERY_ITEM_SIZE, "grf=%08X:%32s", BSWAP32(c->grfid), md5buf) == QUERY_ITEM_SIZE -1); + + // advance the query_ptr to point at the nul byte + query_ptr += QUERY_ITEM_SIZE - 1; + } + + // buil the target URL by formatting NETWORK_NEWGRF_MASTER_API with the method as the only parameter + assert(snprintf(api_url, sizeof(api_url), NETWORK_NEWGRF_MASTER_API, "search") < 512); + + // our useragent + assert(snprintf(useragent, sizeof(useragent), NETWORK_HTTP_USER_AGENT, _openttd_revision) < 512); + + // receiving is so damn hard... + write_ctx.buf = MallocT(512); + write_ctx.len = 512; + write_ctx.offset = 0; + + // set up curl globally if needed + if ((err = do_init_curl())) { + DEBUG(grfdl, 0, "curl_global_init failed: %s", curl_easy_strerror(err)); + goto error; + } + + // allocate a new curl_easy + if ((curl = curl_easy_init()) == NULL) { + DEBUG(grfdl, 0, "curl_easy_init failed"); + goto error; + } + + // let's set some options... + if ( + (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1))) // XXX: tie this in with OpenTTD's debug stuff + /* + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, XXX) + */ + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1))) // no progress meter on stdout, thx + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf))) // human-readable error messages + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_URL, api_url))) // the URL to retrieve + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent))) // the User-agent to use + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, query_buf))) // the POST data + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &check_available_write_cb))) + || (CURLE_OK != (err = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_ctx))) // the write buffer + ) { + DEBUG(grfdl, 0, "curl_easy_setopt failed: %s", curl_easy_strerror(err)); + goto error; + } + + // XXX: currently this freezes the UI.. + if ((err = curl_easy_perform(curl))) { + DEBUG(grfdl, 0, "the curl request failed: %s", curl_easy_strerror(err)); + goto error; + } + + // get the HTTP return code + if ((err = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code))) { + DEBUG(grfdl, 0, "curl_easy_getinfo failed: %s", curl_easy_strerror(err)); + goto error; + } + + if (http_code != 200) { + DEBUG(grfdl, 0, "Bad HTTP response code: %d", http_code); + goto error; + } + + // start parsing... + buf_ptr = write_ctx.buf; + + while (buf_ptr < write_ctx.buf + write_ctx.offset) { + // replace the \n with a \0 + char *nl = strchr(buf_ptr, '\n'); + + if (!nl) { + DEBUG(grfdl, 0, "Incomplete line in search response: %s", buf_ptr); + goto error; + } + + *nl = '\0'; + + // check the format + if (buf_ptr[8] != ':' || buf_ptr[8 + 1 + 32] != ':') { + DEBUG(grfdl, 0, "Invalid line in search response: %s", buf_ptr); + goto error; + } + + // separate out the fields + buf_ptr[8] = buf_ptr[8 + 1 + 32] = '\0'; + + // parse the GRFID... + char *invalid; + uint32 grfid = strtol(buf_ptr + 0, &invalid, 16); + + if (*invalid) { + DEBUG(grfdl, 0, "Invalid GRFID in search response: %s", buf_ptr); + goto error; + } + + // parse the md5sum + uint8 md5sum[16]; + + if (StringToMd5sum(md5sum, buf_ptr + 8 + 1)) { + DEBUG(grfdl, 0, "Invalid md5sum in search response: %s", buf_ptr); + goto error; + } + + // copy the URL + char *url = strdup(buf_ptr + 8 + 1 + 32 + 1); + + // look for the GRF + for (c = list; c != NULL; c = c->next) { + if (BSWAP32(c->grfid) == grfid && memcmp(c->md5sum, md5sum, 16) == 0) { + DEBUG(grfdl, 0, "Found %08X:%s at %s", buf_ptr + 0, buf_ptr + 8 + 1, url); + + c->filename = url; + c->status = GCS_AVAILABLE; + break; + } + } + + // free the allocated memory if it wasn't stored in a GRFConfig + if (c == NULL) { + free(url); + } + + // move on to the next line + buf_ptr = nl + 1; + } + +error : + // cleanup + free(write_ctx.buf); + + if (curl) + curl_easy_cleanup(curl); +} + // XXX: copy-pasted from newgrf_gui.cpp static void ShowDownloadNewGRFInfo(const GRFConfig *c, uint x, uint y, uint w, uint bottom, bool show_params) { @@ -245,7 +475,9 @@ { switch (widget) { case DNGRFS_CHECK_AVAILABLE: - // XXX: implement + CheckAvailableNewGRFs(this->list); + + this->SetDirty(); break; diff -r 6d9a43c48924 -r aa617a8b4f34 src/network/newgrf_download.h --- a/src/network/newgrf_download.h Fri Jul 18 22:41:08 2008 +0300 +++ b/src/network/newgrf_download.h Sat Jul 19 01:38:52 2008 +0300 @@ -7,6 +7,12 @@ #include "../newgrf_config.h" +// URL to the Master NewGRF database API +#define NETWORK_NEWGRF_MASTER_API "http://localhost:8000/openttd-api/%s" + +// our HTTP User-Agent :) +#define NETWORK_HTTP_USER_AGENT "OpenTTD (%s)" + /* In newgrf_download.cpp */ void ShowNewGRFDownload(GRFConfig **config); diff -r 6d9a43c48924 -r aa617a8b4f34 src/string.cpp --- a/src/string.cpp Fri Jul 18 22:41:08 2008 +0300 +++ b/src/string.cpp Sat Jul 19 01:38:52 2008 +0300 @@ -203,6 +203,28 @@ return p; } +/** Convert the hexadecimal string representation to a binary md5sum + * @param md5sum where to put the md5sum into + * @param buf buffer containing a string of 32 hex chars + * @return 0 on success, -1 on failure */ +int StringToMd5sum(uint8 md5sum[16], const char *buf) +{ + char hex[3] = { 0, 0, 0}; + char *invalid; + + for (uint i = 0; i < 16; i++) { + hex[0] = buf[i * 2 + 0]; + hex[1] = buf[i * 2 + 1]; + + md5sum[i] = strtol(hex, &invalid, 16); + + if (*invalid) + return -1; + } + + return 0; +} + /* UTF-8 handling routines */ diff -r 6d9a43c48924 -r aa617a8b4f34 src/string_func.h --- a/src/string_func.h Fri Jul 18 22:41:08 2008 +0300 +++ b/src/string_func.h Sat Jul 19 01:38:52 2008 +0300 @@ -54,6 +54,8 @@ /** Convert the md5sum number to a 'hexadecimal' string, return next pos in buffer */ char *md5sumToString(char *buf, const char *last, const uint8 md5sum[16]); +/** Convert the hexademical string to a binary md5sum, return 0 on success, nonzero on error */ +int StringToMd5sum(uint8 md5sum[16], const char *buf); /** * Only allow certain keys. You can define the filter to be used. This makes