a working CheckAvailableNewGRFs that does a metadata search/retreival via HTTP for a given list of GRFs
--- 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
--- 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 */
--- 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 <curl/curl.h>
#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<char>(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<char>(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<char>(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;
--- 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);
--- 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 */
--- 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