From 1ac2b7c2d9465293d168c33555a43862320b5c04 Mon Sep 17 00:00:00 2001 From: Andy Spencer Date: Mon, 29 Jun 2009 02:06:17 +0000 Subject: [PATCH] Download progress bars --- TODO | 8 ++-- src/aweather-gui.c | 16 +++++-- src/aweather-view.c | 66 ++++++++++++++++--------- src/aweather-view.h | 10 ++-- src/data.c | 114 +++++++++++++++++++++++++------------------- src/data.h | 11 ++++- src/plugin-radar.c | 53 +++++++++++++++----- src/plugin-radar.h | 4 ++ src/plugin-ridge.c | 2 +- 9 files changed, 184 insertions(+), 100 deletions(-) diff --git a/TODO b/TODO index 98e571d..ee0770a 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,15 @@ Road plan --------- -0.1 - Working - * Fix all memory leaks - * Pre-load textures and polys in OpenGL +0.1 + * Cancel partial downloads when switching radars 0.x - Misc * Configuration file * Default site * Keybindings? + * Fix all memory leaks + * Pre-load textures and polys in OpenGL + * Fetch radar list aysnc 0.x - Volume scans * Display iso surfaces of volume scans diff --git a/src/aweather-gui.c b/src/aweather-gui.c index 771ff20..07fb089 100644 --- a/src/aweather-gui.c +++ b/src/aweather-gui.c @@ -427,19 +427,25 @@ AWeatherGui *aweather_gui_new() AWeatherGui *self = g_object_new(AWEATHER_TYPE_GUI, NULL); self->view = aweather_view_new(); self->builder = gtk_builder_new(); - if (!gtk_builder_add_from_file(self->builder, DATADIR "/aweather/main.ui", &error)) g_error("Failed to create gtk builder: %s", error->message); - gtk_builder_connect_signals(self->builder, self); - g_signal_connect(self, "key-press-event", G_CALLBACK(on_gui_key_press), self); gtk_widget_reparent(aweather_gui_get_widget(self, "body"), GTK_WIDGET(self)); + /* Connect signals */ + gtk_builder_connect_signals(self->builder, self); + g_signal_connect(self, "key-press-event", + G_CALLBACK(on_gui_key_press), self); + g_signal_connect(self->view, "location-changed", + G_CALLBACK(on_location_changed), self); + g_signal_connect_swapped(self->view, "offline", + G_CALLBACK(gtk_toggle_action_set_active), + aweather_gui_get_object(self, "offline")); + /* Load components */ - aweather_view_set_location(self->view, 0, 0, -300*1000); - g_signal_connect(self->view, "location-changed", G_CALLBACK(on_location_changed), self); site_setup(self); time_setup(self); opengl_setup(self); + return self; } AWeatherView *aweather_gui_get_view(AWeatherGui *gui) diff --git a/src/aweather-view.c b/src/aweather-view.c index 919d8f9..fe8bafa 100644 --- a/src/aweather-view.c +++ b/src/aweather-view.c @@ -34,6 +34,7 @@ enum { SIG_SITE_CHANGED, SIG_LOCATION_CHANGED, SIG_REFRESH, + SIG_OFFLINE, NUM_SIGNALS, }; static guint signals[NUM_SIGNALS]; @@ -45,8 +46,11 @@ static void aweather_view_init(AWeatherView *self) g_debug("AWeatherView: init"); /* Default values */ self->time = g_strdup(""); - self->offline = FALSE; self->site = g_strdup(""); + self->location[0] = 0; + self->location[1] = 0; + self->location[2] = -300*1000; + self->offline = FALSE; } static void aweather_view_dispose(GObject *gobject) { @@ -149,7 +153,17 @@ static void aweather_view_class_init(AWeatherViewClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); - + signals[SIG_OFFLINE] = g_signal_new( + "offline", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); } /* Signal helpers */ @@ -174,6 +188,11 @@ static void _aweather_view_emit_refresh(AWeatherView *view) { g_signal_emit(view, signals[SIG_REFRESH], 0); } +static void _aweather_view_emit_offline(AWeatherView *view) +{ + g_signal_emit(view, signals[SIG_OFFLINE], 0, + view->offline); +} /*********** @@ -201,18 +220,14 @@ gchar *aweather_view_get_time(AWeatherView *view) return view->time; } -void aweather_view_set_offline(AWeatherView *view, gboolean offline) -{ - g_assert(AWEATHER_IS_VIEW(view)); - g_debug("AWeatherView: set_offline - %d", offline); - view->offline = offline; -} - -gboolean aweather_view_get_offline(AWeatherView *view) +void aweather_view_set_location(AWeatherView *view, gdouble x, gdouble y, gdouble z) { g_assert(AWEATHER_IS_VIEW(view)); - g_debug("AWeatherView: get_offline - %d", view->offline); - return view->offline; + g_debug("AWeatherView: set_location"); + view->location[0] = x; + view->location[1] = y; + view->location[2] = z; + _aweather_view_emit_location_changed(view); } void aweather_view_get_location(AWeatherView *view, gdouble *x, gdouble *y, gdouble *z) @@ -224,16 +239,6 @@ void aweather_view_get_location(AWeatherView *view, gdouble *x, gdouble *y, gdou *z = view->location[2]; } -void aweather_view_set_location(AWeatherView *view, gdouble x, gdouble y, gdouble z) -{ - g_assert(AWEATHER_IS_VIEW(view)); - g_debug("AWeatherView: set_location"); - view->location[0] = x; - view->location[1] = y; - view->location[2] = z; - _aweather_view_emit_location_changed(view); -} - void aweather_view_pan(AWeatherView *view, gdouble x, gdouble y, gdouble z) { g_assert(AWEATHER_IS_VIEW(view)); @@ -258,6 +263,22 @@ void aweather_view_refresh(AWeatherView *view) _aweather_view_emit_refresh(view); } +void aweather_view_set_offline(AWeatherView *view, gboolean offline) +{ + g_assert(AWEATHER_IS_VIEW(view)); + g_debug("AWeatherView: set_offline - %d", offline); + view->offline = offline; + _aweather_view_emit_offline(view); +} + +gboolean aweather_view_get_offline(AWeatherView *view) +{ + g_assert(AWEATHER_IS_VIEW(view)); + g_debug("AWeatherView: get_offline - %d", view->offline); + return view->offline; +} + +/* To be deprecated, use {get,set}_location */ void aweather_view_set_site(AWeatherView *view, const gchar *site) { g_assert(AWEATHER_IS_VIEW(view)); @@ -273,4 +294,3 @@ gchar *aweather_view_get_site(AWeatherView *view) g_debug("AWeatherView: get_site"); return view->site; } - diff --git a/src/aweather-view.h b/src/aweather-view.h index 08d6444..7fc964b 100644 --- a/src/aweather-view.h +++ b/src/aweather-view.h @@ -36,9 +36,9 @@ struct _AWeatherView { /* instance members */ gchar *time; - gboolean offline; gchar *site; gdouble location[3]; + gboolean offline; }; struct _AWeatherViewClass { @@ -55,16 +55,16 @@ AWeatherView *aweather_view_new(); void aweather_view_set_time(AWeatherView *view, const gchar *time); gchar *aweather_view_get_time(AWeatherView *view); -void aweather_view_set_offline(AWeatherView *view, gboolean offline); -gboolean aweather_view_get_offline(AWeatherView *view); - -void aweather_view_get_location(AWeatherView *view, gdouble *x, gdouble *y, gdouble *z); void aweather_view_set_location(AWeatherView *view, gdouble x, gdouble y, gdouble z); +void aweather_view_get_location(AWeatherView *view, gdouble *x, gdouble *y, gdouble *z); void aweather_view_pan (AWeatherView *view, gdouble x, gdouble y, gdouble z); void aweather_view_zoom (AWeatherView *view, gdouble scale); void aweather_view_refresh(AWeatherView *view); +void aweather_view_set_offline(AWeatherView *view, gboolean offline); +gboolean aweather_view_get_offline(AWeatherView *view); + /* To be deprecated, use {get,set}_location */ void aweather_view_set_site(AWeatherView *view, const gchar *site); gchar *aweather_view_get_site(AWeatherView *view); diff --git a/src/data.c b/src/data.c index 0b6293d..c75ef07 100644 --- a/src/data.c +++ b/src/data.c @@ -23,10 +23,12 @@ #include "data.h" typedef struct { - AWeatherCacheDoneCallback callback; - gpointer user_data; + gchar *uri; gchar *local; FILE *fp; + AWeatherCacheDoneCallback user_done_cb; + AWeatherCacheChunkCallback user_chunk_cb; + gpointer user_data; } cache_file_end_t; /* @@ -41,59 +43,65 @@ static FILE *fopen_p(const gchar *path, const gchar *mode) return fopen(path, mode); } -static void cache_file_cb(SoupSession *session, SoupMessage *message, gpointer _info) +static void done_cb(SoupSession *session, SoupMessage *message, gpointer _info) { cache_file_end_t *info = _info; - gchar *uri = soup_uri_to_string(soup_message_get_uri(message), FALSE); - g_debug("data: cache_file_cb"); + g_debug("data: done_cb"); - if (message->status_code == 416) { + if (message->status_code == 416) /* Range unsatisfiable, file already complete */ - info->callback(info->local, FALSE, info->user_data); - } else if (SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) { - gint wrote = fwrite(message->response_body->data, 1, - message->response_body->length, info->fp); - g_debug("data: status=%u wrote=%d/%lld", - message->status_code, - wrote, message->response_body->length); - fclose(info->fp); - info->callback(info->local, TRUE, info->user_data); - } else { - g_warning("data: cache_file_cb - error copying file, status=%d\n" + info->user_done_cb(info->local, FALSE, info->user_data); + else if (SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) + info->user_done_cb(info->local, TRUE, info->user_data); + else + g_warning("data: done_cb - error copying file, status=%d\n" "\tsrc=%s\n" "\tdst=%s", - message->status_code, uri, info->local); - } - g_free(uri); + message->status_code, info->uri, info->local); + g_free(info->uri); g_free(info->local); - g_object_unref(session); + fclose(info->fp); + g_free(info); + //g_object_unref(session); This is probably leaking } -static void do_cache(gchar *uri, gchar *local, gboolean truncate, gchar *reason, - AWeatherCacheDoneCallback callback, gpointer user_data) +void chunk_cb(SoupMessage *message, SoupBuffer *chunk, gpointer _info) { - char *name = g_path_get_basename(uri); + cache_file_end_t *info = _info; + if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) + return; + + fwrite(chunk->data, chunk->length, 1, info->fp); + goffset cur = ftell(info->fp); + //goffset total = soup_message_headers_get_range(message->response_headers); + goffset start=0, end=0, total=0; + soup_message_headers_get_content_range(message->response_headers, + &start, &end, &total); + + if (info->user_chunk_cb) + info->user_chunk_cb(info->local, cur, total, info->user_data); +} + +static SoupSession *do_cache(cache_file_end_t *info, gboolean truncate, gchar *reason) +{ + char *name = g_path_get_basename(info->uri); g_debug("data: do_cache - Caching file %s: %s", name, reason); g_free(name); - cache_file_end_t *info = g_malloc0(sizeof(cache_file_end_t)); - info->callback = callback; - info->user_data = user_data; - info->local = local; - /* TODO: move this to callback so we don't end up with 0 byte files * Then change back to check for valid file after download */ - if (truncate) info->fp = fopen_p(local, "w"); - else info->fp = fopen_p(local, "a"); + if (truncate) info->fp = fopen_p(info->local, "w"); + else info->fp = fopen_p(info->local, "a"); long bytes = ftell(info->fp); SoupSession *session = soup_session_async_new(); - SoupMessage *message = soup_message_new("GET", uri); + SoupMessage *message = soup_message_new("GET", info->uri); if (message == NULL) g_error("message is null, cannot parse uri"); - if (bytes != 0) - soup_message_headers_set_range(message->request_headers, bytes, -1); - soup_session_queue_message(session, message, cache_file_cb, info); + g_signal_connect(message, "got-chunk", G_CALLBACK(chunk_cb), info); + soup_message_headers_set_range(message->request_headers, bytes, -1); + soup_session_queue_message(session, message, done_cb, info); + return session; } /* @@ -101,27 +109,33 @@ static void do_cache(gchar *uri, gchar *local, gboolean truncate, gchar *reason, * \param path Path to the Ridge file, starting after /ridge/ * \return The local path to the cached image */ -void cache_file(char *base, char *path, AWeatherCacheType update, - AWeatherCacheDoneCallback callback, gpointer user_data) +SoupSession *cache_file(char *base, char *path, AWeatherCacheType update, + AWeatherCacheChunkCallback user_chunk_cb, + AWeatherCacheDoneCallback user_done_cb, + gpointer user_data) { - gchar *uri = g_strconcat(base, path, NULL); - gchar *local = g_build_filename(g_get_user_cache_dir(), PACKAGE, path, NULL); + + cache_file_end_t *info = g_malloc0(sizeof(cache_file_end_t)); + info->uri = g_strconcat(base, path, NULL); + info->local = g_build_filename(g_get_user_cache_dir(), PACKAGE, path, NULL); + info->fp = NULL; + info->user_chunk_cb = user_chunk_cb; + info->user_done_cb = user_done_cb; + info->user_data = user_data; if (update == AWEATHER_REFRESH) - return do_cache(uri, local, TRUE, "cache forced", - callback, user_data); + return do_cache(info, TRUE, "cache forced"); if (update == AWEATHER_UPDATE) - return do_cache(uri, local, FALSE, "attempting updating", - callback, user_data); + return do_cache(info, FALSE, "attempting updating"); - if (update == AWEATHER_ONCE && !g_file_test(local, G_FILE_TEST_EXISTS)) - return do_cache(uri, local, TRUE, "local does not exist", - callback, user_data); + if (update == AWEATHER_ONCE && !g_file_test(info->local, G_FILE_TEST_EXISTS)) + return do_cache(info, TRUE, "local does not exist"); /* No nead to cache, run the callback now and clean up */ - callback(local, FALSE, user_data); - g_free(local); - g_free(uri); - return; + user_done_cb(info->local, FALSE, user_data); + g_free(info->uri); + g_free(info->local); + g_free(info); + return NULL; } diff --git a/src/data.h b/src/data.h index dacde3b..f08f936 100644 --- a/src/data.h +++ b/src/data.h @@ -18,6 +18,8 @@ #ifndef __DATA_H__ #define __DATA_H__ +#include + typedef enum { AWEATHER_ONCE, // Cache the file if it does not exist AWEATHER_UPDATE, // Append additional data to cached copy (resume) @@ -27,7 +29,12 @@ typedef enum { typedef void (*AWeatherCacheDoneCallback)(gchar *file, gboolean updated, gpointer user_data); -void cache_file(char *base, char *path, AWeatherCacheType update, - AWeatherCacheDoneCallback callback, gpointer user_data); +typedef void (*AWeatherCacheChunkCallback)(gchar *file, goffset cur, + goffset total, gpointer user_data); + +SoupSession *cache_file(char *base, char *path, AWeatherCacheType update, + AWeatherCacheChunkCallback user_chunk_cb, + AWeatherCacheDoneCallback user_done_cb, + gpointer user_data); #endif diff --git a/src/plugin-radar.c b/src/plugin-radar.c index e5e56b6..8d5fb4e 100644 --- a/src/plugin-radar.c +++ b/src/plugin-radar.c @@ -47,7 +47,8 @@ static void aweather_radar_init(AWeatherRadar *radar) { g_debug("AWeatherRadar: class_init"); /* Set defaults */ - radar->gui = NULL; + radar->gui = NULL; + radar->soup = NULL; } static void aweather_radar_dispose(GObject *gobject) { @@ -279,7 +280,7 @@ static void update_times(AWeatherRadar *self, AWeatherView *view, char *site, ch gchar **lines = g_strsplit(data, "\n", -1); for (int i = 0; lines[i] && lines[i][0]; i++) { char **parts = g_strsplit(lines[i], " ", 2); - times = g_list_prepend(times, parts[1]); + times = g_list_prepend(times, g_strdup(parts[1])); g_strfreev(parts); } g_strfreev(lines); @@ -336,7 +337,23 @@ static void decompressed_cb(GPid pid, gint status, gpointer _udata) g_free(udata); } -static void cached_cb(char *path, gboolean updated, gpointer _self) +static void cache_chunk_cb(char *path, goffset cur, goffset total, gpointer _self) +{ + AWeatherRadar *self = AWEATHER_RADAR(_self); + double percent = (double)cur/total; + + g_message("AWeatherRadar: cache_chunk_cb - %lld/%lld = %.2f%%", + cur, total, percent*100); + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->progress_bar), MIN(percent, 1.0)); + + gchar *msg = g_strdup_printf("Loading radar... %5.1f%% (%.2f/%.2f MB)", + percent*100, (double)cur/1000000, (double)total/1000000); + gtk_label_set_text(GTK_LABEL(self->progress_label), msg); + g_free(msg); +} + +static void cache_done_cb(char *path, gboolean updated, gpointer _self) { AWeatherRadar *self = AWEATHER_RADAR(_self); char *decompressed = g_strconcat(path, ".raw", NULL); @@ -348,7 +365,7 @@ static void cached_cb(char *path, gboolean updated, gpointer _self) decompressed_t *udata = g_malloc(sizeof(decompressed_t)); udata->self = self; udata->radar_file = decompressed; - g_debug("AWeatherRadar: cached_cb - File updated, decompressing.."); + g_debug("AWeatherRadar: cache_done_cb - File updated, decompressing.."); char *argv[] = {"wsr88ddec", path, decompressed, NULL}; GPid pid; GError *error = NULL; @@ -369,6 +386,7 @@ static void cached_cb(char *path, gboolean updated, gpointer _self) g_error_free(error); } g_child_watch_add(pid, decompressed_cb, udata); + self->soup = NULL; } /************* @@ -390,13 +408,20 @@ static void on_time_changed(AWeatherView *view, const char *time, gpointer _self char *base = "http://mesonet.agron.iastate.edu/data/"; char *path = g_strdup_printf("nexrd2/raw/%s/%s_%s", site, site, time); - /* Clear out children */ + /* Set up progress bar */ GtkWidget *child = gtk_bin_get_child(GTK_BIN(self->config_body)); - if (child) - gtk_widget_destroy(child); - gtk_container_add(GTK_CONTAINER(self->config_body), - gtk_label_new("Loading radar...")); + if (child) gtk_widget_destroy(child); + + GtkWidget *vbox = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 10); + self->progress_bar = gtk_progress_bar_new(); + self->progress_label = gtk_label_new("Loading radar..."); + gtk_box_pack_start(GTK_BOX(vbox), self->progress_bar, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), self->progress_label, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(self->config_body), vbox); gtk_widget_show_all(self->config_body); + + /* Clear radar */ if (self->cur_radar) RSL_free_radar(self->cur_radar); self->cur_radar = NULL; @@ -404,10 +429,16 @@ static void on_time_changed(AWeatherView *view, const char *time, gpointer _self aweather_gui_gl_redraw(self->gui); /* Start loading the new radar */ + if (self->soup) { + soup_session_abort(self->soup); + self->soup = NULL; + } if (aweather_view_get_offline(view)) - cache_file(base, path, AWEATHER_ONCE, cached_cb, self); + self->soup = cache_file(base, path, AWEATHER_ONCE, + cache_chunk_cb, cache_done_cb, self); else - cache_file(base, path, AWEATHER_UPDATE, cached_cb, self); + self->soup = cache_file(base, path, AWEATHER_UPDATE, + cache_chunk_cb, cache_done_cb, self); g_free(path); } diff --git a/src/plugin-radar.h b/src/plugin-radar.h index 33b9d32..ca8dbea 100644 --- a/src/plugin-radar.h +++ b/src/plugin-radar.h @@ -19,6 +19,7 @@ #define __RADAR_H__ #include +#include #include /* TODO: convert */ @@ -44,6 +45,9 @@ struct _AWeatherRadar { /* instance members */ AWeatherGui *gui; GtkWidget *config_body; + GtkWidget *progress_bar; + GtkWidget *progress_label; + SoupSession *soup; /* Private data for loading radars */ Radar *cur_radar; diff --git a/src/plugin-ridge.c b/src/plugin-ridge.c index 8dcdac5..75927d9 100644 --- a/src/plugin-ridge.c +++ b/src/plugin-ridge.c @@ -177,7 +177,7 @@ static void on_site_changed(AWeatherView *view, gchar *site, AWeatherRidge *self cached_t *udata = g_malloc(sizeof(cached_t)); udata->self = self; udata->layer = &layers[i]; - cache_file(base, path, AWEATHER_ONCE, cached_cb, udata); + cache_file(base, path, AWEATHER_ONCE, NULL, cached_cb, udata); //cache_file(base, path, AWEATHER_UPDATE, cached_cb, udata); g_free(path); } -- 2.43.2