Verified Commit 7a490bfc authored by James Westman's avatar James Westman
Browse files

map-source: Make the fill_tile vfunc async

Previously, fill_tile_async called the original fill_tile virtual
method, which did not call a callback when it was finished. The async
callback was implemented by connecting to the tile's notify::state
signal.

Now, the virtual method itself is async, not just the wrapper function.
This required some changes to the functions, most notably in
ShumateNetworkTileSource.
parent 957a5661
......@@ -60,11 +60,14 @@ texture_new_for_surface (cairo_surface_t *surface)
static void
shumate_test_tile_source_fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable)
shumate_test_tile_source_fill_tile_async (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GdkTexture) texture = NULL;
g_autoptr(GTask) task = NULL;
guint tile_size = shumate_tile_get_size (tile);
int x = shumate_tile_get_x (tile), y = shumate_tile_get_y (tile);
int zoom = shumate_tile_get_zoom_level (tile);
......@@ -76,6 +79,7 @@ shumate_test_tile_source_fill_tile (ShumateMapSource *map_source,
g_return_if_fail (SHUMATE_IS_TEST_TILE_SOURCE (map_source));
g_return_if_fail (SHUMATE_IS_TILE (tile));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, tile_size, tile_size);
cr = cairo_create (surface);
......@@ -97,6 +101,10 @@ shumate_test_tile_source_fill_tile (ShumateMapSource *map_source,
shumate_tile_set_texture (tile, texture);
shumate_tile_set_fade_in (tile, TRUE);
shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
task = g_task_new (map_source, cancellable, callback, user_data);
g_task_set_source_tag (task, shumate_test_tile_source_fill_tile_async);
g_task_return_boolean (task, TRUE);
}
......@@ -105,7 +113,7 @@ shumate_test_tile_source_class_init (ShumateTestTileSourceClass *klass)
{
ShumateMapSourceClass *map_source_class = SHUMATE_MAP_SOURCE_CLASS (klass);
map_source_class->fill_tile = shumate_test_tile_source_fill_tile;
map_source_class->fill_tile_async = shumate_test_tile_source_fill_tile_async;
}
......
......@@ -27,97 +27,25 @@
#include "shumate-debug.h"
#include "shumate-enum-types.h"
#include "shumate-tile.h"
static GdkTexture *
texture_new_for_surface (cairo_surface_t *surface)
{
g_autoptr(GBytes) bytes = NULL;
GdkTexture *texture;
g_return_val_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE, NULL);
g_return_val_if_fail (cairo_image_surface_get_width (surface) > 0, NULL);
g_return_val_if_fail (cairo_image_surface_get_height (surface) > 0, NULL);
bytes = g_bytes_new_with_free_func (cairo_image_surface_get_data (surface),
cairo_image_surface_get_height (surface)
* cairo_image_surface_get_stride (surface),
(GDestroyNotify) cairo_surface_destroy,
cairo_surface_reference (surface));
texture = gdk_memory_texture_new (cairo_image_surface_get_width (surface),
cairo_image_surface_get_height (surface),
GDK_MEMORY_B8G8R8A8_PREMULTIPLIED,
bytes,
cairo_image_surface_get_stride (surface));
return texture;
}
#include "shumate-network-tile-source.h"
G_DEFINE_TYPE (ShumateErrorTileSource, shumate_error_tile_source, SHUMATE_TYPE_TILE_SOURCE)
static void
fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable)
fill_tile_async (ShumateMapSource *self,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (SHUMATE_IS_ERROR_TILE_SOURCE (map_source));
g_return_if_fail (SHUMATE_IS_ERROR_TILE_SOURCE (self));
g_return_if_fail (SHUMATE_IS_TILE (tile));
ShumateMapSource *next_source = shumate_map_source_get_next_source (map_source);
if (shumate_tile_get_state (tile) == SHUMATE_STATE_DONE)
return;
if (shumate_tile_get_state (tile) != SHUMATE_STATE_LOADED)
{
g_autoptr(GdkTexture) texture = NULL;
guint tile_size = shumate_tile_get_size (tile);
cairo_surface_t *surface;
cairo_t *cr;
cairo_pattern_t *pat;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, tile_size, tile_size);
cr = cairo_create (surface);
// draw a linear gray to white pattern
pat = cairo_pattern_create_linear (tile_size / 2.0, 0.0, tile_size, tile_size / 2.0);
cairo_pattern_add_color_stop_rgb (pat, 0, 0.686, 0.686, 0.686);
cairo_pattern_add_color_stop_rgb (pat, 1, 0.925, 0.925, 0.925);
cairo_set_source (cr, pat);
cairo_rectangle (cr, 0, 0, tile_size, tile_size);
cairo_fill (cr);
cairo_pattern_destroy (pat);
// draw the red cross
cairo_set_source_rgb (cr, 0.424, 0.078, 0.078);
cairo_set_line_width (cr, 14.0);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_move_to (cr, 24, 24);
cairo_line_to (cr, 50, 50);
cairo_move_to (cr, 50, 24);
cairo_line_to (cr, 24, 50);
cairo_stroke (cr);
cairo_destroy (cr);
cairo_surface_flush (surface);
texture = texture_new_for_surface (surface);
cairo_surface_destroy (surface);
shumate_tile_set_texture (tile, texture);
shumate_tile_set_fade_in (tile, TRUE);
shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
}
else if (SHUMATE_IS_MAP_SOURCE (next_source))
shumate_map_source_fill_tile (next_source, tile, cancellable);
else if (shumate_tile_get_state (tile) == SHUMATE_STATE_LOADED)
{
/* if we have some content, use the tile even if it wasn't validated */
shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
}
g_task_report_new_error (self, callback, user_data, fill_tile_async,
SHUMATE_NETWORK_SOURCE_ERROR, SHUMATE_NETWORK_SOURCE_ERROR_FAILED,
"No tile found.");
}
static void
......@@ -125,7 +53,7 @@ shumate_error_tile_source_class_init (ShumateErrorTileSourceClass *klass)
{
ShumateMapSourceClass *map_source_class = SHUMATE_MAP_SOURCE_CLASS (klass);
map_source_class->fill_tile = fill_tile;
map_source_class->fill_tile_async = fill_tile_async;
}
......
......@@ -50,9 +50,11 @@ static guint get_min_zoom_level (ShumateMapSource *map_source);
static guint get_max_zoom_level (ShumateMapSource *map_source);
static guint get_tile_size (ShumateMapSource *map_source);
static void fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable);
static void fill_tile_async (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static void on_set_next_source_cb (ShumateMapSourceChain *source_chain,
G_GNUC_UNUSED gpointer user_data);
......@@ -85,7 +87,7 @@ shumate_map_source_chain_class_init (ShumateMapSourceChainClass *klass)
map_source_class->get_max_zoom_level = get_max_zoom_level;
map_source_class->get_tile_size = get_tile_size;
map_source_class->fill_tile = fill_tile;
map_source_class->fill_tile_async = fill_tile_async;
}
......@@ -203,9 +205,11 @@ get_tile_size (ShumateMapSource *map_source)
static void
fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable)
fill_tile_async (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
ShumateMapSourceChain *source_chain = SHUMATE_MAP_SOURCE_CHAIN (map_source);
ShumateMapSourceChainPrivate *priv = shumate_map_source_chain_get_instance_private (source_chain);
......@@ -213,7 +217,7 @@ fill_tile (ShumateMapSource *map_source,
g_return_if_fail (SHUMATE_IS_MAP_SOURCE_CHAIN (map_source));
g_return_if_fail (priv->stack_top);
shumate_map_source_fill_tile (priv->stack_top, tile, cancellable);
shumate_map_source_fill_tile_async (priv->stack_top, tile, cancellable, callback, user_data);
}
......
......@@ -143,7 +143,7 @@ shumate_map_source_class_init (ShumateMapSourceClass *klass)
klass->get_tile_size = NULL;
klass->get_projection = NULL;
klass->fill_tile = NULL;
klass->fill_tile_async = NULL;
/**
* ShumateMapSource:next-source:
......@@ -532,29 +532,6 @@ shumate_map_source_get_meters_per_pixel (ShumateMapSource *map_source,
}
/**
* shumate_map_source_fill_tile:
* @map_source: a #ShumateMapSource
* @tile: a #ShumateTile
* @cancellable: a #GCancellable or %NULL
*
* Fills the tile with image data (either from cache, network or rendered
* locally).
*/
void
shumate_map_source_fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable)
{
g_return_if_fail (SHUMATE_IS_MAP_SOURCE (map_source));
shumate_tile_set_state (tile, SHUMATE_STATE_LOADING);
SHUMATE_MAP_SOURCE_GET_CLASS (map_source)->fill_tile (map_source, tile, cancellable);
}
static void on_tile_notify_state (GObject *object, GParamSpec *pspec, gpointer user_data);
/**
* shumate_map_source_fill_tile_async:
* @self: a #ShumateMapSource
......@@ -572,33 +549,11 @@ shumate_map_source_fill_tile_async (ShumateMapSource *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_return_if_fail (SHUMATE_IS_MAP_SOURCE (self));
g_return_if_fail (SHUMATE_IS_TILE (tile));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, shumate_map_source_fill_tile_async);
g_signal_connect (tile, "notify::state", G_CALLBACK (on_tile_notify_state), g_object_ref (task));
shumate_map_source_fill_tile (self, tile, cancellable);
}
static void
on_tile_notify_state (GObject *object, GParamSpec *pspec, gpointer user_data)
{
GTask *task = user_data;
ShumateTile *tile = SHUMATE_TILE (object);
if (shumate_tile_get_state (tile) == SHUMATE_STATE_DONE)
{
g_signal_handlers_disconnect_by_data (object, user_data);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
return SHUMATE_MAP_SOURCE_GET_CLASS (self)->fill_tile_async (self, tile, cancellable, callback, user_data);
}
/**
......
......@@ -62,9 +62,11 @@ struct _ShumateMapSourceClass
guint (*get_tile_size)(ShumateMapSource *map_source);
ShumateMapProjection (*get_projection)(ShumateMapSource *map_source);
void (*fill_tile)(ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable);
void (*fill_tile_async) (ShumateMapSource *self,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
};
ShumateMapSource *shumate_map_source_get_next_source (ShumateMapSource *map_source);
......@@ -101,9 +103,6 @@ double shumate_map_source_get_meters_per_pixel (ShumateMapSource *map_source,
double latitude,
double longitude);
void shumate_map_source_fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable);
void shumate_map_source_fill_tile_async (ShumateMapSource *self,
ShumateTile *tile,
GCancellable *cancellable,
......
......@@ -81,30 +81,32 @@ G_DEFINE_TYPE_WITH_PRIVATE (ShumateNetworkTileSource, shumate_network_tile_sourc
*/
#define MAX_CONNS_DEFAULT 2
typedef struct
{
typedef struct {
ShumateNetworkTileSource *self;
GCancellable *cancellable;
ShumateTile *tile;
SoupMessage *msg;
GBytes *cached_data;
} TileLoadedData;
char *etag;
SoupMessage *msg;
} FillTileData;
typedef struct
static void
fill_tile_data_free (FillTileData *data)
{
ShumateNetworkTileSource *self;
GCancellable *cancellable;
ShumateTile *tile;
char *etag;
} TileRenderedData;
g_clear_object (&data->self);
g_clear_object (&data->tile);
g_clear_pointer (&data->cached_data, g_bytes_unref);
g_clear_pointer (&data->etag, g_free);
g_clear_object (&data->msg);
g_free (data);
}
static void fill_tile (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable);
static void tile_state_notify (ShumateTile *tile,
G_GNUC_UNUSED GParamSpec *pspec,
GCancellable *cancellable);
static void fill_tile_async (ShumateMapSource *map_source,
ShumateTile *tile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static char *get_tile_uri (ShumateNetworkTileSource *source,
int x,
......@@ -241,7 +243,7 @@ shumate_network_tile_source_class_init (ShumateNetworkTileSourceClass *klass)
object_class->get_property = shumate_network_tile_source_get_property;
object_class->set_property = shumate_network_tile_source_set_property;
map_source_class->fill_tile = fill_tile;
map_source_class->fill_tile_async = fill_tile_async;
/**
* ShumateNetworkTileSource:uri-format:
......@@ -671,72 +673,68 @@ on_pixbuf_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
TileRenderedData *rendered_data = (TileRenderedData *) user_data;
g_autoptr(ShumateNetworkTileSource) self = g_steal_pointer (&rendered_data->self);
ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (self);
g_autoptr(GCancellable) cancellable = g_steal_pointer (&rendered_data->cancellable);
g_autoptr(ShumateTile) tile = g_steal_pointer (&rendered_data->tile);
g_autoptr(GError) error = NULL;
g_autoptr(GTask) task = user_data;
FillTileData *data = g_task_get_task_data (task);
ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (data->self);
GCancellable *cancellable = g_task_get_cancellable (task);
GError *error = NULL;
g_autoptr(GdkPixbuf) pixbuf = NULL;
g_autoptr(GdkTexture) texture = NULL;
g_autofree char *etag = g_steal_pointer (&rendered_data->etag);
g_slice_free (TileRenderedData, rendered_data);
g_signal_handlers_disconnect_by_func (tile, tile_state_notify, cancellable);
g_autofree char *buffer = NULL;
gsize buffer_size;
pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
if (!pixbuf)
if (error != NULL)
{
ShumateMapSource *next_source = shumate_map_source_get_next_source (SHUMATE_MAP_SOURCE (self));
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
DEBUG ("Download of tile %d, %d got cancelled",
shumate_tile_get_x (tile), shumate_tile_get_y (tile));
return;
}
if (next_source)
shumate_map_source_fill_tile (next_source, tile, cancellable);
g_task_return_error (task, error);
return;
}
if (data->etag != NULL)
shumate_tile_set_etag (data->tile, data->etag);
if (etag != NULL)
shumate_tile_set_etag (tile, etag);
g_autofree char *buffer = NULL;
gsize buffer_size;
if (!gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
g_warning ("Unable to export tile: %s", error->message);
{
g_warning ("Unable to export tile: %s", error->message);
g_error_free (error);
}
else
{
g_autoptr(GBytes) bytes = g_bytes_new_take (g_steal_pointer (&buffer), buffer_size);
shumate_file_cache_store_tile_async (priv->file_cache, tile, bytes, etag, cancellable, NULL, NULL);
shumate_file_cache_store_tile_async (priv->file_cache, data->tile, bytes, data->etag, cancellable, NULL, NULL);
}
texture = gdk_texture_new_for_pixbuf (pixbuf);
shumate_tile_set_texture (tile, texture);
shumate_tile_set_fade_in (tile, TRUE);
shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
shumate_tile_set_texture (data->tile, texture);
shumate_tile_set_fade_in (data->tile, TRUE);
shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
g_task_return_boolean (task, TRUE);
}
static void
on_pixbuf_created_from_cache (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
g_autoptr(ShumateTile) tile = SHUMATE_TILE (user_data);
g_autoptr(GTask) task = user_data;
FillTileData *data = g_task_get_task_data (task);
g_autoptr(GdkTexture) texture = NULL;
g_autoptr(GdkPixbuf) pixbuf = NULL;
GError *error = NULL;
pixbuf = gdk_pixbuf_new_from_stream_finish (res, NULL);
pixbuf = gdk_pixbuf_new_from_stream_finish (res, &error);
if (pixbuf == NULL)
return;
if (error != NULL)
{
g_task_return_error (task, error);
return;
}
texture = gdk_texture_new_for_pixbuf (pixbuf);
shumate_tile_set_texture (tile, texture);
shumate_tile_set_fade_in (tile, TRUE);
shumate_tile_set_state (tile, SHUMATE_STATE_DONE);
shumate_tile_set_texture (data->tile, texture);
shumate_tile_set_fade_in (data->tile, TRUE);
shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
g_task_return_boolean (task, TRUE);
}
static void
......@@ -744,87 +742,50 @@ on_message_sent (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
TileLoadedData *callback_data = (TileLoadedData *) user_data;
g_autoptr(SoupMessage) msg = g_steal_pointer (&callback_data->msg);
g_autoptr(ShumateNetworkTileSource) self = g_steal_pointer (&callback_data->self);
g_autoptr(GCancellable) cancellable = g_steal_pointer (&callback_data->cancellable);
g_autoptr(ShumateTile) tile = g_steal_pointer (&callback_data->tile);
g_autoptr(GBytes) cached_data = g_steal_pointer (&callback_data->cached_data);
g_autoptr(GTask) task = user_data;
FillTileData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_autoptr(GInputStream) input_stream = NULL;
g_autoptr(GError) error = NULL;
ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (self);
ShumateMapSource *next_source = shumate_map_source_get_next_source (SHUMATE_MAP_SOURCE (self));
GError *error = NULL;
ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (data->self);
const char *etag;
TileRenderedData *data;
g_slice_free (TileLoadedData, callback_data);
input_stream = soup_session_send_finish (priv->soup_session, res, &error);
if (!input_stream)
if (error != NULL)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
DEBUG ("Download of tile %d, %d got cancelled",
shumate_tile_get_x (tile), shumate_tile_get_y (tile));
g_signal_handlers_disconnect_by_func (tile, tile_state_notify, cancellable);
return;
}
g_task_return_error (task, error);
return;
}
DEBUG ("Got reply %d", msg->status_code);
DEBUG ("Got reply %d", data->msg->status_code);
if (msg->status_code == SOUP_STATUS_NOT_MODIFIED)
if (data->msg->status_code == SOUP_STATUS_NOT_MODIFIED)
{
g_autoptr(GInputStream) cache_stream = NULL;
shumate_file_cache_mark_up_to_date (priv->file_cache, tile);
shumate_file_cache_mark_up_to_date (priv->file_cache, data->tile);
cache_stream = g_memory_input_stream_new_from_bytes (cached_data);
gdk_pixbuf_new_from_stream_async (cache_stream, cancellable, on_pixbuf_created_from_cache, g_object_ref (tile));
g_signal_handlers_disconnect_by_func (tile, tile_state_notify, cancellable);
cache_stream = g_memory_input_stream_new_from_bytes (data->cached_data);
gdk_pixbuf_new_from_stream_async (cache_stream, cancellable, on_pixbuf_created_from_cache, g_object_ref (task));
return;
}
if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
if (!SOUP_STATUS_IS_SUCCESSFUL (data->msg->status_code))
{
DEBUG ("Unable to download tile %d, %d: %s",
shumate_tile_get_x (tile),
shumate_tile_get_y (tile),
soup_status_get_phrase (msg->status_code));
g_signal_handlers_disconnect_by_func (tile, tile_state_notify, cancellable);
if (next_source)
shumate_map_source_fill_tile (next_source, tile, cancellable);
g_task_return_new_error (task, SHUMATE_NETWORK_SOURCE_ERROR,
SHUMATE_NETWORK_SOURCE_ERROR_BAD_RESPONSE,
"Unable to download tile: HTTP %s",
soup_status_get_phrase (data->msg->status_code));
return;
}
/* Verify if the server sent an etag and save it */
etag = soup_message_headers_get_one (msg->response_headers, "ETag");
etag = soup_message_headers_get_one (data->msg->response_headers, "ETag");
DEBUG ("Received ETag %s", etag);
data = g_slice_new0 (TileRenderedData);
data->self = g_object_ref (self);
if (data->cancellable)
data->cancellable = g_object_ref (cancellable);
data->tile = g_object_ref (tile);
data->etag = g_strdup (etag);
gdk_pixbuf_new_from_stream_async (input_stream, cancellable, on_pixbuf_created, data);
}
static void
tile_state_notify (ShumateTile *tile,
G_GNUC_UNUSED GParamSpec *pspec,
GCancellable *cancellable)
{
if (shumate_tile_get_state (tile) == SHUMATE_STATE_DONE)
{
DEBUG ("Canceling tile download");
g_cancellable_cancel (cancellable);
}
gdk_pixbuf_new_from_stream_async (input_stream, cancellable, on_pixbuf_created, g_object_ref (task));
}
......@@ -842,65 +803,50 @@ get_modified_time_string (ShumateTile *tile)
return g_date_time_format (modified_time, "%a, %d %b %Y %T %Z");
}
typedef struct {
ShumateNetworkTileSource *self;
ShumateTile *tile;
GCancellable *cancellable;
} FillTileData;
static void
fill_tile_data_free (FillTileData *data)
{
g_clear_object (&data->self);
g_clear_object (&data->tile);
g_clear_object (&data->cancellable);