a working CheckAvailableNewGRFs that does a metadata search/retreival via HTTP for a given list of GRFs
authorTero Marttila <terom@fixme.fi>
Sat, 19 Jul 2008 01:38:52 +0300
changeset 11178 aa617a8b4f34
parent 11177 6d9a43c48924
child 11179 fa96e29d7187
a working CheckAvailableNewGRFs that does a metadata search/retreival via HTTP for a given list of GRFs
src/debug.cpp
src/debug.h
src/network/newgrf_download.cpp
src/network/newgrf_download.h
src/string.cpp
src/string_func.h
--- 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