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
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)
SIG_SITE_CHANGED,
SIG_LOCATION_CHANGED,
SIG_REFRESH,
+ SIG_OFFLINE,
NUM_SIGNALS,
};
static guint signals[NUM_SIGNALS];
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)
{
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 */
{
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);
+}
/***********
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)
*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));
_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));
g_debug("AWeatherView: get_site");
return view->site;
}
-
/* instance members */
gchar *time;
- gboolean offline;
gchar *site;
gdouble location[3];
+ gboolean offline;
};
struct _AWeatherViewClass {
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);
#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;
/*
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;
}
/*
* \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;
}
#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)
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
{
g_debug("AWeatherRadar: class_init");
/* Set defaults */
- radar->gui = NULL;
+ radar->gui = NULL;
+ radar->soup = NULL;
}
static void aweather_radar_dispose(GObject *gobject)
{
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);
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);
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;
g_error_free(error);
}
g_child_watch_add(pid, decompressed_cb, udata);
+ self->soup = NULL;
}
/*************
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;
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);
}
#define __RADAR_H__
#include <glib-object.h>
+#include <libsoup/soup.h>
#include <rsl.h>
/* TODO: convert */
/* instance members */
AWeatherGui *gui;
GtkWidget *config_body;
+ GtkWidget *progress_bar;
+ GtkWidget *progress_label;
+ SoupSession *soup;
/* Private data for loading radars */
Radar *cur_radar;
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);
}