1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2009, 2010 Igalia S.L.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 * - Need to hook the feature in the sync SoupSession.
35 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
37 #include "soup-cache.h"
38 #include "soup-cache-private.h"
39 #include "soup-date.h"
40 #include "soup-enum-types.h"
41 #include "soup-headers.h"
42 #include "soup-session.h"
43 #include "soup-session-feature.h"
46 static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
47 static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
49 #define DEFAULT_MAX_SIZE 50 * 1024 * 1024
50 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
51 of the cache that can be
52 filled by a single entry */
54 typedef struct _SoupCacheEntry {
57 guint freshness_lifetime;
58 gboolean must_revalidate;
62 time_t corrected_initial_age;
67 gboolean being_validated;
68 SoupMessageHeaders *headers;
69 GOutputStream *stream;
72 GCancellable *cancellable;
75 struct _SoupCachePrivate {
80 SoupCacheType cache_type;
83 guint max_entry_data_size; /* Computed value. Here for performance reasons */
89 SoupCacheEntry *entry;
91 gulong got_chunk_handler;
92 gulong got_body_handler;
93 gulong restarted_handler;
94 } SoupCacheWritingFixture;
102 #define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
104 G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
105 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
106 soup_cache_session_feature_init))
108 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry);
109 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
110 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
112 static SoupCacheability
113 get_cacheability (SoupCache *cache, SoupMessage *msg)
115 SoupCacheability cacheability;
116 const char *cache_control, *content_type;
118 /* 1. The request method must be cacheable */
119 if (msg->method == SOUP_METHOD_GET)
120 cacheability = SOUP_CACHE_CACHEABLE;
121 else if (msg->method == SOUP_METHOD_HEAD ||
122 msg->method == SOUP_METHOD_TRACE ||
123 msg->method == SOUP_METHOD_CONNECT)
124 return SOUP_CACHE_UNCACHEABLE;
126 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
128 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
129 if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
130 return SOUP_CACHE_UNCACHEABLE;
132 cache_control = soup_message_headers_get (msg->response_headers, "Cache-Control");
135 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
137 hash = soup_header_parse_param_list (cache_control);
139 /* Shared caches MUST NOT store private resources */
140 if (priv->cache_type == SOUP_CACHE_SHARED) {
141 if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
142 soup_header_free_param_list (hash);
143 return SOUP_CACHE_UNCACHEABLE;
147 /* 2. The 'no-store' cache directive does not appear in the
150 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
151 soup_header_free_param_list (hash);
152 return SOUP_CACHE_UNCACHEABLE;
155 /* This does not appear in section 2.1, but I think it makes
156 * sense to check it too?
158 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
159 soup_header_free_param_list (hash);
160 return SOUP_CACHE_UNCACHEABLE;
163 soup_header_free_param_list (hash);
166 switch (msg->status_code) {
167 case SOUP_STATUS_PARTIAL_CONTENT:
168 /* We don't cache partial responses, but they only
169 * invalidate cached full responses if the headers
172 cacheability = SOUP_CACHE_UNCACHEABLE;
175 case SOUP_STATUS_NOT_MODIFIED:
176 /* A 304 response validates an existing cache entry */
177 cacheability = SOUP_CACHE_VALIDATES;
180 case SOUP_STATUS_MULTIPLE_CHOICES:
181 case SOUP_STATUS_MOVED_PERMANENTLY:
182 case SOUP_STATUS_GONE:
183 /* FIXME: cacheable unless indicated otherwise */
184 cacheability = SOUP_CACHE_UNCACHEABLE;
187 case SOUP_STATUS_FOUND:
188 case SOUP_STATUS_TEMPORARY_REDIRECT:
189 /* FIXME: cacheable if explicitly indicated */
190 cacheability = SOUP_CACHE_UNCACHEABLE;
193 case SOUP_STATUS_SEE_OTHER:
194 case SOUP_STATUS_FORBIDDEN:
195 case SOUP_STATUS_NOT_FOUND:
196 case SOUP_STATUS_METHOD_NOT_ALLOWED:
197 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
200 /* Any 5xx status or any 4xx status not handled above
201 * is uncacheable but doesn't break the cache.
203 if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
204 msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
205 msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
206 return SOUP_CACHE_UNCACHEABLE;
208 /* An unrecognized 2xx, 3xx, or 4xx response breaks
211 if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
212 msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
213 (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
214 msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
215 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
223 soup_cache_entry_free (SoupCacheEntry *entry, gboolean purge)
226 GFile *file = g_file_new_for_path (entry->filename);
227 g_file_delete (file, NULL, NULL);
228 g_object_unref (file);
231 g_free (entry->filename);
232 entry->filename = NULL;
236 if (entry->headers) {
237 soup_message_headers_free (entry->headers);
238 entry->headers = NULL;
242 g_string_free (entry->data, TRUE);
246 g_error_free (entry->error);
249 if (entry->cancellable) {
250 g_object_unref (entry->cancellable);
251 entry->cancellable = NULL;
254 g_slice_free (SoupCacheEntry, entry);
258 copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
260 soup_message_headers_append (headers, name, value);
264 update_headers (const char *name, const char *value, SoupMessageHeaders *headers)
266 if (soup_message_headers_get (headers, name))
267 soup_message_headers_replace (headers, name, value);
269 soup_message_headers_append (headers, name, value);
273 soup_cache_entry_get_current_age (SoupCacheEntry *entry)
275 time_t now = time (NULL);
276 time_t resident_time;
278 resident_time = now - entry->response_time;
279 return entry->corrected_initial_age + resident_time;
283 soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
285 guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
286 return entry->freshness_lifetime > limit;
290 soup_message_get_cache_key (SoupMessage *msg)
292 SoupURI *uri = soup_message_get_uri (msg);
293 return soup_uri_to_string (uri, FALSE);
297 soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
299 const char *cache_control;
300 const char *expires, *date, *last_modified;
302 cache_control = soup_message_headers_get (entry->headers, "Cache-Control");
304 const char *max_age, *s_maxage;
305 gint64 freshness_lifetime = 0;
307 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
309 hash = soup_header_parse_param_list (cache_control);
311 /* Should we re-validate the entry when it goes stale */
312 entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
315 if (priv->cache_type == SOUP_CACHE_SHARED) {
316 s_maxage = g_hash_table_lookup (hash, "s-maxage");
318 freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
319 if (freshness_lifetime) {
320 /* Implies proxy-revalidate. TODO: is it true? */
321 entry->must_revalidate = TRUE;
322 soup_header_free_param_list (hash);
328 /* If 'max-age' cache directive is present, use that */
329 max_age = g_hash_table_lookup (hash, "max-age");
331 freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
333 if (freshness_lifetime) {
334 entry->freshness_lifetime = (guint)MIN (freshness_lifetime, G_MAXUINT32);
335 soup_header_free_param_list (hash);
339 soup_header_free_param_list (hash);
342 /* If the 'Expires' response header is present, use its value
343 * minus the value of the 'Date' response header
345 expires = soup_message_headers_get (entry->headers, "Expires");
346 date = soup_message_headers_get (entry->headers, "Date");
347 if (expires && date) {
348 SoupDate *expires_d, *date_d;
349 time_t expires_t, date_t;
351 expires_d = soup_date_new_from_string (expires);
353 date_d = soup_date_new_from_string (date);
355 expires_t = soup_date_to_time_t (expires_d);
356 date_t = soup_date_to_time_t (date_d);
358 soup_date_free (expires_d);
359 soup_date_free (date_d);
361 if (expires_t && date_t) {
362 entry->freshness_lifetime = (guint)MAX (expires_t - date_t, 0);
366 /* If Expires is not a valid date we should
367 treat it as already expired, see section
369 entry->freshness_lifetime = 0;
374 /* Otherwise an heuristic may be used */
376 /* Heuristics MUST NOT be used with these status codes
378 if (msg->status_code != SOUP_STATUS_OK &&
379 msg->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
380 msg->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
381 msg->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
382 msg->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
383 msg->status_code != SOUP_STATUS_GONE)
386 /* TODO: attach warning 113 if response's current_age is more
387 than 24h (section 2.3.1.1) when using heuristics */
389 /* Last-Modified based heuristic */
390 last_modified = soup_message_headers_get (entry->headers, "Last-Modified");
393 time_t now, last_modified_t;
395 soup_date = soup_date_new_from_string (last_modified);
396 last_modified_t = soup_date_to_time_t (soup_date);
399 #define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
401 entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
402 soup_date_free (soup_date);
408 /* If all else fails, make the entry expire immediately */
409 entry->freshness_lifetime = 0;
412 static SoupCacheEntry *
413 soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
415 SoupCacheEntry *entry;
416 SoupMessageHeaders *headers;
420 entry = g_slice_new0 (SoupCacheEntry);
421 entry->dirty = FALSE;
422 entry->writing = FALSE;
423 entry->got_body = FALSE;
424 entry->being_validated = FALSE;
425 entry->data = g_string_new (NULL);
430 entry->key = soup_message_get_cache_key (msg);
431 md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, entry->key, -1);
432 entry->filename = g_build_filename (cache->priv->cache_dir, md5, NULL);
436 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
437 soup_message_headers_foreach (msg->response_headers,
438 (SoupMessageHeadersForeachFunc)copy_headers,
440 entry->headers = headers;
445 /* Section 2.3.1, Freshness Lifetime */
446 soup_cache_entry_set_freshness (entry, msg, cache);
448 /* Section 2.3.2, Calculating Age */
449 date = soup_message_headers_get (entry->headers, "Date");
454 time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
456 soup_date = soup_date_new_from_string (date);
457 date_value = soup_date_to_time_t (soup_date);
458 soup_date_free (soup_date);
460 age = soup_message_headers_get (entry->headers, "Age");
462 age_value = g_ascii_strtoll (age, NULL, 10);
464 entry->response_time = response_time;
465 apparent_age = MAX (0, entry->response_time - date_value);
466 corrected_received_age = MAX (apparent_age, age_value);
467 response_delay = entry->response_time - request_time;
468 entry->corrected_initial_age = corrected_received_age + response_delay;
470 /* Is this correct ? */
471 entry->corrected_initial_age = time (NULL);
478 soup_cache_writing_fixture_free (SoupCacheWritingFixture *fixture)
480 /* Free fixture. And disconnect signals, we don't want to
481 listen to more SoupMessage events as we're finished with
483 if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler))
484 g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler);
485 if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler))
486 g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
487 if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
488 g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
489 g_object_unref (fixture->msg);
490 g_object_unref (fixture->cache);
491 g_slice_free (SoupCacheWritingFixture, fixture);
495 close_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
497 SoupCacheEntry *entry = fixture->entry;
498 SoupCache *cache = fixture->cache;
499 GOutputStream *stream = G_OUTPUT_STREAM (source);
500 goffset content_length;
502 g_warn_if_fail (entry->error == NULL);
504 /* FIXME: what do we do on error ? */
507 g_output_stream_close_finish (stream, result, NULL);
508 g_object_unref (stream);
510 entry->stream = NULL;
512 content_length = soup_message_headers_get_content_length (entry->headers);
514 /* If the process was cancelled, then delete the entry from
515 the cache. Do it also if the size of a chunked resource is
516 too much for the cache */
517 if (g_cancellable_is_cancelled (entry->cancellable)) {
518 entry->dirty = FALSE;
519 soup_cache_entry_remove (cache, entry);
520 soup_cache_entry_free (entry, TRUE);
522 } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
523 entry->length != (gsize) content_length) {
524 /** Two options here:
526 * 1. "chunked" data, entry was temporarily added to
527 * cache (as content-length is 0) and now that we have
528 * the actual size we have to evaluate if we want it
529 * in the cache or not
531 * 2. Content-Length has a different value than actual
532 * length, means that the content was encoded for
533 * transmission (typically compressed) and thus we
534 * have to substract the content-length value that was
535 * added to the cache and add the unencoded length
537 gint length_to_add = entry->length - content_length;
539 /* Make room in cache if needed */
540 if (cache_accepts_entries_of_size (cache, length_to_add)) {
541 make_room_for_new_entry (cache, length_to_add);
543 cache->priv->size += length_to_add;
545 entry->dirty = FALSE;
546 soup_cache_entry_remove (cache, entry);
547 soup_cache_entry_free (entry, TRUE);
553 /* Get rid of the GString in memory for the resource now */
555 g_string_free (entry->data, TRUE);
559 entry->dirty = FALSE;
560 entry->writing = FALSE;
561 entry->got_body = FALSE;
564 g_object_unref (entry->cancellable);
565 entry->cancellable = NULL;
568 cache->priv->n_pending--;
571 soup_cache_writing_fixture_free (fixture);
575 write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
577 GOutputStream *stream = G_OUTPUT_STREAM (source);
578 GError *error = NULL;
580 SoupCacheEntry *entry = fixture->entry;
582 if (g_cancellable_is_cancelled (entry->cancellable)) {
583 g_output_stream_close_async (stream,
586 (GAsyncReadyCallback)close_ready_cb,
591 write_size = g_output_stream_write_finish (stream, result, &error);
592 if (write_size <= 0 || error) {
594 entry->error = error;
595 g_output_stream_close_async (stream,
598 (GAsyncReadyCallback)close_ready_cb,
600 /* FIXME: We should completely stop caching the
601 resource at this point */
603 entry->pos += write_size;
605 /* Are we still writing and is there new data to write
607 if (entry->data && entry->pos < entry->data->len) {
608 g_output_stream_write_async (entry->stream,
609 entry->data->str + entry->pos,
610 entry->data->len - entry->pos,
613 (GAsyncReadyCallback)write_ready_cb,
616 entry->writing = FALSE;
618 if (entry->got_body) {
619 /* If we already received 'got-body'
620 and we have written all the data,
621 we can close the stream */
622 g_output_stream_close_async (entry->stream,
625 (GAsyncReadyCallback)close_ready_cb,
633 msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, SoupCacheWritingFixture *fixture)
635 SoupCacheEntry *entry = fixture->entry;
637 g_return_if_fail (chunk->data && chunk->length);
638 g_return_if_fail (entry);
640 /* Ignore this if the writing or appending was cancelled */
641 if (!g_cancellable_is_cancelled (entry->cancellable)) {
642 g_string_append_len (entry->data, chunk->data, chunk->length);
643 entry->length = entry->data->len;
645 if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
646 /* Quickly cancel the caching of the resource */
647 g_cancellable_cancel (entry->cancellable);
651 /* FIXME: remove the error check when we cancel the caching at
652 the first write error */
653 /* Only write if the entry stream is ready */
654 if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
655 GString *data = entry->data;
656 entry->writing = TRUE;
657 g_output_stream_write_async (entry->stream,
658 data->str + entry->pos,
659 data->len - entry->pos,
662 (GAsyncReadyCallback)write_ready_cb,
668 msg_got_body_cb (SoupMessage *msg, SoupCacheWritingFixture *fixture)
670 SoupCacheEntry *entry = fixture->entry;
671 g_return_if_fail (entry);
673 entry->got_body = TRUE;
675 if (!entry->stream && entry->pos != entry->length)
676 /* The stream is not ready to be written but we still
677 have data to write, we'll write it when the stream
678 is opened for writing */
682 if (entry->pos != entry->length) {
683 /* If we still have data to write, write it,
684 write_ready_cb will close the stream */
685 if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
686 g_output_stream_write_async (entry->stream,
687 entry->data->str + entry->pos,
688 entry->data->len - entry->pos,
691 (GAsyncReadyCallback)write_ready_cb,
697 if (entry->stream && !entry->writing)
698 g_output_stream_close_async (entry->stream,
701 (GAsyncReadyCallback)close_ready_cb,
706 soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry)
710 /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */
712 g_cancellable_cancel (entry->cancellable);
716 g_assert (!entry->dirty);
717 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
719 /* Remove from cache */
720 if (!g_hash_table_remove (cache->priv->cache, entry->key))
723 /* Remove from LRU */
724 lru_item = g_list_find (cache->priv->lru_start, entry);
725 cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
727 /* Adjust cache size */
728 cache->priv->size -= entry->length;
730 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
736 lru_compare_func (gconstpointer a, gconstpointer b)
738 SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
739 SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
741 /** The rationale of this sorting func is
743 * 1. sort by hits -> LRU algorithm, then
745 * 2. sort by freshness lifetime, we better discard first
746 * entries that are close to expire
748 * 3. sort by size, replace first small size resources as they
749 * are cheaper to download
753 if (entry_a->hits != entry_b->hits)
754 return entry_a->hits - entry_b->hits;
756 /* Sort by freshness_lifetime */
757 if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
758 return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
761 return entry_a->length - entry_b->length;
765 cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
767 /* We could add here some more heuristics. TODO: review how
768 this is done by other HTTP caches */
770 return length_to_add <= cache->priv->max_entry_data_size;
774 make_room_for_new_entry (SoupCache *cache, guint length_to_add)
776 GList *lru_entry = cache->priv->lru_start;
778 /* Check that there is enough room for the new entry. This is
779 an approximation as we're not working out the size of the
780 cache file or the size of the headers for performance
781 reasons. TODO: check if that would be really that expensive */
784 (length_to_add + cache->priv->size > cache->priv->max_size)) {
785 SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
787 /* Discard entries. Once cancelled resources will be
788 * freed in close_ready_cb
790 if (soup_cache_entry_remove (cache, old_entry)) {
791 soup_cache_entry_free (old_entry, TRUE);
792 lru_entry = cache->priv->lru_start;
794 lru_entry = g_list_next (lru_entry);
799 soup_cache_entry_insert_by_key (SoupCache *cache,
801 SoupCacheEntry *entry,
804 guint length_to_add = 0;
806 if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED)
807 length_to_add = soup_message_headers_get_content_length (entry->headers);
809 /* Check if we are going to store the resource depending on its size */
811 if (!cache_accepts_entries_of_size (cache, length_to_add))
814 /* Make room for new entry if needed */
815 make_room_for_new_entry (cache, length_to_add);
818 g_hash_table_insert (cache->priv->cache, g_strdup (key), entry);
820 /* Compute new cache size */
821 cache->priv->size += length_to_add;
825 cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
827 cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
829 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
835 msg_restarted_cb (SoupMessage *msg, SoupCacheEntry *entry)
837 /* FIXME: What should we do here exactly? */
841 replace_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
843 SoupCacheEntry *entry = fixture->entry;
844 GOutputStream *stream = (GOutputStream *) g_file_replace_finish (G_FILE (source),
845 result, &entry->error);
847 if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) {
849 g_object_unref (stream);
850 fixture->cache->priv->n_pending--;
851 entry->dirty = FALSE;
852 soup_cache_entry_remove (fixture->cache, entry);
853 soup_cache_entry_free (entry, TRUE);
854 soup_cache_writing_fixture_free (fixture);
858 entry->stream = stream;
860 /* If we already got all the data we have to initiate the
861 * writing here, since we won't get more 'got-chunk'
864 if (!entry->got_body)
867 /* It could happen that reading the data from server
868 * was completed before this happens. In that case
872 entry->writing = TRUE;
873 g_output_stream_write_async (entry->stream,
874 entry->data->str + entry->pos,
875 entry->data->len - entry->pos,
878 (GAsyncReadyCallback)write_ready_cb,
885 SoupSessionFeature *feature;
886 gulong got_headers_handler;
890 msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
893 SoupCacheability cacheable;
894 RequestHelper *helper;
895 time_t request_time, response_time;
897 response_time = time (NULL);
899 helper = (RequestHelper *)user_data;
900 cache = SOUP_CACHE (helper->feature);
901 request_time = helper->request_time;
902 g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
903 g_slice_free (RequestHelper, helper);
905 cacheable = soup_cache_get_cacheability (cache, msg);
907 if (cacheable & SOUP_CACHE_CACHEABLE) {
908 SoupCacheEntry *entry;
911 SoupCacheWritingFixture *fixture;
913 /* Check if we are already caching this resource */
914 key = soup_message_get_cache_key (msg);
915 entry = g_hash_table_lookup (cache->priv->cache, key);
918 if (entry && entry->dirty)
921 /* Create a new entry, deleting any old one if present */
923 soup_cache_entry_remove (cache, entry);
924 soup_cache_entry_free (entry, TRUE);
927 entry = soup_cache_entry_new (cache, msg, request_time, response_time);
930 /* Do not continue if it can not be stored */
931 if (!soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, TRUE)) {
932 soup_cache_entry_free (entry, TRUE);
936 fixture = g_slice_new0 (SoupCacheWritingFixture);
937 fixture->cache = g_object_ref (cache);
938 fixture->entry = entry;
939 fixture->msg = g_object_ref (msg);
941 /* We connect now to these signals and buffer the data
942 if it comes before the file is ready for writing */
943 fixture->got_chunk_handler =
944 g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture);
945 fixture->got_body_handler =
946 g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture);
947 fixture->restarted_handler =
948 g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry);
951 file = g_file_new_for_path (entry->filename);
952 cache->priv->n_pending++;
955 entry->cancellable = g_cancellable_new ();
956 g_file_replace_async (file, NULL, FALSE,
957 G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
958 G_PRIORITY_LOW, entry->cancellable,
959 (GAsyncReadyCallback) replace_cb, fixture);
960 g_object_unref (file);
961 } else if (cacheable & SOUP_CACHE_INVALIDATES) {
963 SoupCacheEntry *entry;
965 key = soup_message_get_cache_key (msg);
966 entry = g_hash_table_lookup (cache->priv->cache, key);
970 if (soup_cache_entry_remove (cache, entry))
971 soup_cache_entry_free (entry, TRUE);
973 } else if (cacheable & SOUP_CACHE_VALIDATES) {
975 SoupCacheEntry *entry;
977 key = soup_message_get_cache_key (msg);
978 entry = g_hash_table_lookup (cache->priv->cache, key);
981 /* It's possible to get a CACHE_VALIDATES with no
982 * entry in the hash table. This could happen if for
983 * example the soup client is the one creating the
984 * conditional request.
987 entry->being_validated = FALSE;
989 /* We update the headers of the existing cache item,
991 soup_message_headers_foreach (msg->response_headers,
992 (SoupMessageHeadersForeachFunc)update_headers,
994 soup_cache_entry_set_freshness (entry, msg, cache);
1000 soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
1003 SoupCacheEntry *entry;
1005 GInputStream *stream = NULL;
1008 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1009 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
1011 key = soup_message_get_cache_key (msg);
1012 entry = g_hash_table_lookup (cache->priv->cache, key);
1014 g_return_val_if_fail (entry, NULL);
1016 /* If we are told to send a response from cache any validation
1017 in course is over by now */
1018 entry->being_validated = FALSE;
1021 soup_message_headers_foreach (entry->headers,
1022 (SoupMessageHeadersForeachFunc)update_headers,
1023 msg->response_headers);
1025 /* Add 'Age' header with the current age */
1026 current_age = g_strdup_printf ("%d", soup_cache_entry_get_current_age (entry));
1027 soup_message_headers_replace (msg->response_headers,
1030 g_free (current_age);
1032 /* TODO: the original idea was to save reads, but current code
1033 assumes that a stream is always returned. Need to reach
1034 some agreement here. Also we have to handle the situation
1035 were the file was no longer there (for example files
1036 removed without notifying the cache */
1037 file = g_file_new_for_path (entry->filename);
1038 stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
1039 g_object_unref (file);
1045 request_started (SoupSessionFeature *feature, SoupSession *session,
1046 SoupMessage *msg, SoupSocket *socket)
1048 RequestHelper *helper = g_slice_new0 (RequestHelper);
1049 helper->request_time = time (NULL);
1050 helper->feature = feature;
1051 helper->got_headers_handler = g_signal_connect (msg, "got-headers",
1052 G_CALLBACK (msg_got_headers_cb),
1057 attach (SoupSessionFeature *feature, SoupSession *session)
1059 SoupCache *cache = SOUP_CACHE (feature);
1060 cache->priv->session = session;
1062 soup_cache_default_feature_interface->attach (feature, session);
1066 soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
1067 gpointer interface_data)
1069 soup_cache_default_feature_interface =
1070 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
1072 feature_interface->attach = attach;
1073 feature_interface->request_started = request_started;
1077 soup_cache_init (SoupCache *cache)
1079 SoupCachePrivate *priv;
1081 priv = cache->priv = SOUP_CACHE_GET_PRIVATE (cache);
1083 priv->cache = g_hash_table_new_full (g_str_hash,
1085 (GDestroyNotify)g_free,
1089 priv->lru_start = NULL;
1092 priv->n_pending = 0;
1095 priv->max_size = DEFAULT_MAX_SIZE;
1096 priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1101 remove_cache_item (gpointer data,
1104 SoupCache *cache = (SoupCache *) user_data;
1105 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1107 if (soup_cache_entry_remove (cache, entry))
1108 soup_cache_entry_free (entry, FALSE);
1112 soup_cache_finalize (GObject *object)
1114 SoupCachePrivate *priv;
1117 priv = SOUP_CACHE (object)->priv;
1119 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1120 entries = g_hash_table_get_values (priv->cache);
1121 g_list_foreach (entries, remove_cache_item, object);
1122 g_list_free (entries);
1124 g_hash_table_destroy (priv->cache);
1125 g_free (priv->cache_dir);
1127 g_list_free (priv->lru_start);
1128 priv->lru_start = NULL;
1130 G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
1134 soup_cache_set_property (GObject *object, guint prop_id,
1135 const GValue *value, GParamSpec *pspec)
1137 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1140 case PROP_CACHE_DIR:
1141 priv->cache_dir = g_value_dup_string (value);
1142 /* Create directory if it does not exist (FIXME: should we?) */
1143 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1144 g_mkdir_with_parents (priv->cache_dir, 0700);
1146 case PROP_CACHE_TYPE:
1147 priv->cache_type = g_value_get_enum (value);
1148 /* TODO: clear private entries and issue a warning if moving to shared? */
1151 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1157 soup_cache_get_property (GObject *object, guint prop_id,
1158 GValue *value, GParamSpec *pspec)
1160 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1163 case PROP_CACHE_DIR:
1164 g_value_set_string (value, priv->cache_dir);
1166 case PROP_CACHE_TYPE:
1167 g_value_set_enum (value, priv->cache_type);
1170 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1176 soup_cache_constructed (GObject *object)
1178 SoupCachePrivate *priv;
1180 priv = SOUP_CACHE (object)->priv;
1182 if (!priv->cache_dir) {
1183 /* Set a default cache dir, different for each user */
1184 priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
1187 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1188 g_mkdir_with_parents (priv->cache_dir, 0700);
1191 if (G_OBJECT_CLASS (soup_cache_parent_class)->constructed)
1192 G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
1196 soup_cache_class_init (SoupCacheClass *cache_class)
1198 GObjectClass *gobject_class = (GObjectClass *)cache_class;
1200 gobject_class->finalize = soup_cache_finalize;
1201 gobject_class->constructed = soup_cache_constructed;
1202 gobject_class->set_property = soup_cache_set_property;
1203 gobject_class->get_property = soup_cache_get_property;
1205 cache_class->get_cacheability = get_cacheability;
1207 g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
1208 g_param_spec_string ("cache-dir",
1210 "The directory to store the cache files",
1212 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1214 g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
1215 g_param_spec_enum ("cache-type",
1217 "Whether the cache is private or shared",
1218 SOUP_TYPE_CACHE_TYPE,
1219 SOUP_CACHE_SINGLE_USER,
1220 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1222 g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
1227 * @cache_dir: the directory to store the cached data, or %NULL to use the default one
1228 * @cache_type: the #SoupCacheType of the cache
1230 * Creates a new #SoupCache.
1232 * Returns: a new #SoupCache
1237 soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1239 return g_object_new (SOUP_TYPE_CACHE,
1240 "cache-dir", cache_dir,
1241 "cache-type", cache_type,
1246 * soup_cache_has_response:
1247 * @cache: a #SoupCache
1248 * @msg: a #SoupMessage
1250 * This function calculates whether the @cache object has a proper
1251 * response for the request @msg given the flags both in the request
1252 * and the cached reply and the time ellapsed since it was cached.
1254 * Returns: whether or not the @cache has a valid response for @msg
1257 soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1260 SoupCacheEntry *entry;
1261 const char *cache_control, *pragma;
1263 int max_age, max_stale, min_fresh;
1264 GList *lru_item, *item;
1266 key = soup_message_get_cache_key (msg);
1267 entry = g_hash_table_lookup (cache->priv->cache, key);
1270 /* 1. The presented Request-URI and that of stored response
1274 return SOUP_CACHE_RESPONSE_STALE;
1276 /* Increase hit count. Take sorting into account */
1278 lru_item = g_list_find (cache->priv->lru_start, entry);
1280 while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1281 item = g_list_next (item);
1283 if (item != lru_item) {
1284 cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
1285 item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1286 g_list_free (lru_item);
1289 if (entry->dirty || entry->being_validated)
1290 return SOUP_CACHE_RESPONSE_STALE;
1292 /* 2. The request method associated with the stored response
1293 * allows it to be used for the presented request
1296 /* In practice this means we only return our resource for GET,
1297 * cacheability for other methods is a TODO in the RFC
1298 * (TODO: although we could return the headers for HEAD
1301 if (msg->method != SOUP_METHOD_GET)
1302 return SOUP_CACHE_RESPONSE_STALE;
1304 /* 3. Selecting request-headers nominated by the stored
1305 * response (if any) match those presented.
1310 /* 4. The request is a conditional request issued by the client.
1312 if (soup_message_headers_get (msg->request_headers, "If-Modified-Since") ||
1313 soup_message_headers_get (msg->request_headers, "If-None-Match"))
1314 return SOUP_CACHE_RESPONSE_STALE;
1316 /* 5. The presented request and stored response are free from
1317 * directives that would prevent its use.
1320 max_age = max_stale = min_fresh = -1;
1322 /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1324 pragma = soup_message_headers_get (msg->request_headers, "Pragma");
1325 if (pragma && soup_header_contains (pragma, "no-cache"))
1326 return SOUP_CACHE_RESPONSE_STALE;
1328 cache_control = soup_message_headers_get (msg->request_headers, "Cache-Control");
1329 if (cache_control) {
1330 GHashTable *hash = soup_header_parse_param_list (cache_control);
1332 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1333 soup_header_free_param_list (hash);
1334 return SOUP_CACHE_RESPONSE_STALE;
1337 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1338 soup_header_free_param_list (hash);
1339 return SOUP_CACHE_RESPONSE_STALE;
1342 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
1343 max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1344 /* Forcing cache revalidaton
1347 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1350 /* max-stale can have no value set, we need to use _extended */
1351 if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1353 max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1355 max_stale = G_MAXINT32;
1358 value = g_hash_table_lookup (hash, "min-fresh");
1360 min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1362 soup_header_free_param_list (hash);
1365 guint current_age = soup_cache_entry_get_current_age (entry);
1367 /* If we are over max-age and max-stale is not
1368 set, do not use the value from the cache
1369 without validation */
1370 if ((guint) max_age <= current_age && max_stale == -1)
1371 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1375 /* 6. The stored response is either: fresh, allowed to be
1376 * served stale or succesfully validated
1378 /* TODO consider also proxy-revalidate & s-maxage */
1379 if (entry->must_revalidate)
1380 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1382 if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1383 /* Not fresh, can it be served stale? */
1384 if (max_stale != -1) {
1385 /* G_MAXINT32 means we accept any staleness */
1386 if (max_stale == G_MAXINT32)
1387 return SOUP_CACHE_RESPONSE_FRESH;
1389 if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1390 return SOUP_CACHE_RESPONSE_FRESH;
1393 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1396 return SOUP_CACHE_RESPONSE_FRESH;
1400 * soup_cache_get_cacheability:
1401 * @cache: a #SoupCache
1402 * @msg: a #SoupMessage
1404 * Calculates whether the @msg can be cached or not.
1406 * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
1409 soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1411 g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1412 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1414 return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1418 force_flush_timeout (gpointer data)
1420 gboolean *forced = (gboolean *)data;
1428 * @cache: a #SoupCache
1429 * @session: the #SoupSession associated with the @cache
1431 * This function will force all pending writes in the @cache to be
1432 * committed to disk. For doing so it will iterate the #GMainContext
1433 * associated with the @session (which can be the default one) as long
1437 soup_cache_flush (SoupCache *cache)
1439 GMainContext *async_context;
1440 SoupSession *session;
1442 gboolean forced = FALSE;
1444 g_return_if_fail (SOUP_IS_CACHE (cache));
1446 session = cache->priv->session;
1447 g_return_if_fail (SOUP_IS_SESSION (session));
1448 async_context = soup_session_get_async_context (session);
1450 /* We give cache 10 secs to finish */
1451 timeout_id = g_timeout_add (10000, force_flush_timeout, &forced);
1453 while (!forced && cache->priv->n_pending > 0)
1454 g_main_context_iteration (async_context, FALSE);
1457 g_source_remove (timeout_id);
1459 g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
1463 clear_cache_item (gpointer data,
1466 SoupCache *cache = (SoupCache *) user_data;
1467 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1469 if (soup_cache_entry_remove (cache, entry))
1470 soup_cache_entry_free (entry, TRUE);
1475 * @cache: a #SoupCache
1477 * Will remove all entries in the @cache plus all the cache files
1478 * associated with them.
1481 soup_cache_clear (SoupCache *cache)
1486 g_return_if_fail (SOUP_IS_CACHE (cache));
1488 hash = cache->priv->cache;
1489 g_return_if_fail (hash);
1491 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1492 entries = g_hash_table_get_values (hash);
1493 g_list_foreach (entries, clear_cache_item, cache);
1494 g_list_free (entries);
1498 soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1502 SoupCacheEntry *entry;
1506 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1507 g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1509 /* First copy the data we need from the original message */
1510 uri = soup_message_get_uri (original);
1511 msg = soup_message_new_from_uri (original->method, uri);
1513 soup_message_headers_foreach (original->request_headers,
1514 (SoupMessageHeadersForeachFunc)copy_headers,
1515 msg->request_headers);
1517 /* Now add the validator entries in the header from the cached
1519 key = soup_message_get_cache_key (original);
1520 entry = g_hash_table_lookup (cache->priv->cache, key);
1523 g_return_val_if_fail (entry, NULL);
1525 entry->being_validated = TRUE;
1527 value = soup_message_headers_get (entry->headers, "Last-Modified");
1529 soup_message_headers_append (msg->request_headers,
1530 "If-Modified-Since",
1532 value = soup_message_headers_get (entry->headers, "ETag");
1534 soup_message_headers_append (msg->request_headers,
1540 #define SOUP_CACHE_FILE "soup.cache"
1542 #define SOUP_CACHE_HEADERS_FORMAT "{ss}"
1543 #define SOUP_CACHE_PHEADERS_FORMAT "(ssbuuuuua" SOUP_CACHE_HEADERS_FORMAT ")"
1544 #define SOUP_CACHE_ENTRIES_FORMAT "a" SOUP_CACHE_PHEADERS_FORMAT
1546 /* Basically the same format than above except that some strings are
1547 prepended with &. This way the GVariant returns a pointer to the
1548 data instead of duplicating the string */
1549 #define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
1552 pack_entry (gpointer data,
1555 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1556 SoupMessageHeadersIter iter;
1557 const gchar *header_key, *header_value;
1558 GVariantBuilder *headers_builder;
1559 GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1561 /* Do not store non-consolidated entries */
1562 if (entry->dirty || entry->writing || !entry->key)
1566 headers_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
1567 soup_message_headers_iter_init (&iter, entry->headers);
1568 while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1569 if (g_utf8_validate (header_value, -1, NULL))
1570 g_variant_builder_add (headers_builder, SOUP_CACHE_HEADERS_FORMAT,
1571 header_key, header_value);
1575 g_variant_builder_add (entries_builder, SOUP_CACHE_PHEADERS_FORMAT,
1576 entry->key, entry->filename, entry->must_revalidate,
1577 entry->freshness_lifetime, entry->corrected_initial_age,
1578 entry->response_time, entry->hits, entry->length, headers_builder);
1580 g_variant_builder_unref (headers_builder);
1584 soup_cache_dump (SoupCache *cache)
1586 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
1588 GVariantBuilder *entries_builder;
1589 GVariant *cache_variant;
1591 if (!g_list_length (cache->priv->lru_start))
1594 /* Create the builder and iterate over all entries */
1595 entries_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
1596 g_list_foreach (cache->priv->lru_start, pack_entry, entries_builder);
1598 /* Serialize and dump */
1599 cache_variant = g_variant_new (SOUP_CACHE_ENTRIES_FORMAT, entries_builder);
1600 g_variant_builder_unref (entries_builder);
1602 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1603 g_file_set_contents (filename, (const gchar *)g_variant_get_data (cache_variant),
1604 g_variant_get_size (cache_variant), NULL);
1606 g_variant_unref (cache_variant);
1610 soup_cache_load (SoupCache *cache)
1612 gchar *filename = NULL, *contents = NULL;
1613 GVariant *cache_variant;
1614 GVariantIter *entries_iter, *headers_iter;
1615 GVariantType *variant_format;
1617 SoupCacheEntry *entry;
1618 SoupCachePrivate *priv = cache->priv;
1620 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1621 if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1628 variant_format = g_variant_type_new (SOUP_CACHE_ENTRIES_FORMAT);
1629 cache_variant = g_variant_new_from_data (variant_format, (const gchar *)contents, length, FALSE, g_free, contents);
1630 g_variant_type_free (variant_format);
1632 g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &entries_iter);
1633 entry = g_slice_new0 (SoupCacheEntry);
1635 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1636 &entry->key, &entry->filename, &entry->must_revalidate,
1637 &entry->freshness_lifetime, &entry->corrected_initial_age,
1638 &entry->response_time, &entry->hits, &entry->length,
1640 const gchar *header_key, *header_value;
1642 /* SoupMessage Headers */
1643 entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1644 while (g_variant_iter_loop (headers_iter, SOUP_CACHE_DECODE_HEADERS_FORMAT, &header_key, &header_value))
1645 soup_message_headers_append (entry->headers, header_key, header_value);
1647 /* Insert in cache */
1648 if (!soup_cache_entry_insert_by_key (cache, (const gchar *)entry->key, entry, FALSE))
1649 soup_cache_entry_free (entry, TRUE);
1651 /* New entry for the next iteration. This creates an
1652 extra object the last iteration but it's worth it
1653 as we save several if's */
1654 entry = g_slice_new0 (SoupCacheEntry);
1656 /* Remove last created entry */
1657 g_slice_free (SoupCacheEntry, entry);
1659 /* Sort LRU (shouldn't be needed). First reverse as elements
1660 * are always prepended when inserting
1662 cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
1663 cache->priv->lru_start = g_list_sort (cache->priv->lru_start, lru_compare_func);
1666 g_variant_iter_free (entries_iter);
1667 g_variant_unref (cache_variant);
1671 soup_cache_set_max_size (SoupCache *cache,
1674 cache->priv->max_size = max_size;
1675 cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1679 soup_cache_get_max_size (SoupCache *cache)
1681 return cache->priv->max_size;