summaryrefslogtreecommitdiff
path: root/libsoup/soup-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/soup-cache.c')
-rw-r--r--libsoup/soup-cache.c811
1 files changed, 331 insertions, 480 deletions
diff --git a/libsoup/soup-cache.c b/libsoup/soup-cache.c
index 8b26478d..c17e537c 100644
--- a/libsoup/soup-cache.c
+++ b/libsoup/soup-cache.c
@@ -31,15 +31,28 @@
#include <string.h>
-#define LIBSOUP_USE_UNSTABLE_REQUEST_API
-
#include "soup-cache.h"
+#include "soup-body-input-stream.h"
+#include "soup-cache-input-stream.h"
#include "soup-cache-private.h"
+#include "soup-content-processor.h"
+#include "soup-message-private.h"
#include "soup.h"
+#include "soup-message-private.h"
+
+/**
+ * SECTION:soup-cache
+ * @short_description: Caching support
+ *
+ * #SoupCache implements a file-based cache for HTTP resources.
+ */
static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+static SoupContentProcessorInterface *soup_cache_default_content_processor_interface;
+static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
+
#define DEFAULT_MAX_SIZE 50 * 1024 * 1024
#define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
of the cache that can be
@@ -65,6 +78,19 @@ static void soup_cache_session_feature_init (SoupSessionFeatureInterface *featur
*/
#define SOUP_CACHE_CURRENT_VERSION 5
+#define OLD_SOUP_CACHE_FILE "soup.cache"
+#define SOUP_CACHE_FILE "soup.cache2"
+
+#define SOUP_CACHE_HEADERS_FORMAT "{ss}"
+#define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
+#define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
+
+/* Basically the same format than above except that some strings are
+ prepended with &. This way the GVariant returns a pointer to the
+ data instead of duplicating the string */
+#define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
+
+
typedef struct _SoupCacheEntry {
guint32 key;
char *uri;
@@ -73,13 +99,9 @@ typedef struct _SoupCacheEntry {
gsize length;
guint32 corrected_initial_age;
guint32 response_time;
- SoupBuffer *current_writing_buffer;
gboolean dirty;
- gboolean got_body;
gboolean being_validated;
SoupMessageHeaders *headers;
- GOutputStream *stream;
- GError *error;
guint32 hits;
GCancellable *cancellable;
guint16 status_code;
@@ -97,17 +119,6 @@ struct _SoupCachePrivate {
GList *lru_start;
};
-typedef struct {
- SoupCache *cache;
- SoupCacheEntry *entry;
- SoupMessage *msg;
- gulong content_sniffed_handler;
- gulong got_chunk_handler;
- gulong got_body_handler;
- gulong restarted_handler;
- GQueue *buffer_queue;
-} SoupCacheWritingFixture;
-
enum {
PROP_0,
PROP_CACHE_DIR,
@@ -118,12 +129,13 @@ enum {
G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
- soup_cache_session_feature_init))
+ soup_cache_session_feature_init)
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
+ soup_cache_content_processor_init))
-static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry);
+static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge);
static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
-static gboolean write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture);
static GFile *
get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
@@ -260,17 +272,10 @@ get_cacheability (SoupCache *cache, SoupMessage *msg)
* and also unref's the GFile object representing it.
*/
static void
-soup_cache_entry_free (SoupCacheEntry *entry, GFile *file)
+soup_cache_entry_free (SoupCacheEntry *entry)
{
- if (file) {
- g_file_delete (file, NULL, NULL);
- g_object_unref (file);
- }
-
g_free (entry->uri);
- g_clear_pointer (&entry->current_writing_buffer, soup_buffer_free);
g_clear_pointer (&entry->headers, soup_message_headers_free);
- g_clear_error (&entry->error);
g_clear_object (&entry->cancellable);
g_slice_free (SoupCacheEntry, entry);
@@ -282,6 +287,12 @@ copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
soup_message_headers_append (headers, name, value);
}
+static void
+remove_headers (const char *name, const char *value, SoupMessageHeaders *headers)
+{
+ soup_message_headers_remove (headers, name);
+}
+
static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
static void
@@ -324,8 +335,14 @@ soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCac
const char *cache_control;
const char *expires, *date, *last_modified;
+ /* Reset these values. We have to do this to ensure that
+ * revalidations overwrite previous values for the headers.
+ */
+ entry->must_revalidate = FALSE;
+ entry->freshness_lifetime = 0;
+
cache_control = soup_message_headers_get_list (entry->headers, "Cache-Control");
- if (cache_control) {
+ if (cache_control && *cache_control) {
const char *max_age, *s_maxage;
gint64 freshness_lifetime = 0;
GHashTable *hash;
@@ -442,10 +459,7 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, t
entry = g_slice_new0 (SoupCacheEntry);
entry->dirty = FALSE;
- entry->current_writing_buffer = NULL;
- entry->got_body = FALSE;
entry->being_validated = FALSE;
- entry->error = NULL;
entry->status_code = msg->status_code;
entry->response_time = response_time;
entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
@@ -488,243 +502,11 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, t
return entry;
}
-static void
-soup_cache_writing_fixture_free (SoupCacheWritingFixture *fixture)
-{
- /* Free fixture. And disconnect signals, we don't want to
- listen to more SoupMessage events as we're finished with
- this resource */
- if (g_signal_handler_is_connected (fixture->msg, fixture->content_sniffed_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->content_sniffed_handler);
- if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler);
- if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
- if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
- g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
- g_queue_foreach (fixture->buffer_queue, (GFunc) soup_buffer_free, NULL);
- g_queue_free (fixture->buffer_queue);
- g_object_unref (fixture->msg);
- g_object_unref (fixture->cache);
- g_slice_free (SoupCacheWritingFixture, fixture);
-}
-
-static void
-msg_content_sniffed_cb (SoupMessage *msg, gchar *content_type, GHashTable *params, SoupCacheWritingFixture *fixture)
-{
- soup_message_headers_set_content_type (fixture->entry->headers, content_type, params);
-}
-
-static void
-close_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
-{
- SoupCacheEntry *entry = fixture->entry;
- SoupCache *cache = fixture->cache;
- GOutputStream *stream = G_OUTPUT_STREAM (source);
- goffset content_length;
-
- g_warn_if_fail (entry->error == NULL);
-
- /* FIXME: what do we do on error ? */
-
- if (stream) {
- g_output_stream_close_finish (stream, result, NULL);
- g_object_unref (stream);
- }
- entry->stream = NULL;
-
- content_length = soup_message_headers_get_content_length (entry->headers);
-
- /* If the process was cancelled, then delete the entry from
- the cache. Do it also if the size of a chunked resource is
- too much for the cache */
- if (g_cancellable_is_cancelled (entry->cancellable)) {
- entry->dirty = FALSE;
- soup_cache_entry_remove (cache, entry);
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
- entry = NULL;
- } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
- entry->length != (gsize) content_length) {
- /* Two options here:
- *
- * 1. "chunked" data, entry was temporarily added to
- * cache (as content-length is 0) and now that we have
- * the actual size we have to evaluate if we want it
- * in the cache or not
- *
- * 2. Content-Length has a different value than actual
- * length, means that the content was encoded for
- * transmission (typically compressed) and thus we
- * have to substract the content-length value that was
- * added to the cache and add the unencoded length
- */
- gint length_to_add = entry->length - content_length;
-
- /* Make room in cache if needed */
- if (cache_accepts_entries_of_size (cache, length_to_add)) {
- make_room_for_new_entry (cache, length_to_add);
-
- cache->priv->size += length_to_add;
- } else {
- entry->dirty = FALSE;
- soup_cache_entry_remove (cache, entry);
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
- entry = NULL;
- }
- }
-
- if (entry) {
- entry->dirty = FALSE;
- entry->got_body = FALSE;
-
- if (entry->current_writing_buffer) {
- soup_buffer_free (entry->current_writing_buffer);
- entry->current_writing_buffer = NULL;
- }
-
- g_object_unref (entry->cancellable);
- entry->cancellable = NULL;
- }
-
- cache->priv->n_pending--;
-
- /* Frees */
- soup_cache_writing_fixture_free (fixture);
-}
-
-static void
-write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
-{
- GOutputStream *stream = G_OUTPUT_STREAM (source);
- GError *error = NULL;
- gssize write_size;
- SoupCacheEntry *entry = fixture->entry;
-
- if (g_cancellable_is_cancelled (entry->cancellable)) {
- g_output_stream_close_async (stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- return;
- }
-
- write_size = g_output_stream_write_finish (stream, result, &error);
- if (write_size <= 0 || error) {
- if (error)
- entry->error = error;
- g_output_stream_close_async (stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- /* FIXME: We should completely stop caching the
- resource at this point */
- } else {
- /* Are we still writing and is there new data to write
- already ? */
- if (fixture->buffer_queue->length > 0)
- write_next_buffer (entry, fixture);
- else {
- soup_buffer_free (entry->current_writing_buffer);
- entry->current_writing_buffer = NULL;
-
- if (entry->got_body) {
- /* If we already received 'got-body'
- and we have written all the data,
- we can close the stream */
- g_output_stream_close_async (entry->stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
- }
- }
- }
-}
-
static gboolean
-write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture)
-{
- SoupBuffer *buffer = g_queue_pop_head (fixture->buffer_queue);
-
- if (buffer == NULL)
- return FALSE;
-
- /* Free the old buffer */
- if (entry->current_writing_buffer) {
- soup_buffer_free (entry->current_writing_buffer);
- entry->current_writing_buffer = NULL;
- }
- entry->current_writing_buffer = buffer;
-
- g_output_stream_write_async (entry->stream, buffer->data, buffer->length,
- G_PRIORITY_LOW, entry->cancellable,
- (GAsyncReadyCallback) write_ready_cb,
- fixture);
- return TRUE;
-}
-
-static void
-msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, SoupCacheWritingFixture *fixture)
-{
- SoupCacheEntry *entry = fixture->entry;
-
- /* Ignore this if the writing or appending was cancelled */
- if (!g_cancellable_is_cancelled (entry->cancellable)) {
- g_queue_push_tail (fixture->buffer_queue, soup_buffer_copy (chunk));
- entry->length += chunk->length;
-
- if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
- /* Quickly cancel the caching of the resource */
- g_cancellable_cancel (entry->cancellable);
- }
- }
-
- /* FIXME: remove the error check when we cancel the caching at
- the first write error */
- /* Only write if the entry stream is ready */
- if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
- write_next_buffer (entry, fixture);
-}
-
-static void
-msg_got_body_cb (SoupMessage *msg, SoupCacheWritingFixture *fixture)
-{
- SoupCacheEntry *entry = fixture->entry;
- g_return_if_fail (entry);
-
- entry->got_body = TRUE;
-
- if (!entry->stream && fixture->buffer_queue->length > 0)
- /* The stream is not ready to be written but we still
- have data to write, we'll write it when the stream
- is opened for writing */
- return;
-
-
- if (fixture->buffer_queue->length > 0) {
- /* If we still have data to write, write it,
- write_ready_cb will close the stream */
- if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
- write_next_buffer (entry, fixture);
- return;
- }
-
- if (entry->stream && entry->current_writing_buffer == NULL)
- g_output_stream_close_async (entry->stream,
- G_PRIORITY_LOW,
- entry->cancellable,
- (GAsyncReadyCallback)close_ready_cb,
- fixture);
-}
-
-static gboolean
-soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry)
+soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge)
{
GList *lru_item;
- /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */
if (entry->dirty) {
g_cancellable_cancel (entry->cancellable);
return FALSE;
@@ -745,6 +527,14 @@ soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry)
g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
+ /* Free resources */
+ if (purge) {
+ GFile *file = get_file_from_entry (cache, entry);
+ g_file_delete (file, NULL, NULL);
+ g_object_unref (file);
+ }
+ soup_cache_entry_free (entry);
+
return TRUE;
}
@@ -803,10 +593,9 @@ make_room_for_new_entry (SoupCache *cache, guint length_to_add)
/* Discard entries. Once cancelled resources will be
* freed in close_ready_cb
*/
- if (soup_cache_entry_remove (cache, old_entry)) {
- soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry));
+ if (soup_cache_entry_remove (cache, old_entry, TRUE))
lru_entry = cache->priv->lru_start;
- } else
+ else
lru_entry = g_list_next (lru_entry);
}
}
@@ -822,7 +611,7 @@ soup_cache_entry_insert (SoupCache *cache,
/* Fill the key */
entry->key = get_cache_key_from_uri ((const char *) entry->uri);
- if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED)
+ if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
length_to_add = soup_message_headers_get_content_length (entry->headers);
/* Check if we are going to store the resource depending on its size */
@@ -836,9 +625,7 @@ soup_cache_entry_insert (SoupCache *cache,
/* Remove any previous entry */
if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
- if (soup_cache_entry_remove (cache, old_entry))
- soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry));
- else
+ if (!soup_cache_entry_remove (cache, old_entry, TRUE))
return FALSE;
}
@@ -879,157 +666,12 @@ soup_cache_entry_lookup (SoupCache *cache,
return entry;
}
-static void
-msg_restarted_cb (SoupMessage *msg, SoupCacheEntry *entry)
-{
- /* FIXME: What should we do here exactly? */
-}
-
-static void
-replace_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
-{
- SoupCacheEntry *entry = fixture->entry;
- GOutputStream *stream = (GOutputStream *) g_file_replace_finish (G_FILE (source),
- result, &entry->error);
-
- if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) {
- if (stream)
- g_object_unref (stream);
- fixture->cache->priv->n_pending--;
- entry->dirty = FALSE;
- soup_cache_entry_remove (fixture->cache, entry);
- soup_cache_entry_free (entry, get_file_from_entry (fixture->cache, entry));
- soup_cache_writing_fixture_free (fixture);
- return;
- }
-
- entry->stream = stream;
-
- /* If we already got all the data we have to initiate the
- * writing here, since we won't get more 'got-chunk'
- * signals
- */
- if (!entry->got_body)
- return;
-
- /* It could happen that reading the data from server
- * was completed before this happens. In that case
- * there is no data
- */
- if (!write_next_buffer (entry, fixture))
- /* Could happen if the resource is empty */
- g_output_stream_close_async (stream, G_PRIORITY_LOW, entry->cancellable,
- (GAsyncReadyCallback) close_ready_cb,
- fixture);
-}
-
-typedef struct {
- time_t request_time;
- SoupSessionFeature *feature;
- gulong got_headers_handler;
-} RequestHelper;
-
-static void
-msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
-{
- SoupCache *cache;
- SoupCacheability cacheable;
- RequestHelper *helper;
- time_t request_time, response_time;
- SoupCacheEntry *entry;
-
- response_time = time (NULL);
-
- helper = (RequestHelper *)user_data;
- cache = SOUP_CACHE (helper->feature);
- request_time = helper->request_time;
- g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
- g_slice_free (RequestHelper, helper);
-
- cacheable = soup_cache_get_cacheability (cache, msg);
-
- if (cacheable & SOUP_CACHE_CACHEABLE) {
- GFile *file;
- SoupCacheWritingFixture *fixture;
-
- /* Check if we are already caching this resource */
- entry = soup_cache_entry_lookup (cache, msg);
-
- if (entry && (entry->dirty || entry->being_validated))
- return;
-
- /* Create a new entry, deleting any old one if present */
- if (entry) {
- soup_cache_entry_remove (cache, entry);
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
- }
-
- entry = soup_cache_entry_new (cache, msg, request_time, response_time);
- entry->hits = 1;
-
- /* Do not continue if it can not be stored */
- if (!soup_cache_entry_insert (cache, entry, TRUE)) {
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
- return;
- }
-
- fixture = g_slice_new0 (SoupCacheWritingFixture);
- fixture->cache = g_object_ref (cache);
- fixture->entry = entry;
- fixture->msg = g_object_ref (msg);
- fixture->buffer_queue = g_queue_new ();
-
- /* We connect now to these signals and buffer the data
- if it comes before the file is ready for writing */
- fixture->content_sniffed_handler =
- g_signal_connect (msg, "content-sniffed", G_CALLBACK (msg_content_sniffed_cb), fixture);
- fixture->got_chunk_handler =
- g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture);
- fixture->got_body_handler =
- g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture);
- fixture->restarted_handler =
- g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry);
-
- /* Prepare entry */
- cache->priv->n_pending++;
-
- entry->dirty = TRUE;
- entry->cancellable = g_cancellable_new ();
- file = get_file_from_entry (cache, entry);
- g_file_replace_async (file, NULL, FALSE,
- G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
- G_PRIORITY_LOW, entry->cancellable,
- (GAsyncReadyCallback) replace_cb, fixture);
- g_object_unref (file);
- } else if (cacheable & SOUP_CACHE_INVALIDATES) {
- entry = soup_cache_entry_lookup (cache, msg);
-
- if (entry) {
- if (soup_cache_entry_remove (cache, entry))
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
- }
- } else if (cacheable & SOUP_CACHE_VALIDATES) {
- entry = soup_cache_entry_lookup (cache, msg);
-
- /* It's possible to get a CACHE_VALIDATES with no
- * entry in the hash table. This could happen if for
- * example the soup client is the one creating the
- * conditional request.
- */
- if (entry) {
- entry->being_validated = FALSE;
- copy_end_to_end_headers (msg->response_headers, entry->headers);
- soup_cache_entry_set_freshness (entry, msg, cache);
- }
- }
-}
-
GInputStream *
soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
{
SoupCacheEntry *entry;
char *current_age;
- GInputStream *stream = NULL;
+ GInputStream *file_stream, *body_stream, *cache_stream;
GFile *file;
g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
@@ -1038,18 +680,19 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
entry = soup_cache_entry_lookup (cache, msg);
g_return_val_if_fail (entry, NULL);
- /* TODO: the original idea was to save reads, but current code
- assumes that a stream is always returned. Need to reach
- some agreement here. Also we have to handle the situation
- were the file was no longer there (for example files
- removed without notifying the cache */
file = get_file_from_entry (cache, entry);
- stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+ file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
g_object_unref (file);
/* Do not change the original message if there is no resource */
- if (stream == NULL)
- return stream;
+ if (!file_stream)
+ return NULL;
+
+ body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length);
+ g_object_unref (file_stream);
+
+ if (!body_stream)
+ return NULL;
/* If we are told to send a response from cache any validation
in course is over by now */
@@ -1068,19 +711,29 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
current_age);
g_free (current_age);
- return stream;
+ /* Create the cache stream. */
+ soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
+ cache_stream = soup_message_setup_body_istream (body_stream, msg,
+ cache->priv->session,
+ SOUP_STAGE_ENTITY_BODY);
+ g_object_unref (body_stream);
+
+ return cache_stream;
+}
+
+static void
+msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
+{
+ g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL)));
+ g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
}
static void
request_started (SoupSessionFeature *feature, SoupSession *session,
SoupMessage *msg, SoupSocket *socket)
{
- RequestHelper *helper = g_slice_new0 (RequestHelper);
- helper->request_time = time (NULL);
- helper->feature = feature;
- helper->got_headers_handler = g_signal_connect (msg, "got-headers",
- G_CALLBACK (msg_got_headers_cb),
- helper);
+ g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL)));
+ g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), NULL);
}
static void
@@ -1103,6 +756,138 @@ soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
feature_interface->request_started = request_started;
}
+typedef struct {
+ SoupCache *cache;
+ SoupCacheEntry *entry;
+} StreamHelper;
+
+static void
+istream_caching_finished (SoupCacheInputStream *istream,
+ gsize bytes_written,
+ GError *error,
+ gpointer user_data)
+{
+ StreamHelper *helper = (StreamHelper *) user_data;
+ SoupCache *cache = helper->cache;
+ SoupCacheEntry *entry = helper->entry;
+
+ --cache->priv->n_pending;
+
+ entry->dirty = FALSE;
+ entry->length = bytes_written;
+ g_clear_object (&entry->cancellable);
+
+ if (error) {
+ /* Update cache size */
+ if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
+ cache->priv->size -= soup_message_headers_get_content_length (entry->headers);
+
+ soup_cache_entry_remove (cache, entry, TRUE);
+ helper->entry = entry = NULL;
+ goto cleanup;
+ }
+
+ if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) {
+
+ if (cache_accepts_entries_of_size (cache, entry->length)) {
+ make_room_for_new_entry (cache, entry->length);
+ cache->priv->size += entry->length;
+ } else {
+ soup_cache_entry_remove (cache, entry, TRUE);
+ helper->entry = entry = NULL;
+ }
+ }
+
+ cleanup:
+ g_object_unref (helper->cache);
+ g_slice_free (StreamHelper, helper);
+}
+
+static GInputStream*
+soup_cache_content_processor_wrap_input (SoupContentProcessor *processor,
+ GInputStream *base_stream,
+ SoupMessage *msg,
+ GError **error)
+{
+ SoupCache *cache = (SoupCache*) processor;
+ SoupCacheEntry *entry;
+ SoupCacheability cacheability;
+ GInputStream *istream;
+ GFile *file;
+ StreamHelper *helper;
+ time_t request_time, response_time;
+
+ /* First of all, check if we should cache the resource. */
+ cacheability = soup_cache_get_cacheability (cache, msg);
+ entry = soup_cache_entry_lookup (cache, msg);
+
+ if (cacheability & SOUP_CACHE_INVALIDATES) {
+ if (entry)
+ soup_cache_entry_remove (cache, entry, TRUE);
+ return NULL;
+ }
+
+ if (cacheability & SOUP_CACHE_VALIDATES) {
+ /* It's possible to get a CACHE_VALIDATES with no
+ * entry in the hash table. This could happen if for
+ * example the soup client is the one creating the
+ * conditional request.
+ */
+ if (entry)
+ soup_cache_update_from_conditional_request (cache, msg);
+ return NULL;
+ }
+
+ if (!(cacheability & SOUP_CACHE_CACHEABLE))
+ return NULL;
+
+ /* Check if we are already caching this resource */
+ if (entry && (entry->dirty || entry->being_validated))
+ return NULL;
+
+ /* Create a new entry, deleting any old one if present */
+ if (entry)
+ soup_cache_entry_remove (cache, entry, TRUE);
+
+ request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
+ response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
+ entry = soup_cache_entry_new (cache, msg, request_time, response_time);
+ entry->hits = 1;
+ entry->dirty = TRUE;
+
+ /* Do not continue if it can not be stored */
+ if (!soup_cache_entry_insert (cache, entry, TRUE)) {
+ soup_cache_entry_free (entry);
+ return NULL;
+ }
+
+ entry->cancellable = g_cancellable_new ();
+ ++cache->priv->n_pending;
+
+ helper = g_slice_new (StreamHelper);
+ helper->cache = g_object_ref (cache);
+ helper->entry = entry;
+
+ file = get_file_from_entry (cache, entry);
+ istream = soup_cache_input_stream_new (base_stream, file);
+ g_object_unref (file);
+
+ g_signal_connect (istream, "caching-finished", G_CALLBACK (istream_caching_finished), helper);
+
+ return istream;
+}
+
+static void
+soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface,
+ gpointer interface_data)
+{
+ soup_cache_default_content_processor_interface =
+ g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
+
+ processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY;
+ processor_interface->wrap_input = soup_cache_content_processor_wrap_input;
+}
+
static void
soup_cache_init (SoupCache *cache)
{
@@ -1127,11 +912,7 @@ static void
remove_cache_item (gpointer data,
gpointer user_data)
{
- SoupCache *cache = (SoupCache *) user_data;
- SoupCacheEntry *entry = (SoupCacheEntry *) data;
-
- if (soup_cache_entry_remove (cache, entry))
- soup_cache_entry_free (entry, NULL);
+ soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE);
}
static void
@@ -1361,7 +1142,7 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
return SOUP_CACHE_RESPONSE_STALE;
cache_control = soup_message_headers_get_list (msg->request_headers, "Cache-Control");
- if (cache_control) {
+ if (cache_control && *cache_control) {
GHashTable *hash = soup_header_parse_param_list (cache_control);
if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
@@ -1470,6 +1251,9 @@ force_flush_timeout (gpointer data)
* committed to disk. For doing so it will iterate the #GMainContext
* associated with @cache's session as long as needed.
*
+ * Contrast with soup_cache_dump(), which writes out the cache index
+ * file.
+ *
* Since: 2.34
*/
void
@@ -1502,19 +1286,39 @@ static void
clear_cache_item (gpointer data,
gpointer user_data)
{
- SoupCache *cache = (SoupCache *) user_data;
- SoupCacheEntry *entry = (SoupCacheEntry *) data;
+ soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE);
+}
+
+static void
+clear_cache_files (SoupCache *cache)
+{
+ GFileInfo *file_info;
+ GFileEnumerator *file_enumerator;
+ GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
+
+ file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
+ G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ if (file_enumerator) {
+ while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
+ const char *filename = g_file_info_get_name (file_info);
- if (soup_cache_entry_remove (cache, entry))
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
+ if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
+ GFile *cache_file = g_file_get_child (cache_dir_file, filename);
+ g_file_delete (cache_file, NULL, NULL);
+ g_object_unref (cache_file);
+ }
+ g_object_unref (file_info);
+ }
+ g_object_unref (file_enumerator);
+ }
+ g_object_unref (cache_dir_file);
}
/**
* soup_cache_clear:
* @cache: a #SoupCache
*
- * Will remove all entries in the @cache plus all the cache files
- * associated with them.
+ * Will remove all entries in the @cache plus all the cache files.
*
* Since: 2.34
*/
@@ -1530,6 +1334,9 @@ soup_cache_clear (SoupCache *cache)
entries = g_hash_table_get_values (cache->priv->cache);
g_list_foreach (entries, clear_cache_item, cache);
g_list_free (entries);
+
+ /* Remove also any file not associated with a cache entry. */
+ clear_cache_files (cache);
}
SoupMessage *
@@ -1539,6 +1346,8 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original
SoupURI *uri;
SoupCacheEntry *entry;
const char *last_modified, *etag;
+ SoupMessagePrivate *origpriv;
+ GSList *f;
g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
@@ -1558,11 +1367,16 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original
/* Copy the data we need from the original message */
uri = soup_message_get_uri (original);
msg = soup_message_new_from_uri (original->method, uri);
+ soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
soup_message_headers_foreach (original->request_headers,
(SoupMessageHeadersForeachFunc)copy_headers,
msg->request_headers);
+ origpriv = SOUP_MESSAGE_GET_PRIVATE (original);
+ for (f = origpriv->disabled_features; f; f = f->next)
+ soup_message_disable_feature (msg, (GType) GPOINTER_TO_SIZE (f->data));
+
if (last_modified)
soup_message_headers_append (msg->request_headers,
"If-Modified-Since",
@@ -1575,17 +1389,38 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original
return msg;
}
-#define OLD_SOUP_CACHE_FILE "soup.cache"
-#define SOUP_CACHE_FILE "soup.cache2"
+void
+soup_cache_cancel_conditional_request (SoupCache *cache,
+ SoupMessage *msg)
+{
+ SoupCacheEntry *entry;
-#define SOUP_CACHE_HEADERS_FORMAT "{ss}"
-#define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
-#define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
+ entry = soup_cache_entry_lookup (cache, msg);
+ if (entry)
+ entry->being_validated = FALSE;
-/* Basically the same format than above except that some strings are
- prepended with &. This way the GVariant returns a pointer to the
- data instead of duplicating the string */
-#define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
+ soup_session_cancel_message (cache->priv->session, msg, SOUP_STATUS_CANCELLED);
+}
+
+void
+soup_cache_update_from_conditional_request (SoupCache *cache,
+ SoupMessage *msg)
+{
+ SoupCacheEntry *entry = soup_cache_entry_lookup (cache, msg);
+ if (!entry)
+ return;
+
+ entry->being_validated = FALSE;
+
+ if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
+ soup_message_headers_foreach (msg->response_headers,
+ (SoupMessageHeadersForeachFunc) remove_headers,
+ entry->headers);
+ copy_end_to_end_headers (msg->response_headers, entry->headers);
+
+ soup_cache_entry_set_freshness (entry, msg, cache);
+ }
+}
static void
pack_entry (gpointer data,
@@ -1597,7 +1432,7 @@ pack_entry (gpointer data,
GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
/* Do not store non-consolidated entries */
- if (entry->dirty || entry->current_writing_buffer != NULL || !entry->key)
+ if (entry->dirty || !entry->key)
return;
g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
@@ -1622,6 +1457,19 @@ pack_entry (gpointer data,
g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
}
+/**
+ * soup_cache_dump:
+ * @cache: a #SoupCache
+ *
+ * Synchronously writes the cache index out to disk. Contrast with
+ * soup_cache_flush(), which writes pending cache
+ * <emphasis>entries</emphasis> to disk.
+ *
+ * You must call this before exiting if you want your cache data to
+ * persist between sessions.
+ *
+ * Since: 2.34.
+ */
void
soup_cache_dump (SoupCache *cache)
{
@@ -1650,30 +1498,14 @@ soup_cache_dump (SoupCache *cache)
g_variant_unref (cache_variant);
}
-static void
-clear_cache_files (SoupCache *cache)
-{
- GFileInfo *file_info;
- GFileEnumerator *file_enumerator;
- GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
-
- file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
- G_FILE_QUERY_INFO_NONE, NULL, NULL);
- if (file_enumerator) {
- while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
- const char *filename = g_file_info_get_name (file_info);
-
- if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
- GFile *cache_file = g_file_get_child (cache_dir_file, filename);
- g_file_delete (cache_file, NULL, NULL);
- g_object_unref (cache_file);
- }
- }
- g_object_unref (file_enumerator);
- }
- g_object_unref (cache_dir_file);
-}
-
+/**
+ * soup_cache_load:
+ * @cache: a #SoupCache
+ *
+ * Loads the contents of @cache's index into memory.
+ *
+ * Since: 2.34
+ */
void
soup_cache_load (SoupCache *cache)
{
@@ -1741,7 +1573,7 @@ soup_cache_load (SoupCache *cache)
entry->status_code = status_code;
if (!soup_cache_entry_insert (cache, entry, FALSE))
- soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
+ soup_cache_entry_free (entry);
}
cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
@@ -1751,6 +1583,15 @@ soup_cache_load (SoupCache *cache)
g_variant_unref (cache_variant);
}
+/**
+ * soup_cache_set_max_size:
+ * @cache: a #SoupCache
+ * @max_size: the maximum size of the cache, in bytes
+ *
+ * Sets the maximum size of the cache.
+ *
+ * Since: 2.34
+ */
void
soup_cache_set_max_size (SoupCache *cache,
guint max_size)
@@ -1759,6 +1600,16 @@ soup_cache_set_max_size (SoupCache *cache,
cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
}
+/**
+ * soup_cache_get_max_size:
+ * @cache: a #SoupCache
+ *
+ * Gets the maximum size of the cache.
+ *
+ * Return value: the maximum size of the cache, in bytes.
+ *
+ * Since: 2.34
+ */
guint
soup_cache_get_max_size (SoupCache *cache)
{