From 2e309b46e8d121fbc3d74701b19f844a25d99128 Mon Sep 17 00:00:00 2001 From: Matthew Bowra-Dean Date: Fri, 3 Dec 2010 05:21:24 +1300 Subject: [PATCH] Downloading works. Still to come: cancelling downloads and extraction. --- OpenRA.Launcher.Gtk/bridge.c | 297 +++++++++++++++++++++++++++++++++- OpenRA.Launcher.Gtk/utility.c | 59 +++++-- OpenRA.Launcher.Gtk/utility.h | 1 + 3 files changed, 345 insertions(+), 12 deletions(-) diff --git a/OpenRA.Launcher.Gtk/bridge.c b/OpenRA.Launcher.Gtk/bridge.c index fc62b877e8..57df575faa 100644 --- a/OpenRA.Launcher.Gtk/bridge.c +++ b/OpenRA.Launcher.Gtk/bridge.c @@ -14,6 +14,7 @@ #include #include "main.h" +#include "utility.h" #define JS_STR(str) JSStringCreateWithUTF8CString(str) #define JS_FUNC(ctx, callback) JSObjectMakeFunctionWithCallback(ctx, NULL, \ @@ -101,6 +102,8 @@ JSValueRef js_exists_in_mod(JSContextRef ctx, JSObjectRef func, return_value = JSValueMakeNumber(ctx, 1); } + g_message("JS ExistsInMod: Not found"); + return return_value; } @@ -175,6 +178,287 @@ JSValueRef js_launch_mod(JSContextRef ctx, JSObjectRef func, return return_value; } +typedef struct download_t +{ + char key[32]; + char url[128]; + char dest[128]; + int current_bytes; + int total_bytes; + JSContextGroupRef ctx_group; + JSObjectRef download_progressed_func; + JSValueRef status; + JSValueRef error; + GIOChannel * output_channel; + GPid pid; +} download_t; + +#define MAX_DOWNLOADS 16 + +static download_t downloads[MAX_DOWNLOADS]; +static int num_downloads = 0; + +download_t * find_download(char const * key) +{ + int i; + for (i = 0; i < num_downloads; i++) + { + if (0 == strcmp(downloads[i].key, key)) + return downloads + i; + } + return NULL; +} + +JSValueRef js_register_download(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key, * url, * filename; + size_t key_size, url_size, filename_size; + download_t * download; + JSValueRef o; + if (!js_check_num_args(ctx, "registerDownload", argc, 3, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + g_message("JS RegisterDownload: Registering %s", key); + + if (NULL == (download = find_download(key))) + { + download = downloads + num_downloads++; + if (num_downloads >= MAX_DOWNLOADS) + { + num_downloads = MAX_DOWNLOADS - 1; + return JSValueMakeNull(ctx); + } + } + + memset(download, 0, sizeof(download_t)); + + download->ctx_group = JSContextGetGroup(ctx); + o = JSObjectGetProperty(ctx, JSContextGetGlobalObject(ctx), JS_STR("downloadProgressed"), NULL); + download->download_progressed_func = JSValueToObject(ctx, o, NULL); + + strncpy(download->key, key, 31); + download->key[31] = '\0'; + + free(key); + + url = js_get_cstr_from_val(ctx, argv[1], &url_size); + strncpy(download->url, url, 127); + download->url[127] = '\0'; + free(url); + + filename = js_get_cstr_from_val(ctx, argv[2], &filename_size); + strncpy(download->dest, filename, 127); + download->dest[127] = '\0'; + free(filename); + + download->status = JSValueMakeString(ctx, JS_STR("AVAILABLE")); + + return JSValueMakeNull(ctx); +} + +gboolean update_download_stats(GIOChannel * source, GIOCondition condition, gpointer data) +{ + download_t * download = (download_t *)data; + gchar * line; + gsize line_length; + GIOStatus io_status; + JSValueRef args[1]; + JSContextRef ctx; + + ctx = JSGlobalContextCreateInGroup(download->ctx_group, NULL); + + switch(condition) + { + case G_IO_IN: + io_status = g_io_channel_read_line(source, &line, &line_length, NULL, NULL); + if (G_IO_STATUS_NORMAL == io_status) + { + if (0 == memcmp(line, "Error:", 6)) + { + download->status = JSValueMakeString(ctx, JS_STR("ERROR")); + download->error = JSValueMakeString(ctx, JS_STR(line + 7)); + } + else + { + download->status = JSValueMakeString(ctx, JS_STR("DOWNLOADING")); + GRegex * pattern = g_regex_new("(\\d{1,3})% (\\d+)/(\\d+) bytes", 0, 0, NULL); + GMatchInfo * match; + if (g_regex_match(pattern, line, 0, &match)) + { + gchar * current = g_match_info_fetch(match, 2), * total = g_match_info_fetch(match, 3); + download->current_bytes = atoi(current); + download->total_bytes = atoi(total); + g_free(current); + g_free(total); + } + g_free(match); + } + } + g_free(line); + break; + case G_IO_HUP: + download->status = JSValueMakeString(ctx, JS_STR("DOWNLOADED")); + g_io_channel_shutdown(source, FALSE, NULL); + break; + default: + break; + } + + args[0] = JSValueMakeString(ctx, JS_STR(download->key)); + JSObjectCallAsFunction(ctx, download->download_progressed_func, NULL, 1, args, NULL); + + JSGlobalContextRelease((JSGlobalContextRef)ctx); + return TRUE; +} + +JSValueRef js_start_download(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key; + size_t key_size; + download_t * download; + int fd; + GPid pid; + + if (!js_check_num_args(ctx, "startDownload", argc, 1, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + if (NULL == (download = find_download(key))) + { + free(key); + return JSValueMakeBoolean(ctx, 0); + } + + free(key); + + g_message("Starting download %s", download->key); + + download->status = JSValueMakeString(ctx, JS_STR("DOWNLOADING")); + + fd = util_do_download(download->url, download->dest, &pid); + + if (!fd) + return JSValueMakeBoolean(ctx, 0); + + download->pid = pid; + download->output_channel = g_io_channel_unix_new(fd); + + g_io_add_watch(download->output_channel, G_IO_IN | G_IO_HUP, update_download_stats, download); + + return JSValueMakeBoolean(ctx, 1); +} + +JSValueRef js_cancel_download(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + return JSValueMakeNull(ctx); +} + +JSValueRef js_download_status(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key; + size_t key_size; + download_t * download; + + if (!js_check_num_args(ctx, "downloadStatus", argc, 1, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + if (NULL == (download = find_download(key))) + { + free(key); + return JSValueMakeString(ctx, JS_STR("NOT_REGISTERED")); + } + + free(key); + + return download->status; +} + +JSValueRef js_download_error(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key; + size_t key_size; + download_t * download; + + if (!js_check_num_args(ctx, "downloadError", argc, 1, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + g_message("JS DownloadError: Retrieving error message for %s", key); + + if (NULL == (download = find_download(key))) + { + free(key); + return JSValueMakeString(ctx, JS_STR("")); + } + + free(key); + + return download->error; +} + +JSValueRef js_bytes_completed(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key; + size_t key_size; + download_t * download; + + if (!js_check_num_args(ctx, "bytesCompleted", argc, 1, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + if (NULL == (download = find_download(key))) + { + free(key); + return JSValueMakeNumber(ctx, 0); + } + + free(key); + + return JSValueMakeNumber(ctx, download->current_bytes); +} + +JSValueRef js_bytes_total(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + char * key; + size_t key_size; + download_t * download; + + if (!js_check_num_args(ctx, "bytesTotal", argc, 1, exception)) + return JSValueMakeNull(ctx); + + key = js_get_cstr_from_val(ctx, argv[0], &key_size); + + if (NULL == (download = find_download(key))) + { + free(key); + return JSValueMakeNumber(ctx, 0); + } + + free(key); + + return JSValueMakeNumber(ctx, download->total_bytes); +} + +JSValueRef js_extract_download(JSContextRef ctx, JSObjectRef func, JSObjectRef this, + size_t argc, const JSValueRef argv[], JSValueRef * exception) +{ + return JSValueMakeNull(ctx); +} + void js_add_functions(JSGlobalContextRef ctx, JSObjectRef target, char ** names, JSObjectCallAsFunctionCallback * callbacks, size_t count) { @@ -193,9 +477,16 @@ void bind_js_bridge(WebKitWebView * view, WebKitWebFrame * frame, JSGlobalContextRef js_ctx; JSObjectRef window_obj, external_obj; - int func_count = 3; - char * names[] = { "log", "existsInMod", "launchMod" }; - JSObjectCallAsFunctionCallback callbacks[] = { js_log, js_exists_in_mod, js_launch_mod }; + int func_count = 11; + char * names[] = { "log", "existsInMod", "launchMod", "registerDownload", + "startDownload", "cancelDownload", "downloadStatus", + "downloadError", "bytesCompleted", "bytesTotal", + "extractDownload"}; + JSObjectCallAsFunctionCallback callbacks[] = { js_log, js_exists_in_mod, js_launch_mod, + js_register_download, js_start_download, + js_cancel_download, js_download_status, + js_download_error, js_bytes_completed, + js_bytes_total, js_extract_download }; js_ctx = (JSGlobalContextRef)context; diff --git a/OpenRA.Launcher.Gtk/utility.c b/OpenRA.Launcher.Gtk/utility.c index 120523ffb3..c80af0b3d9 100644 --- a/OpenRA.Launcher.Gtk/utility.c +++ b/OpenRA.Launcher.Gtk/utility.c @@ -12,14 +12,16 @@ #include #include -int util_get_mod_list (GChildWatchFunc callback) +int util_do_command_async(char * command, GChildWatchFunc callback) { GPid child_pid; gint * out_fd = (gint *)malloc(sizeof(gint)); - char * spawn_args[] = { "mono", "OpenRA.Utility.exe", "-l", NULL }; - gboolean result = g_spawn_async_with_pipes(NULL, spawn_args, NULL, - G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &child_pid, NULL, out_fd, NULL, NULL); + char * spawn_args[] = { "mono", "OpenRA.Utility.exe", command, NULL }; + gboolean result; + + result = g_spawn_async_with_pipes(NULL, spawn_args, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &child_pid, NULL, out_fd, NULL, NULL); if (!result) { @@ -31,6 +33,11 @@ int util_get_mod_list (GChildWatchFunc callback) return TRUE; } +int util_get_mod_list (GChildWatchFunc callback) +{ + return util_do_command_async("-l", callback); +} + int util_do_command_blocking(char * command, GChildWatchFunc callback) { GPid child_pid; @@ -58,16 +65,50 @@ int util_do_command_blocking(char * command, GChildWatchFunc callback) int util_get_mod_metadata(char const * mod, GChildWatchFunc callback) { - char util_args[32]; + char * util_args; + int return_val; + + util_args = (char *)malloc(strlen(mod) + strlen("-i=") + 1); sprintf(util_args, "-i=%s", mod); - return util_do_command_blocking(util_args, callback); + return_val = util_do_command_blocking(util_args, callback); + free(util_args); + return return_val; } int util_get_setting(const char * setting, GChildWatchFunc callback) { - char command[64]; + char * command; + int return_val; + + command = (char *)malloc(strlen(setting) + strlen("--settings-value=~/.openra,") + 1); sprintf(command, "--settings-value=~/.openra,%s", setting); - return util_do_command_blocking(command, callback); + return_val = util_do_command_blocking(command, callback); + free(command); + return return_val; +} + +int util_do_download(const char * url, const char * dest, GPid * pid) +{ + char * command; + int out_fd; + gboolean result; + + char * launch_args[] = { "mono", "OpenRA.Utility.exe", NULL, NULL }; + + command = (char *)malloc(strlen(url) + strlen(dest) + strlen("--download-url=") + 2); + sprintf(command, "--download-url=%s,%s", url, dest); + + launch_args[2] = command; + + result = g_spawn_async_with_pipes(NULL, launch_args, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, pid, NULL, &out_fd, NULL, NULL); + free(command); + + if (!result) + { + return 0; + } + return out_fd; } char * util_get_output(int fd, int * output_len) diff --git a/OpenRA.Launcher.Gtk/utility.h b/OpenRA.Launcher.Gtk/utility.h index 675e6d760a..689b81e198 100644 --- a/OpenRA.Launcher.Gtk/utility.h +++ b/OpenRA.Launcher.Gtk/utility.h @@ -9,4 +9,5 @@ int util_get_mod_list (GChildWatchFunc); int util_get_mod_metadata(char const *, GChildWatchFunc); int util_get_setting(const char *, GChildWatchFunc); +int util_do_download(const char *, const char *, GPid *); char * util_get_output(int, int *);