[libshumate] network-source: Render expired cache data



commit 5e56023a83029b683cdb79962ddf176d5eec8945
Author: James Westman <james jwestman net>
Date:   Thu Apr 8 00:25:33 2021 -0500

    network-source: Render expired cache data
    
    In the network tile source, render cached tiles even if they are out of
    date, then update them if a new tile is fetched from the network. This
    way, the map doesn't flicker while we wait for network requests every
    time the cache expiration period is up.

 shumate/shumate-network-tile-source.c | 90 ++++++++++++++++++++++-------------
 1 file changed, 57 insertions(+), 33 deletions(-)
---
diff --git a/shumate/shumate-network-tile-source.c b/shumate/shumate-network-tile-source.c
index c784b90..94d7870 100644
--- a/shumate/shumate-network-tile-source.c
+++ b/shumate/shumate-network-tile-source.c
@@ -85,9 +85,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (ShumateNetworkTileSource, shumate_network_tile_sourc
 typedef struct {
   ShumateNetworkTileSource *self;
   ShumateTile *tile;
-  GBytes *cached_data;
+  GBytes *bytes;
   char *etag;
   SoupMessage *msg;
+  GDateTime *modtime;
 } FillTileData;
 
 static void
@@ -95,9 +96,10 @@ fill_tile_data_free (FillTileData *data)
 {
   g_clear_object (&data->self);
   g_clear_object (&data->tile);
-  g_clear_pointer (&data->cached_data, g_bytes_unref);
+  g_clear_pointer (&data->bytes, g_bytes_unref);
   g_clear_pointer (&data->etag, g_free);
   g_clear_object (&data->msg);
+  g_clear_pointer (&data->modtime, g_date_time_unref);
   g_free (data);
 }
 
@@ -667,6 +669,7 @@ get_tile_uri (ShumateNetworkTileSource *tile_source,
   return token;
 }
 
+static void fetch_from_network (GTask *task);
 
 static gboolean
 tile_is_expired (GDateTime *modified_time)
@@ -678,6 +681,9 @@ tile_is_expired (GDateTime *modified_time)
 }
 
 
+/* Fill the tile from the pixbuf, created from the network response. Begin
+ * storing the data in the cache (but don't wait for that to finish). Then
+ * return. */
 static void
 on_pixbuf_created (GObject      *source_object,
                    GAsyncResult *res,
@@ -703,7 +709,7 @@ on_pixbuf_created (GObject      *source_object,
   if (!gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &buffer_size, "png", &error, NULL))
     {
       g_warning ("Unable to export tile: %s", error->message);
-      g_error_free (error);
+      g_clear_pointer (&error, g_error_free);
     }
   else
     {
@@ -714,11 +720,13 @@ on_pixbuf_created (GObject      *source_object,
   texture = gdk_texture_new_for_pixbuf (pixbuf);
   shumate_tile_set_texture (data->tile, texture);
   shumate_tile_set_fade_in (data->tile, TRUE);
-  shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
 
+  shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
   g_task_return_boolean (task, TRUE);
 }
 
+/* Fill the tile from the pixbuf, created from the cache. Then, if the cache
+ * data is potentially out of date, fetch from the network. */
 static void
 on_pixbuf_created_from_cache (GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
@@ -739,11 +747,19 @@ on_pixbuf_created_from_cache (GObject *source_object, GAsyncResult *res, gpointe
   texture = gdk_texture_new_for_pixbuf (pixbuf);
   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);
+  if (data->bytes != NULL && !tile_is_expired (data->modtime))
+    {
+      shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
+      g_task_return_boolean (task, TRUE);
+    }
+  else
+    fetch_from_network (task);
 }
 
+
+/* Receive the response from the network. If the tile hasn't been modified,
+ * return; otherwise, parse the data into a pixbuf. */
 static void
 on_message_sent (GObject *source_object,
                  GAsyncResult *res,
@@ -755,7 +771,6 @@ on_message_sent (GObject *source_object,
   g_autoptr(GInputStream) input_stream = NULL;
   GError *error = NULL;
   ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (data->self);
-  const char *etag;
 
   input_stream = soup_session_send_finish (priv->soup_session, res, &error);
   if (error != NULL)
@@ -763,17 +778,19 @@ on_message_sent (GObject *source_object,
       g_task_return_error (task, error);
       return;
     }
-  
+
   DEBUG ("Got reply %d", data->msg->status_code);
 
   if (data->msg->status_code == SOUP_STATUS_NOT_MODIFIED)
     {
-      g_autoptr(GInputStream) cache_stream = NULL;
+      /* The tile has already been filled from the cache, and the server says
+       * it doesn't have a newer one. Just update the cache, mark the tile as
+       * DONE, and return. */
 
       shumate_file_cache_mark_up_to_date (priv->file_cache, data->tile);
 
-      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));
+      shumate_tile_set_state (data->tile, SHUMATE_STATE_DONE);
+      g_task_return_boolean (task, TRUE);
       return;
     }
 
@@ -787,11 +804,10 @@ on_message_sent (GObject *source_object,
     }
 
   /* Verify if the server sent an etag and save it */
-  etag = soup_message_headers_get_one (data->msg->response_headers, "ETag");
+  g_clear_pointer (&data->etag, g_free);
+  data->etag = g_strdup (soup_message_headers_get_one (data->msg->response_headers, "ETag"));
   DEBUG ("Received ETag %s", etag);
 
-  data->etag = g_strdup (etag);
-
   gdk_pixbuf_new_from_stream_async (input_stream, cancellable, on_pixbuf_created, g_object_ref (task));
 }
 
@@ -842,39 +858,47 @@ fill_tile_async (ShumateMapSource *self,
   shumate_file_cache_get_tile_async (priv->file_cache, tile, cancellable, on_file_cache_get_tile, 
g_object_ref (task));
 }
 
+/* If the cache returned data, parse it into a pixbuf, otherwise go straight
+ * to fetching from the network */
 static void
 on_file_cache_get_tile (GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
   g_autoptr(GTask) task = user_data;
   FillTileData *data = g_task_get_task_data (task);
   GCancellable *cancellable = g_task_get_cancellable (task);
-  ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (data->self);
-  g_autoptr(GDateTime) modtime = NULL;
-  g_autofree char *uri = NULL;
-  g_autofree char *etag = NULL;
-  g_autoptr(GBytes) bytes = NULL;
-  g_autofree char *modtime_string = NULL;
 
-  bytes = shumate_file_cache_get_tile_finish (SHUMATE_FILE_CACHE (source_object),
-                                              &etag, &modtime, res, NULL);
+  data->bytes = shumate_file_cache_get_tile_finish (SHUMATE_FILE_CACHE (source_object),
+                                                    &data->etag, &data->modtime, res, NULL);
 
-  if (bytes && !tile_is_expired (modtime))
+  if (data->bytes != NULL)
     {
-      /* No need to fetch new data from the network. Just fill the tile
-       * directly from the cache. */
+      g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (data->bytes);
 
-      g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (bytes);
+      /* When on_pixbuf_created_from_cache() is called, it will call
+       * fetch_from_network() if needed */
       gdk_pixbuf_new_from_stream_async (input_stream, cancellable, on_pixbuf_created_from_cache, 
g_object_ref (task));
-      return;
     }
+  else
+    {
+      fetch_from_network (task);
+    }
+}
+
+/* Fetch the tile from the network. */
+static void
+fetch_from_network (GTask *task)
+{
+  FillTileData *data = g_task_get_task_data (task);
+  GCancellable *cancellable = g_task_get_cancellable (task);
+  ShumateNetworkTileSourcePrivate *priv = shumate_network_tile_source_get_instance_private (data->self);
+  g_autofree char *uri = NULL;
+  g_autofree char *modtime_string = NULL;
 
   uri = get_tile_uri (data->self,
         shumate_tile_get_x (data->tile),
         shumate_tile_get_y (data->tile),
         shumate_tile_get_zoom_level (data->tile));
 
-  if (bytes)
-    data->cached_data = g_bytes_ref (bytes);
   data->msg = soup_message_new (SOUP_METHOD_GET, uri);
 
   if (data->msg == NULL)
@@ -885,17 +909,17 @@ on_file_cache_get_tile (GObject *source_object, GAsyncResult *res, gpointer user
       return;
     }
 
-  modtime_string = get_modified_time_string (modtime);
+  modtime_string = get_modified_time_string (data->modtime);
 
   /* If an etag is available, only use it.
    * OSM servers seems to send now as the modified time for all tiles
    * Omarender servers set the modified time correctly
    */
-  if (etag)
+  if (data->etag)
     {
-      DEBUG ("If-None-Match: %s", etag);
+      DEBUG ("If-None-Match: %s", data->etag);
       soup_message_headers_append (data->msg->request_headers,
-          "If-None-Match", etag);
+          "If-None-Match", data->etag);
     }
   else if (modtime_string)
     {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]