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.
34 #include "soup-cache.h"
35 #include "soup-body-input-stream.h"
36 #include "soup-cache-input-stream.h"
37 #include "soup-cache-private.h"
38 #include "soup-content-processor.h"
39 #include "soup-message-private.h"
41 #include "soup-message-private.h"
45 * @short_description: Caching support
47 * #SoupCache implements a file-based cache for HTTP resources.
50 static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
51 static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
53 static SoupContentProcessorInterface *soup_cache_default_content_processor_interface;
54 static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
56 #define DEFAULT_MAX_SIZE 50 * 1024 * 1024
57 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
58 of the cache that can be
59 filled by a single entry */
62 * Version 2: cache is now saved in soup.cache2. Added the version
63 * number to the beginning of the file.
65 * Version 3: added HTTP status code to the cache entries.
67 * Version 4: replaced several types.
68 * - freshness_lifetime,corrected_initial_age,response_time: time_t -> guint32
69 * - status_code: guint -> guint16
70 * - hits: guint -> guint32
72 * Version 5: key is no longer stored on disk as it can be easily
73 * built from the URI. Apart from that some fields in the
74 * SoupCacheEntry have changed:
75 * - entry key is now a uint32 instead of a (char *).
76 * - added uri, used to check for collisions
77 * - removed filename, it's built from the entry key.
79 #define SOUP_CACHE_CURRENT_VERSION 5
81 #define OLD_SOUP_CACHE_FILE "soup.cache"
82 #define SOUP_CACHE_FILE "soup.cache2"
84 #define SOUP_CACHE_HEADERS_FORMAT "{ss}"
85 #define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
86 #define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
88 /* Basically the same format than above except that some strings are
89 prepended with &. This way the GVariant returns a pointer to the
90 data instead of duplicating the string */
91 #define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
94 typedef struct _SoupCacheEntry {
97 guint32 freshness_lifetime;
98 gboolean must_revalidate;
100 guint32 corrected_initial_age;
101 guint32 response_time;
103 gboolean being_validated;
104 SoupMessageHeaders *headers;
106 GCancellable *cancellable;
110 struct _SoupCachePrivate {
114 SoupSession *session;
115 SoupCacheType cache_type;
118 guint max_entry_data_size; /* Computed value. Here for performance reasons */
128 #define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
130 G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
131 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
132 soup_cache_session_feature_init)
133 G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
134 soup_cache_content_processor_init))
136 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge);
137 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
138 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
141 get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
143 char *filename = g_strdup_printf ("%s%s%u", cache->priv->cache_dir,
144 G_DIR_SEPARATOR_S, (guint) entry->key);
145 GFile *file = g_file_new_for_path (filename);
151 static SoupCacheability
152 get_cacheability (SoupCache *cache, SoupMessage *msg)
154 SoupCacheability cacheability;
155 const char *cache_control, *content_type;
156 gboolean has_max_age = FALSE;
158 /* 1. The request method must be cacheable */
159 if (msg->method == SOUP_METHOD_GET)
160 cacheability = SOUP_CACHE_CACHEABLE;
161 else if (msg->method == SOUP_METHOD_HEAD ||
162 msg->method == SOUP_METHOD_TRACE ||
163 msg->method == SOUP_METHOD_CONNECT)
164 return SOUP_CACHE_UNCACHEABLE;
166 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
168 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
169 if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
170 return SOUP_CACHE_UNCACHEABLE;
172 cache_control = soup_message_headers_get_list (msg->response_headers, "Cache-Control");
173 if (cache_control && *cache_control) {
175 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
177 hash = soup_header_parse_param_list (cache_control);
179 /* Shared caches MUST NOT store private resources */
180 if (priv->cache_type == SOUP_CACHE_SHARED) {
181 if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
182 soup_header_free_param_list (hash);
183 return SOUP_CACHE_UNCACHEABLE;
187 /* 2. The 'no-store' cache directive does not appear in the
190 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
191 soup_header_free_param_list (hash);
192 return SOUP_CACHE_UNCACHEABLE;
195 if (g_hash_table_lookup_extended (hash, "max-age", NULL, NULL))
198 /* This does not appear in section 2.1, but I think it makes
199 * sense to check it too?
201 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
202 soup_header_free_param_list (hash);
203 return SOUP_CACHE_UNCACHEABLE;
206 soup_header_free_param_list (hash);
210 if ((soup_message_get_uri (msg))->query &&
211 !soup_message_headers_get_one (msg->response_headers, "Expires") &&
213 return SOUP_CACHE_UNCACHEABLE;
215 switch (msg->status_code) {
216 case SOUP_STATUS_PARTIAL_CONTENT:
217 /* We don't cache partial responses, but they only
218 * invalidate cached full responses if the headers
221 cacheability = SOUP_CACHE_UNCACHEABLE;
224 case SOUP_STATUS_NOT_MODIFIED:
225 /* A 304 response validates an existing cache entry */
226 cacheability = SOUP_CACHE_VALIDATES;
229 case SOUP_STATUS_MULTIPLE_CHOICES:
230 case SOUP_STATUS_MOVED_PERMANENTLY:
231 case SOUP_STATUS_GONE:
232 /* FIXME: cacheable unless indicated otherwise */
233 cacheability = SOUP_CACHE_UNCACHEABLE;
236 case SOUP_STATUS_FOUND:
237 case SOUP_STATUS_TEMPORARY_REDIRECT:
238 /* FIXME: cacheable if explicitly indicated */
239 cacheability = SOUP_CACHE_UNCACHEABLE;
242 case SOUP_STATUS_SEE_OTHER:
243 case SOUP_STATUS_FORBIDDEN:
244 case SOUP_STATUS_NOT_FOUND:
245 case SOUP_STATUS_METHOD_NOT_ALLOWED:
246 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
249 /* Any 5xx status or any 4xx status not handled above
250 * is uncacheable but doesn't break the cache.
252 if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
253 msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
254 msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
255 return SOUP_CACHE_UNCACHEABLE;
257 /* An unrecognized 2xx, 3xx, or 4xx response breaks
260 if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
261 msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
262 (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
263 msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
264 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
271 /* NOTE: this function deletes the file pointed by the file argument
272 * and also unref's the GFile object representing it.
275 soup_cache_entry_free (SoupCacheEntry *entry)
278 g_clear_pointer (&entry->headers, soup_message_headers_free);
279 g_clear_object (&entry->cancellable);
281 g_slice_free (SoupCacheEntry, entry);
285 copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
287 soup_message_headers_append (headers, name, value);
291 remove_headers (const char *name, const char *value, SoupMessageHeaders *headers)
293 soup_message_headers_remove (headers, name);
296 static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
299 copy_end_to_end_headers (SoupMessageHeaders *source, SoupMessageHeaders *destination)
303 soup_message_headers_foreach (source, (SoupMessageHeadersForeachFunc) copy_headers, destination);
304 for (i = 0; i < G_N_ELEMENTS (hop_by_hop_headers); i++)
305 soup_message_headers_remove (destination, hop_by_hop_headers[i]);
306 soup_message_headers_clean_connection_headers (destination);
310 soup_cache_entry_get_current_age (SoupCacheEntry *entry)
312 time_t now = time (NULL);
313 time_t resident_time;
315 resident_time = now - entry->response_time;
316 return entry->corrected_initial_age + resident_time;
320 soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
322 guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
323 return entry->freshness_lifetime > limit;
326 static inline guint32
327 get_cache_key_from_uri (const char *uri)
329 return (guint32) g_str_hash (uri);
333 soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
335 const char *cache_control;
336 const char *expires, *date, *last_modified;
338 /* Reset these values. We have to do this to ensure that
339 * revalidations overwrite previous values for the headers.
341 entry->must_revalidate = FALSE;
342 entry->freshness_lifetime = 0;
344 cache_control = soup_message_headers_get_list (entry->headers, "Cache-Control");
345 if (cache_control && *cache_control) {
346 const char *max_age, *s_maxage;
347 gint64 freshness_lifetime = 0;
349 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
351 hash = soup_header_parse_param_list (cache_control);
353 /* Should we re-validate the entry when it goes stale */
354 entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
357 if (priv->cache_type == SOUP_CACHE_SHARED) {
358 s_maxage = g_hash_table_lookup (hash, "s-maxage");
360 freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
361 if (freshness_lifetime) {
362 /* Implies proxy-revalidate. TODO: is it true? */
363 entry->must_revalidate = TRUE;
364 soup_header_free_param_list (hash);
370 /* If 'max-age' cache directive is present, use that */
371 max_age = g_hash_table_lookup (hash, "max-age");
373 freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
375 if (freshness_lifetime) {
376 entry->freshness_lifetime = (guint32) MIN (freshness_lifetime, G_MAXUINT32);
377 soup_header_free_param_list (hash);
381 soup_header_free_param_list (hash);
384 /* If the 'Expires' response header is present, use its value
385 * minus the value of the 'Date' response header
387 expires = soup_message_headers_get_one (entry->headers, "Expires");
388 date = soup_message_headers_get_one (entry->headers, "Date");
389 if (expires && date) {
390 SoupDate *expires_d, *date_d;
391 time_t expires_t, date_t;
393 expires_d = soup_date_new_from_string (expires);
395 date_d = soup_date_new_from_string (date);
397 expires_t = soup_date_to_time_t (expires_d);
398 date_t = soup_date_to_time_t (date_d);
400 soup_date_free (expires_d);
401 soup_date_free (date_d);
403 if (expires_t && date_t) {
404 entry->freshness_lifetime = (guint32) MAX (expires_t - date_t, 0);
408 /* If Expires is not a valid date we should
409 treat it as already expired, see section
411 entry->freshness_lifetime = 0;
416 /* Otherwise an heuristic may be used */
418 /* Heuristics MUST NOT be used with stored responses with
419 these status codes (section 2.3.1.1) */
420 if (entry->status_code != SOUP_STATUS_OK &&
421 entry->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
422 entry->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
423 entry->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
424 entry->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
425 entry->status_code != SOUP_STATUS_GONE)
428 /* TODO: attach warning 113 if response's current_age is more
429 than 24h (section 2.3.1.1) when using heuristics */
431 /* Last-Modified based heuristic */
432 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
435 time_t now, last_modified_t;
437 soup_date = soup_date_new_from_string (last_modified);
438 last_modified_t = soup_date_to_time_t (soup_date);
441 #define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
443 entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
444 soup_date_free (soup_date);
450 /* If all else fails, make the entry expire immediately */
451 entry->freshness_lifetime = 0;
454 static SoupCacheEntry *
455 soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
457 SoupCacheEntry *entry;
460 entry = g_slice_new0 (SoupCacheEntry);
461 entry->dirty = FALSE;
462 entry->being_validated = FALSE;
463 entry->status_code = msg->status_code;
464 entry->response_time = response_time;
465 entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
468 entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
469 copy_end_to_end_headers (msg->response_headers, entry->headers);
474 /* Section 2.3.1, Freshness Lifetime */
475 soup_cache_entry_set_freshness (entry, msg, cache);
477 /* Section 2.3.2, Calculating Age */
478 date = soup_message_headers_get_one (entry->headers, "Date");
483 time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
485 soup_date = soup_date_new_from_string (date);
486 date_value = soup_date_to_time_t (soup_date);
487 soup_date_free (soup_date);
489 age = soup_message_headers_get_one (entry->headers, "Age");
491 age_value = g_ascii_strtoll (age, NULL, 10);
493 apparent_age = MAX (0, entry->response_time - date_value);
494 corrected_received_age = MAX (apparent_age, age_value);
495 response_delay = entry->response_time - request_time;
496 entry->corrected_initial_age = corrected_received_age + response_delay;
498 /* Is this correct ? */
499 entry->corrected_initial_age = time (NULL);
506 soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge)
511 g_cancellable_cancel (entry->cancellable);
515 g_assert (!entry->dirty);
516 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
518 if (!g_hash_table_remove (cache->priv->cache, GUINT_TO_POINTER (entry->key)))
521 /* Remove from LRU */
522 lru_item = g_list_find (cache->priv->lru_start, entry);
523 cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
525 /* Adjust cache size */
526 cache->priv->size -= entry->length;
528 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
532 GFile *file = get_file_from_entry (cache, entry);
533 g_file_delete (file, NULL, NULL);
534 g_object_unref (file);
536 soup_cache_entry_free (entry);
542 lru_compare_func (gconstpointer a, gconstpointer b)
544 SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
545 SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
547 /* The rationale of this sorting func is
549 * 1. sort by hits -> LRU algorithm, then
551 * 2. sort by freshness lifetime, we better discard first
552 * entries that are close to expire
554 * 3. sort by size, replace first small size resources as they
555 * are cheaper to download
559 if (entry_a->hits != entry_b->hits)
560 return entry_a->hits - entry_b->hits;
562 /* Sort by freshness_lifetime */
563 if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
564 return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
567 return entry_a->length - entry_b->length;
571 cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
573 /* We could add here some more heuristics. TODO: review how
574 this is done by other HTTP caches */
576 return length_to_add <= cache->priv->max_entry_data_size;
580 make_room_for_new_entry (SoupCache *cache, guint length_to_add)
582 GList *lru_entry = cache->priv->lru_start;
584 /* Check that there is enough room for the new entry. This is
585 an approximation as we're not working out the size of the
586 cache file or the size of the headers for performance
587 reasons. TODO: check if that would be really that expensive */
590 (length_to_add + cache->priv->size > cache->priv->max_size)) {
591 SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
593 /* Discard entries. Once cancelled resources will be
594 * freed in close_ready_cb
596 if (soup_cache_entry_remove (cache, old_entry, TRUE))
597 lru_entry = cache->priv->lru_start;
599 lru_entry = g_list_next (lru_entry);
604 soup_cache_entry_insert (SoupCache *cache,
605 SoupCacheEntry *entry,
608 guint length_to_add = 0;
609 SoupCacheEntry *old_entry;
612 entry->key = get_cache_key_from_uri ((const char *) entry->uri);
614 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
615 length_to_add = soup_message_headers_get_content_length (entry->headers);
617 /* Check if we are going to store the resource depending on its size */
619 if (!cache_accepts_entries_of_size (cache, length_to_add))
622 /* Make room for new entry if needed */
623 make_room_for_new_entry (cache, length_to_add);
626 /* Remove any previous entry */
627 if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
628 if (!soup_cache_entry_remove (cache, old_entry, TRUE))
632 /* Add to hash table */
633 g_hash_table_insert (cache->priv->cache, GUINT_TO_POINTER (entry->key), entry);
635 /* Compute new cache size */
636 cache->priv->size += length_to_add;
640 cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
642 cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
644 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
649 static SoupCacheEntry*
650 soup_cache_entry_lookup (SoupCache *cache,
653 SoupCacheEntry *entry;
657 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
658 key = get_cache_key_from_uri ((const char *) uri);
660 entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (key));
662 if (entry != NULL && (strcmp (entry->uri, uri) != 0))
670 soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
672 SoupCacheEntry *entry;
674 GInputStream *file_stream, *body_stream, *cache_stream;
677 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
678 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
680 entry = soup_cache_entry_lookup (cache, msg);
681 g_return_val_if_fail (entry, NULL);
683 file = get_file_from_entry (cache, entry);
684 file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
685 g_object_unref (file);
687 /* Do not change the original message if there is no resource */
691 body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length);
692 g_object_unref (file_stream);
697 /* If we are told to send a response from cache any validation
698 in course is over by now */
699 entry->being_validated = FALSE;
702 soup_message_set_status (msg, entry->status_code);
705 copy_end_to_end_headers (entry->headers, msg->response_headers);
707 /* Add 'Age' header with the current age */
708 current_age = g_strdup_printf ("%d", soup_cache_entry_get_current_age (entry));
709 soup_message_headers_replace (msg->response_headers,
712 g_free (current_age);
714 /* Create the cache stream. */
715 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
716 cache_stream = soup_message_setup_body_istream (body_stream, msg,
717 cache->priv->session,
718 SOUP_STAGE_ENTITY_BODY);
719 g_object_unref (body_stream);
725 msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
727 g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL)));
728 g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
732 request_started (SoupSessionFeature *feature, SoupSession *session,
733 SoupMessage *msg, SoupSocket *socket)
735 g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL)));
736 g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), NULL);
740 attach (SoupSessionFeature *feature, SoupSession *session)
742 SoupCache *cache = SOUP_CACHE (feature);
743 cache->priv->session = session;
745 soup_cache_default_feature_interface->attach (feature, session);
749 soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
750 gpointer interface_data)
752 soup_cache_default_feature_interface =
753 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
755 feature_interface->attach = attach;
756 feature_interface->request_started = request_started;
761 SoupCacheEntry *entry;
765 istream_caching_finished (SoupCacheInputStream *istream,
770 StreamHelper *helper = (StreamHelper *) user_data;
771 SoupCache *cache = helper->cache;
772 SoupCacheEntry *entry = helper->entry;
774 --cache->priv->n_pending;
776 entry->dirty = FALSE;
777 entry->length = bytes_written;
778 g_clear_object (&entry->cancellable);
781 /* Update cache size */
782 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
783 cache->priv->size -= soup_message_headers_get_content_length (entry->headers);
785 soup_cache_entry_remove (cache, entry, TRUE);
786 helper->entry = entry = NULL;
790 if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) {
792 if (cache_accepts_entries_of_size (cache, entry->length)) {
793 make_room_for_new_entry (cache, entry->length);
794 cache->priv->size += entry->length;
796 soup_cache_entry_remove (cache, entry, TRUE);
797 helper->entry = entry = NULL;
802 g_object_unref (helper->cache);
803 g_slice_free (StreamHelper, helper);
807 soup_cache_content_processor_wrap_input (SoupContentProcessor *processor,
808 GInputStream *base_stream,
812 SoupCache *cache = (SoupCache*) processor;
813 SoupCacheEntry *entry;
814 SoupCacheability cacheability;
815 GInputStream *istream;
817 StreamHelper *helper;
818 time_t request_time, response_time;
820 /* First of all, check if we should cache the resource. */
821 cacheability = soup_cache_get_cacheability (cache, msg);
822 entry = soup_cache_entry_lookup (cache, msg);
824 if (cacheability & SOUP_CACHE_INVALIDATES) {
826 soup_cache_entry_remove (cache, entry, TRUE);
830 if (cacheability & SOUP_CACHE_VALIDATES) {
831 /* It's possible to get a CACHE_VALIDATES with no
832 * entry in the hash table. This could happen if for
833 * example the soup client is the one creating the
834 * conditional request.
837 soup_cache_update_from_conditional_request (cache, msg);
841 if (!(cacheability & SOUP_CACHE_CACHEABLE))
844 /* Check if we are already caching this resource */
845 if (entry && (entry->dirty || entry->being_validated))
848 /* Create a new entry, deleting any old one if present */
850 soup_cache_entry_remove (cache, entry, TRUE);
852 request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
853 response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
854 entry = soup_cache_entry_new (cache, msg, request_time, response_time);
858 /* Do not continue if it can not be stored */
859 if (!soup_cache_entry_insert (cache, entry, TRUE)) {
860 soup_cache_entry_free (entry);
864 entry->cancellable = g_cancellable_new ();
865 ++cache->priv->n_pending;
867 helper = g_slice_new (StreamHelper);
868 helper->cache = g_object_ref (cache);
869 helper->entry = entry;
871 file = get_file_from_entry (cache, entry);
872 istream = soup_cache_input_stream_new (base_stream, file);
873 g_object_unref (file);
875 g_signal_connect (istream, "caching-finished", G_CALLBACK (istream_caching_finished), helper);
881 soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface,
882 gpointer interface_data)
884 soup_cache_default_content_processor_interface =
885 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
887 processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY;
888 processor_interface->wrap_input = soup_cache_content_processor_wrap_input;
892 soup_cache_init (SoupCache *cache)
894 SoupCachePrivate *priv;
896 priv = cache->priv = SOUP_CACHE_GET_PRIVATE (cache);
898 priv->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
900 priv->lru_start = NULL;
906 priv->max_size = DEFAULT_MAX_SIZE;
907 priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
912 remove_cache_item (gpointer data,
915 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE);
919 soup_cache_finalize (GObject *object)
921 SoupCachePrivate *priv;
924 priv = SOUP_CACHE (object)->priv;
926 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
927 entries = g_hash_table_get_values (priv->cache);
928 g_list_foreach (entries, remove_cache_item, object);
929 g_list_free (entries);
931 g_hash_table_destroy (priv->cache);
932 g_free (priv->cache_dir);
934 g_list_free (priv->lru_start);
936 G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
940 soup_cache_set_property (GObject *object, guint prop_id,
941 const GValue *value, GParamSpec *pspec)
943 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
947 priv->cache_dir = g_value_dup_string (value);
948 /* Create directory if it does not exist (FIXME: should we?) */
949 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
950 g_mkdir_with_parents (priv->cache_dir, 0700);
952 case PROP_CACHE_TYPE:
953 priv->cache_type = g_value_get_enum (value);
954 /* TODO: clear private entries and issue a warning if moving to shared? */
957 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
963 soup_cache_get_property (GObject *object, guint prop_id,
964 GValue *value, GParamSpec *pspec)
966 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
970 g_value_set_string (value, priv->cache_dir);
972 case PROP_CACHE_TYPE:
973 g_value_set_enum (value, priv->cache_type);
976 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
982 soup_cache_constructed (GObject *object)
984 SoupCachePrivate *priv;
986 priv = SOUP_CACHE (object)->priv;
988 if (!priv->cache_dir) {
989 /* Set a default cache dir, different for each user */
990 priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
993 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
994 g_mkdir_with_parents (priv->cache_dir, 0700);
997 if (G_OBJECT_CLASS (soup_cache_parent_class)->constructed)
998 G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
1002 soup_cache_class_init (SoupCacheClass *cache_class)
1004 GObjectClass *gobject_class = (GObjectClass *)cache_class;
1006 gobject_class->finalize = soup_cache_finalize;
1007 gobject_class->constructed = soup_cache_constructed;
1008 gobject_class->set_property = soup_cache_set_property;
1009 gobject_class->get_property = soup_cache_get_property;
1011 cache_class->get_cacheability = get_cacheability;
1013 g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
1014 g_param_spec_string ("cache-dir",
1016 "The directory to store the cache files",
1018 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1020 g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
1021 g_param_spec_enum ("cache-type",
1023 "Whether the cache is private or shared",
1024 SOUP_TYPE_CACHE_TYPE,
1025 SOUP_CACHE_SINGLE_USER,
1026 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1028 g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
1033 * @SOUP_CACHE_SINGLE_USER: a single-user cache
1034 * @SOUP_CACHE_SHARED: a shared cache
1036 * The type of cache; this affects what kinds of responses will be
1044 * @cache_dir: the directory to store the cached data, or %NULL to use the default one
1045 * @cache_type: the #SoupCacheType of the cache
1047 * Creates a new #SoupCache.
1049 * Returns: a new #SoupCache
1054 soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1056 return g_object_new (SOUP_TYPE_CACHE,
1057 "cache-dir", cache_dir,
1058 "cache-type", cache_type,
1063 * soup_cache_has_response:
1064 * @cache: a #SoupCache
1065 * @msg: a #SoupMessage
1067 * This function calculates whether the @cache object has a proper
1068 * response for the request @msg given the flags both in the request
1069 * and the cached reply and the time ellapsed since it was cached.
1071 * Returns: whether or not the @cache has a valid response for @msg
1076 soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1078 SoupCacheEntry *entry;
1079 const char *cache_control, *pragma;
1081 int max_age, max_stale, min_fresh;
1082 GList *lru_item, *item;
1084 entry = soup_cache_entry_lookup (cache, msg);
1086 /* 1. The presented Request-URI and that of stored response
1090 return SOUP_CACHE_RESPONSE_STALE;
1092 /* Increase hit count. Take sorting into account */
1094 lru_item = g_list_find (cache->priv->lru_start, entry);
1096 while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1097 item = g_list_next (item);
1099 if (item != lru_item) {
1100 cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
1101 item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1102 g_list_free (lru_item);
1105 if (entry->dirty || entry->being_validated)
1106 return SOUP_CACHE_RESPONSE_STALE;
1108 /* 2. The request method associated with the stored response
1109 * allows it to be used for the presented request
1112 /* In practice this means we only return our resource for GET,
1113 * cacheability for other methods is a TODO in the RFC
1114 * (TODO: although we could return the headers for HEAD
1117 if (msg->method != SOUP_METHOD_GET)
1118 return SOUP_CACHE_RESPONSE_STALE;
1120 /* 3. Selecting request-headers nominated by the stored
1121 * response (if any) match those presented.
1126 /* 4. The request is a conditional request issued by the client.
1128 if (soup_message_headers_get_one (msg->request_headers, "If-Modified-Since") ||
1129 soup_message_headers_get_list (msg->request_headers, "If-None-Match"))
1130 return SOUP_CACHE_RESPONSE_STALE;
1132 /* 5. The presented request and stored response are free from
1133 * directives that would prevent its use.
1136 max_age = max_stale = min_fresh = -1;
1138 /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1140 pragma = soup_message_headers_get_list (msg->request_headers, "Pragma");
1141 if (pragma && soup_header_contains (pragma, "no-cache"))
1142 return SOUP_CACHE_RESPONSE_STALE;
1144 cache_control = soup_message_headers_get_list (msg->request_headers, "Cache-Control");
1145 if (cache_control && *cache_control) {
1146 GHashTable *hash = soup_header_parse_param_list (cache_control);
1148 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1149 soup_header_free_param_list (hash);
1150 return SOUP_CACHE_RESPONSE_STALE;
1153 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1154 soup_header_free_param_list (hash);
1155 return SOUP_CACHE_RESPONSE_STALE;
1158 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
1159 max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1160 /* Forcing cache revalidaton
1163 soup_header_free_param_list (hash);
1164 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1168 /* max-stale can have no value set, we need to use _extended */
1169 if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1171 max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1173 max_stale = G_MAXINT32;
1176 value = g_hash_table_lookup (hash, "min-fresh");
1178 min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1180 soup_header_free_param_list (hash);
1183 guint current_age = soup_cache_entry_get_current_age (entry);
1185 /* If we are over max-age and max-stale is not
1186 set, do not use the value from the cache
1187 without validation */
1188 if ((guint) max_age <= current_age && max_stale == -1)
1189 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1193 /* 6. The stored response is either: fresh, allowed to be
1194 * served stale or succesfully validated
1196 /* TODO consider also proxy-revalidate & s-maxage */
1197 if (entry->must_revalidate)
1198 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1200 if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1201 /* Not fresh, can it be served stale? */
1202 if (max_stale != -1) {
1203 /* G_MAXINT32 means we accept any staleness */
1204 if (max_stale == G_MAXINT32)
1205 return SOUP_CACHE_RESPONSE_FRESH;
1207 if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1208 return SOUP_CACHE_RESPONSE_FRESH;
1211 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1214 return SOUP_CACHE_RESPONSE_FRESH;
1218 * soup_cache_get_cacheability:
1219 * @cache: a #SoupCache
1220 * @msg: a #SoupMessage
1222 * Calculates whether the @msg can be cached or not.
1224 * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
1229 soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1231 g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1232 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1234 return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1238 force_flush_timeout (gpointer data)
1240 gboolean *forced = (gboolean *)data;
1248 * @cache: a #SoupCache
1250 * This function will force all pending writes in the @cache to be
1251 * committed to disk. For doing so it will iterate the #GMainContext
1252 * associated with @cache's session as long as needed.
1254 * Contrast with soup_cache_dump(), which writes out the cache index
1260 soup_cache_flush (SoupCache *cache)
1262 GMainContext *async_context;
1263 SoupSession *session;
1265 gboolean forced = FALSE;
1267 g_return_if_fail (SOUP_IS_CACHE (cache));
1269 session = cache->priv->session;
1270 g_return_if_fail (SOUP_IS_SESSION (session));
1271 async_context = soup_session_get_async_context (session);
1273 /* We give cache 10 secs to finish */
1274 timeout = soup_add_timeout (async_context, 10000, force_flush_timeout, &forced);
1276 while (!forced && cache->priv->n_pending > 0)
1277 g_main_context_iteration (async_context, FALSE);
1280 g_source_destroy (timeout);
1282 g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
1286 clear_cache_item (gpointer data,
1289 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE);
1293 clear_cache_files (SoupCache *cache)
1295 GFileInfo *file_info;
1296 GFileEnumerator *file_enumerator;
1297 GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
1299 file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
1300 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1301 if (file_enumerator) {
1302 while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
1303 const char *filename = g_file_info_get_name (file_info);
1305 if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
1306 GFile *cache_file = g_file_get_child (cache_dir_file, filename);
1307 g_file_delete (cache_file, NULL, NULL);
1308 g_object_unref (cache_file);
1310 g_object_unref (file_info);
1312 g_object_unref (file_enumerator);
1314 g_object_unref (cache_dir_file);
1319 * @cache: a #SoupCache
1321 * Will remove all entries in the @cache plus all the cache files.
1326 soup_cache_clear (SoupCache *cache)
1330 g_return_if_fail (SOUP_IS_CACHE (cache));
1331 g_return_if_fail (cache->priv->cache);
1333 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1334 entries = g_hash_table_get_values (cache->priv->cache);
1335 g_list_foreach (entries, clear_cache_item, cache);
1336 g_list_free (entries);
1338 /* Remove also any file not associated with a cache entry. */
1339 clear_cache_files (cache);
1343 soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1347 SoupCacheEntry *entry;
1348 const char *last_modified, *etag;
1349 SoupMessagePrivate *origpriv;
1352 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1353 g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1355 /* Add the validator entries in the header from the cached data */
1356 entry = soup_cache_entry_lookup (cache, original);
1357 g_return_val_if_fail (entry, NULL);
1359 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
1360 etag = soup_message_headers_get_one (entry->headers, "ETag");
1362 if (!last_modified && !etag)
1365 entry->being_validated = TRUE;
1367 /* Copy the data we need from the original message */
1368 uri = soup_message_get_uri (original);
1369 msg = soup_message_new_from_uri (original->method, uri);
1370 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
1372 soup_message_headers_foreach (original->request_headers,
1373 (SoupMessageHeadersForeachFunc)copy_headers,
1374 msg->request_headers);
1376 origpriv = SOUP_MESSAGE_GET_PRIVATE (original);
1377 for (f = origpriv->disabled_features; f; f = f->next)
1378 soup_message_disable_feature (msg, (GType) GPOINTER_TO_SIZE (f->data));
1381 soup_message_headers_append (msg->request_headers,
1382 "If-Modified-Since",
1385 soup_message_headers_append (msg->request_headers,
1393 soup_cache_cancel_conditional_request (SoupCache *cache,
1396 SoupCacheEntry *entry;
1398 entry = soup_cache_entry_lookup (cache, msg);
1400 entry->being_validated = FALSE;
1402 soup_session_cancel_message (cache->priv->session, msg, SOUP_STATUS_CANCELLED);
1406 soup_cache_update_from_conditional_request (SoupCache *cache,
1409 SoupCacheEntry *entry = soup_cache_entry_lookup (cache, msg);
1413 entry->being_validated = FALSE;
1415 if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
1416 soup_message_headers_foreach (msg->response_headers,
1417 (SoupMessageHeadersForeachFunc) remove_headers,
1419 copy_end_to_end_headers (msg->response_headers, entry->headers);
1421 soup_cache_entry_set_freshness (entry, msg, cache);
1426 pack_entry (gpointer data,
1429 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1430 SoupMessageHeadersIter iter;
1431 const char *header_key, *header_value;
1432 GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1434 /* Do not store non-consolidated entries */
1435 if (entry->dirty || !entry->key)
1438 g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
1439 g_variant_builder_add (entries_builder, "s", entry->uri);
1440 g_variant_builder_add (entries_builder, "b", entry->must_revalidate);
1441 g_variant_builder_add (entries_builder, "u", entry->freshness_lifetime);
1442 g_variant_builder_add (entries_builder, "u", entry->corrected_initial_age);
1443 g_variant_builder_add (entries_builder, "u", entry->response_time);
1444 g_variant_builder_add (entries_builder, "u", entry->hits);
1445 g_variant_builder_add (entries_builder, "u", entry->length);
1446 g_variant_builder_add (entries_builder, "q", entry->status_code);
1449 g_variant_builder_open (entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_HEADERS_FORMAT));
1450 soup_message_headers_iter_init (&iter, entry->headers);
1451 while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1452 if (g_utf8_validate (header_value, -1, NULL))
1453 g_variant_builder_add (entries_builder, SOUP_CACHE_HEADERS_FORMAT,
1454 header_key, header_value);
1456 g_variant_builder_close (entries_builder); /* "a" SOUP_CACHE_HEADERS_FORMAT */
1457 g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
1462 * @cache: a #SoupCache
1464 * Synchronously writes the cache index out to disk. Contrast with
1465 * soup_cache_flush(), which writes pending cache
1466 * <emphasis>entries</emphasis> to disk.
1468 * You must call this before exiting if you want your cache data to
1469 * persist between sessions.
1474 soup_cache_dump (SoupCache *cache)
1476 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
1478 GVariantBuilder entries_builder;
1479 GVariant *cache_variant;
1481 if (!g_list_length (cache->priv->lru_start))
1484 /* Create the builder and iterate over all entries */
1485 g_variant_builder_init (&entries_builder, G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT));
1486 g_variant_builder_add (&entries_builder, "q", SOUP_CACHE_CURRENT_VERSION);
1487 g_variant_builder_open (&entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_PHEADERS_FORMAT));
1488 g_list_foreach (cache->priv->lru_start, pack_entry, &entries_builder);
1489 g_variant_builder_close (&entries_builder);
1491 /* Serialize and dump */
1492 cache_variant = g_variant_builder_end (&entries_builder);
1493 g_variant_ref_sink (cache_variant);
1494 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1495 g_file_set_contents (filename, (const char *) g_variant_get_data (cache_variant),
1496 g_variant_get_size (cache_variant), NULL);
1498 g_variant_unref (cache_variant);
1503 * @cache: a #SoupCache
1505 * Loads the contents of @cache's index into memory.
1510 soup_cache_load (SoupCache *cache)
1512 gboolean must_revalidate;
1513 guint32 freshness_lifetime, hits;
1514 guint32 corrected_initial_age, response_time;
1515 char *url, *filename = NULL, *contents = NULL;
1516 GVariant *cache_variant;
1517 GVariantIter *entries_iter = NULL, *headers_iter = NULL;
1519 SoupCacheEntry *entry;
1520 SoupCachePrivate *priv = cache->priv;
1521 guint16 version, status_code;
1523 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1524 if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1527 clear_cache_files (cache);
1532 cache_variant = g_variant_new_from_data (G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT),
1533 (const char *) contents, length, FALSE, g_free, contents);
1534 g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &version, &entries_iter);
1535 if (version != SOUP_CACHE_CURRENT_VERSION) {
1536 g_variant_iter_free (entries_iter);
1537 g_variant_unref (cache_variant);
1538 clear_cache_files (cache);
1542 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1543 &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1544 &response_time, &hits, &length, &status_code,
1546 const char *header_key, *header_value;
1547 SoupMessageHeaders *headers;
1548 SoupMessageHeadersIter soup_headers_iter;
1550 /* SoupMessage Headers */
1551 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1552 while (g_variant_iter_loop (headers_iter, SOUP_CACHE_HEADERS_FORMAT, &header_key, &header_value))
1553 if (*header_key && *header_value)
1554 soup_message_headers_append (headers, header_key, header_value);
1556 /* Check that we have headers */
1557 soup_message_headers_iter_init (&soup_headers_iter, headers);
1558 if (!soup_message_headers_iter_next (&soup_headers_iter, &header_key, &header_value)) {
1559 soup_message_headers_free (headers);
1563 /* Insert in cache */
1564 entry = g_slice_new0 (SoupCacheEntry);
1565 entry->uri = g_strdup (url);
1566 entry->must_revalidate = must_revalidate;
1567 entry->freshness_lifetime = freshness_lifetime;
1568 entry->corrected_initial_age = corrected_initial_age;
1569 entry->response_time = response_time;
1571 entry->length = length;
1572 entry->headers = headers;
1573 entry->status_code = status_code;
1575 if (!soup_cache_entry_insert (cache, entry, FALSE))
1576 soup_cache_entry_free (entry);
1579 cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
1582 g_variant_iter_free (entries_iter);
1583 g_variant_unref (cache_variant);
1587 * soup_cache_set_max_size:
1588 * @cache: a #SoupCache
1589 * @max_size: the maximum size of the cache, in bytes
1591 * Sets the maximum size of the cache.
1596 soup_cache_set_max_size (SoupCache *cache,
1599 cache->priv->max_size = max_size;
1600 cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1604 * soup_cache_get_max_size:
1605 * @cache: a #SoupCache
1607 * Gets the maximum size of the cache.
1609 * Return value: the maximum size of the cache, in bytes.
1614 soup_cache_get_max_size (SoupCache *cache)
1616 return cache->priv->max_size;