]> Pileus Git - grits/commitdiff
Download progress bars
authorAndy Spencer <andy753421@gmail.com>
Mon, 29 Jun 2009 02:06:17 +0000 (02:06 +0000)
committerAndy Spencer <andy753421@gmail.com>
Mon, 29 Jun 2009 02:06:17 +0000 (02:06 +0000)
TODO
src/aweather-gui.c
src/aweather-view.c
src/aweather-view.h
src/data.c
src/data.h
src/plugin-radar.c
src/plugin-radar.h
src/plugin-ridge.c

diff --git a/TODO b/TODO
index 98e571df32dff66dea0edd2b787e94b514f3b619..ee0770a3e3953d2db7517df08fb840d7b96d08ee 100644 (file)
--- 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
index 771ff20b1745d0b03209e38d3ccf1b7bdc5f050b..07fb089b69cdf58b8e35756ae64597f2b151b105 100644 (file)
@@ -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)
index 919d8f92b0ae07da941b4abe5a51d599ab386da5..fe8bafa462f42384e94ca949b29c80cd7c4991d8 100644 (file)
@@ -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;
 }
-
index 08d6444c9ce6f9b282e31a4d48a4cd6e3d316d16..7fc964b8d1f28be78da0a654db0a2990d1d91adc 100644 (file)
@@ -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);
index 0b6293ddb6b80bf90b049393967d26afa0579656..c75ef07db9bc4921392496628f61a0629336961b 100644 (file)
 #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;
 }
index dacde3bc2c877aff10268f082691d1f28c1f5def..f08f9365c335125abfa773eb29df70a1217cfbe4 100644 (file)
@@ -18,6 +18,8 @@
 #ifndef __DATA_H__
 #define __DATA_H__
 
+#include <libsoup/soup.h>
+
 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
index e5e56b68f288b7e4eae1a2ea27ad530df9b7b2fa..8d5fb4e3d58cd3f853a004a818e9b3771e3483e5 100644 (file)
@@ -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);
 }
 
index 33b9d32df44805f6e7693745e981b8e915ed39af..ca8dbea6ce514ac93064a56256d8786b70b81f47 100644 (file)
@@ -19,6 +19,7 @@
 #define __RADAR_H__
 
 #include <glib-object.h>
+#include <libsoup/soup.h>
 #include <rsl.h>
 
 /* 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;
index 8dcdac559a5da58b1570cfa99d7e046b5a79a433..75927d93ed8f72253f2858370cb4b761cc225757 100644 (file)
@@ -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);
        }