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"
44 * @short_description: Caching support
46 * #SoupCache implements a file-based cache for HTTP resources.
49 static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
50 static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
52 static SoupContentProcessorInterface *soup_cache_default_content_processor_interface;
53 static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
55 #define DEFAULT_MAX_SIZE 50 * 1024 * 1024
56 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
57 of the cache that can be
58 filled by a single entry */
61 * Version 2: cache is now saved in soup.cache2. Added the version
62 * number to the beginning of the file.
64 * Version 3: added HTTP status code to the cache entries.
66 * Version 4: replaced several types.
67 * - freshness_lifetime,corrected_initial_age,response_time: time_t -> guint32
68 * - status_code: guint -> guint16
69 * - hits: guint -> guint32
71 * Version 5: key is no longer stored on disk as it can be easily
72 * built from the URI. Apart from that some fields in the
73 * SoupCacheEntry have changed:
74 * - entry key is now a uint32 instead of a (char *).
75 * - added uri, used to check for collisions
76 * - removed filename, it's built from the entry key.
78 #define SOUP_CACHE_CURRENT_VERSION 5
80 #define OLD_SOUP_CACHE_FILE "soup.cache"
81 #define SOUP_CACHE_FILE "soup.cache2"
83 #define SOUP_CACHE_HEADERS_FORMAT "{ss}"
84 #define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
85 #define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
87 /* Basically the same format than above except that some strings are
88 prepended with &. This way the GVariant returns a pointer to the
89 data instead of duplicating the string */
90 #define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
93 typedef struct _SoupCacheEntry {
96 guint32 freshness_lifetime;
97 gboolean must_revalidate;
99 guint32 corrected_initial_age;
100 guint32 response_time;
102 gboolean being_validated;
103 SoupMessageHeaders *headers;
105 GCancellable *cancellable;
109 struct _SoupCachePrivate {
113 SoupSession *session;
114 SoupCacheType cache_type;
117 guint max_entry_data_size; /* Computed value. Here for performance reasons */
127 #define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
129 G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
130 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
131 soup_cache_session_feature_init)
132 G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
133 soup_cache_content_processor_init))
135 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge);
136 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
137 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
140 get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
142 char *filename = g_strdup_printf ("%s%s%u", cache->priv->cache_dir,
143 G_DIR_SEPARATOR_S, (guint) entry->key);
144 GFile *file = g_file_new_for_path (filename);
150 static SoupCacheability
151 get_cacheability (SoupCache *cache, SoupMessage *msg)
153 SoupCacheability cacheability;
154 const char *cache_control, *content_type;
155 gboolean has_max_age = FALSE;
157 /* 1. The request method must be cacheable */
158 if (msg->method == SOUP_METHOD_GET)
159 cacheability = SOUP_CACHE_CACHEABLE;
160 else if (msg->method == SOUP_METHOD_HEAD ||
161 msg->method == SOUP_METHOD_TRACE ||
162 msg->method == SOUP_METHOD_CONNECT)
163 return SOUP_CACHE_UNCACHEABLE;
165 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
167 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
168 if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
169 return SOUP_CACHE_UNCACHEABLE;
171 cache_control = soup_message_headers_get_list (msg->response_headers, "Cache-Control");
172 if (cache_control && *cache_control) {
174 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
176 hash = soup_header_parse_param_list (cache_control);
178 /* Shared caches MUST NOT store private resources */
179 if (priv->cache_type == SOUP_CACHE_SHARED) {
180 if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
181 soup_header_free_param_list (hash);
182 return SOUP_CACHE_UNCACHEABLE;
186 /* 2. The 'no-store' cache directive does not appear in the
189 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
190 soup_header_free_param_list (hash);
191 return SOUP_CACHE_UNCACHEABLE;
194 if (g_hash_table_lookup_extended (hash, "max-age", NULL, NULL))
197 /* This does not appear in section 2.1, but I think it makes
198 * sense to check it too?
200 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
201 soup_header_free_param_list (hash);
202 return SOUP_CACHE_UNCACHEABLE;
205 soup_header_free_param_list (hash);
209 if ((soup_message_get_uri (msg))->query &&
210 !soup_message_headers_get_one (msg->response_headers, "Expires") &&
212 return SOUP_CACHE_UNCACHEABLE;
214 switch (msg->status_code) {
215 case SOUP_STATUS_PARTIAL_CONTENT:
216 /* We don't cache partial responses, but they only
217 * invalidate cached full responses if the headers
220 cacheability = SOUP_CACHE_UNCACHEABLE;
223 case SOUP_STATUS_NOT_MODIFIED:
224 /* A 304 response validates an existing cache entry */
225 cacheability = SOUP_CACHE_VALIDATES;
228 case SOUP_STATUS_MULTIPLE_CHOICES:
229 case SOUP_STATUS_MOVED_PERMANENTLY:
230 case SOUP_STATUS_GONE:
231 /* FIXME: cacheable unless indicated otherwise */
232 cacheability = SOUP_CACHE_UNCACHEABLE;
235 case SOUP_STATUS_FOUND:
236 case SOUP_STATUS_TEMPORARY_REDIRECT:
237 /* FIXME: cacheable if explicitly indicated */
238 cacheability = SOUP_CACHE_UNCACHEABLE;
241 case SOUP_STATUS_SEE_OTHER:
242 case SOUP_STATUS_FORBIDDEN:
243 case SOUP_STATUS_NOT_FOUND:
244 case SOUP_STATUS_METHOD_NOT_ALLOWED:
245 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
248 /* Any 5xx status or any 4xx status not handled above
249 * is uncacheable but doesn't break the cache.
251 if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
252 msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
253 msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
254 return SOUP_CACHE_UNCACHEABLE;
256 /* An unrecognized 2xx, 3xx, or 4xx response breaks
259 if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
260 msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
261 (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
262 msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
263 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
270 /* NOTE: this function deletes the file pointed by the file argument
271 * and also unref's the GFile object representing it.
274 soup_cache_entry_free (SoupCacheEntry *entry)
277 g_clear_pointer (&entry->headers, soup_message_headers_free);
278 g_clear_object (&entry->cancellable);
280 g_slice_free (SoupCacheEntry, entry);
284 copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
286 soup_message_headers_append (headers, name, value);
289 static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
292 copy_end_to_end_headers (SoupMessageHeaders *source, SoupMessageHeaders *destination)
296 soup_message_headers_foreach (source, (SoupMessageHeadersForeachFunc) copy_headers, destination);
297 for (i = 0; i < G_N_ELEMENTS (hop_by_hop_headers); i++)
298 soup_message_headers_remove (destination, hop_by_hop_headers[i]);
299 soup_message_headers_clean_connection_headers (destination);
303 soup_cache_entry_get_current_age (SoupCacheEntry *entry)
305 time_t now = time (NULL);
306 time_t resident_time;
308 resident_time = now - entry->response_time;
309 return entry->corrected_initial_age + resident_time;
313 soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
315 guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
316 return entry->freshness_lifetime > limit;
319 static inline guint32
320 get_cache_key_from_uri (const char *uri)
322 return (guint32) g_str_hash (uri);
326 soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
328 const char *cache_control;
329 const char *expires, *date, *last_modified;
331 cache_control = soup_message_headers_get_list (entry->headers, "Cache-Control");
332 if (cache_control && *cache_control) {
333 const char *max_age, *s_maxage;
334 gint64 freshness_lifetime = 0;
336 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
338 hash = soup_header_parse_param_list (cache_control);
340 /* Should we re-validate the entry when it goes stale */
341 entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
344 if (priv->cache_type == SOUP_CACHE_SHARED) {
345 s_maxage = g_hash_table_lookup (hash, "s-maxage");
347 freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
348 if (freshness_lifetime) {
349 /* Implies proxy-revalidate. TODO: is it true? */
350 entry->must_revalidate = TRUE;
351 soup_header_free_param_list (hash);
357 /* If 'max-age' cache directive is present, use that */
358 max_age = g_hash_table_lookup (hash, "max-age");
360 freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
362 if (freshness_lifetime) {
363 entry->freshness_lifetime = (guint32) MIN (freshness_lifetime, G_MAXUINT32);
364 soup_header_free_param_list (hash);
368 soup_header_free_param_list (hash);
371 /* If the 'Expires' response header is present, use its value
372 * minus the value of the 'Date' response header
374 expires = soup_message_headers_get_one (entry->headers, "Expires");
375 date = soup_message_headers_get_one (entry->headers, "Date");
376 if (expires && date) {
377 SoupDate *expires_d, *date_d;
378 time_t expires_t, date_t;
380 expires_d = soup_date_new_from_string (expires);
382 date_d = soup_date_new_from_string (date);
384 expires_t = soup_date_to_time_t (expires_d);
385 date_t = soup_date_to_time_t (date_d);
387 soup_date_free (expires_d);
388 soup_date_free (date_d);
390 if (expires_t && date_t) {
391 entry->freshness_lifetime = (guint32) MAX (expires_t - date_t, 0);
395 /* If Expires is not a valid date we should
396 treat it as already expired, see section
398 entry->freshness_lifetime = 0;
403 /* Otherwise an heuristic may be used */
405 /* Heuristics MUST NOT be used with stored responses with
406 these status codes (section 2.3.1.1) */
407 if (entry->status_code != SOUP_STATUS_OK &&
408 entry->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
409 entry->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
410 entry->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
411 entry->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
412 entry->status_code != SOUP_STATUS_GONE)
415 /* TODO: attach warning 113 if response's current_age is more
416 than 24h (section 2.3.1.1) when using heuristics */
418 /* Last-Modified based heuristic */
419 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
422 time_t now, last_modified_t;
424 soup_date = soup_date_new_from_string (last_modified);
425 last_modified_t = soup_date_to_time_t (soup_date);
428 #define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
430 entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
431 soup_date_free (soup_date);
437 /* If all else fails, make the entry expire immediately */
438 entry->freshness_lifetime = 0;
441 static SoupCacheEntry *
442 soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
444 SoupCacheEntry *entry;
447 entry = g_slice_new0 (SoupCacheEntry);
448 entry->dirty = FALSE;
449 entry->being_validated = FALSE;
450 entry->status_code = msg->status_code;
451 entry->response_time = response_time;
452 entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
455 entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
456 copy_end_to_end_headers (msg->response_headers, entry->headers);
461 /* Section 2.3.1, Freshness Lifetime */
462 soup_cache_entry_set_freshness (entry, msg, cache);
464 /* Section 2.3.2, Calculating Age */
465 date = soup_message_headers_get_one (entry->headers, "Date");
470 time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
472 soup_date = soup_date_new_from_string (date);
473 date_value = soup_date_to_time_t (soup_date);
474 soup_date_free (soup_date);
476 age = soup_message_headers_get_one (entry->headers, "Age");
478 age_value = g_ascii_strtoll (age, NULL, 10);
480 apparent_age = MAX (0, entry->response_time - date_value);
481 corrected_received_age = MAX (apparent_age, age_value);
482 response_delay = entry->response_time - request_time;
483 entry->corrected_initial_age = corrected_received_age + response_delay;
485 /* Is this correct ? */
486 entry->corrected_initial_age = time (NULL);
493 soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge)
498 g_cancellable_cancel (entry->cancellable);
502 g_assert (!entry->dirty);
503 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
505 if (!g_hash_table_remove (cache->priv->cache, GUINT_TO_POINTER (entry->key)))
508 /* Remove from LRU */
509 lru_item = g_list_find (cache->priv->lru_start, entry);
510 cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
512 /* Adjust cache size */
513 cache->priv->size -= entry->length;
515 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
519 GFile *file = get_file_from_entry (cache, entry);
520 g_file_delete (file, NULL, NULL);
521 g_object_unref (file);
523 soup_cache_entry_free (entry);
529 lru_compare_func (gconstpointer a, gconstpointer b)
531 SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
532 SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
534 /* The rationale of this sorting func is
536 * 1. sort by hits -> LRU algorithm, then
538 * 2. sort by freshness lifetime, we better discard first
539 * entries that are close to expire
541 * 3. sort by size, replace first small size resources as they
542 * are cheaper to download
546 if (entry_a->hits != entry_b->hits)
547 return entry_a->hits - entry_b->hits;
549 /* Sort by freshness_lifetime */
550 if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
551 return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
554 return entry_a->length - entry_b->length;
558 cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
560 /* We could add here some more heuristics. TODO: review how
561 this is done by other HTTP caches */
563 return length_to_add <= cache->priv->max_entry_data_size;
567 make_room_for_new_entry (SoupCache *cache, guint length_to_add)
569 GList *lru_entry = cache->priv->lru_start;
571 /* Check that there is enough room for the new entry. This is
572 an approximation as we're not working out the size of the
573 cache file or the size of the headers for performance
574 reasons. TODO: check if that would be really that expensive */
577 (length_to_add + cache->priv->size > cache->priv->max_size)) {
578 SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
580 /* Discard entries. Once cancelled resources will be
581 * freed in close_ready_cb
583 if (soup_cache_entry_remove (cache, old_entry, TRUE))
584 lru_entry = cache->priv->lru_start;
586 lru_entry = g_list_next (lru_entry);
591 soup_cache_entry_insert (SoupCache *cache,
592 SoupCacheEntry *entry,
595 guint length_to_add = 0;
596 SoupCacheEntry *old_entry;
599 entry->key = get_cache_key_from_uri ((const char *) entry->uri);
601 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
602 length_to_add = soup_message_headers_get_content_length (entry->headers);
604 /* Check if we are going to store the resource depending on its size */
606 if (!cache_accepts_entries_of_size (cache, length_to_add))
609 /* Make room for new entry if needed */
610 make_room_for_new_entry (cache, length_to_add);
613 /* Remove any previous entry */
614 if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
615 if (!soup_cache_entry_remove (cache, old_entry, TRUE))
619 /* Add to hash table */
620 g_hash_table_insert (cache->priv->cache, GUINT_TO_POINTER (entry->key), entry);
622 /* Compute new cache size */
623 cache->priv->size += length_to_add;
627 cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
629 cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
631 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
636 static SoupCacheEntry*
637 soup_cache_entry_lookup (SoupCache *cache,
640 SoupCacheEntry *entry;
644 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
645 key = get_cache_key_from_uri ((const char *) uri);
647 entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (key));
649 if (entry != NULL && (strcmp (entry->uri, uri) != 0))
657 soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
659 SoupCacheEntry *entry;
661 GInputStream *file_stream, *body_stream, *cache_stream;
664 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
665 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
667 entry = soup_cache_entry_lookup (cache, msg);
668 g_return_val_if_fail (entry, NULL);
670 file = get_file_from_entry (cache, entry);
671 file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
672 g_object_unref (file);
674 /* Do not change the original message if there is no resource */
678 body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length);
679 g_object_unref (file_stream);
684 /* If we are told to send a response from cache any validation
685 in course is over by now */
686 entry->being_validated = FALSE;
689 soup_message_set_status (msg, entry->status_code);
692 copy_end_to_end_headers (entry->headers, msg->response_headers);
694 /* Add 'Age' header with the current age */
695 current_age = g_strdup_printf ("%d", soup_cache_entry_get_current_age (entry));
696 soup_message_headers_replace (msg->response_headers,
699 g_free (current_age);
701 /* Create the cache stream. */
702 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
703 cache_stream = soup_message_setup_body_istream (body_stream, msg,
704 cache->priv->session,
705 SOUP_STAGE_ENTITY_BODY);
706 g_object_unref (body_stream);
712 msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
714 g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL)));
715 g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
719 request_started (SoupSessionFeature *feature, SoupSession *session,
720 SoupMessage *msg, SoupSocket *socket)
722 g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL)));
723 g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), NULL);
727 attach (SoupSessionFeature *feature, SoupSession *session)
729 SoupCache *cache = SOUP_CACHE (feature);
730 cache->priv->session = session;
732 soup_cache_default_feature_interface->attach (feature, session);
736 soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
737 gpointer interface_data)
739 soup_cache_default_feature_interface =
740 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
742 feature_interface->attach = attach;
743 feature_interface->request_started = request_started;
748 SoupCacheEntry *entry;
752 istream_cache_cb (GObject *source,
756 SoupCacheInputStream *istream = SOUP_CACHE_INPUT_STREAM (source);
757 StreamHelper *helper = (StreamHelper *) user_data;
758 SoupCache *cache = helper->cache;
759 SoupCacheEntry *entry = helper->entry;
760 GError *error = NULL;
762 entry->dirty = FALSE;
763 g_clear_object (&entry->cancellable);
764 --cache->priv->n_pending;
766 entry->length = soup_cache_input_stream_cache_finish (istream, res, &error);
769 /* Update cache size */
770 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
771 cache->priv->size -= soup_message_headers_get_content_length (entry->headers);
773 soup_cache_entry_remove (cache, entry, TRUE);
774 helper->entry = entry = NULL;
778 if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) {
780 if (cache_accepts_entries_of_size (cache, entry->length)) {
781 make_room_for_new_entry (cache, entry->length);
782 cache->priv->size += entry->length;
784 soup_cache_entry_remove (cache, entry, TRUE);
785 helper->entry = entry = NULL;
790 g_object_unref (helper->cache);
791 g_slice_free (StreamHelper, helper);
796 soup_cache_content_processor_wrap_input (SoupContentProcessor *processor,
797 GInputStream *base_stream,
801 SoupCache *cache = (SoupCache*) processor;
802 SoupCacheEntry *entry;
803 SoupCacheability cacheability;
804 GInputStream *istream;
806 StreamHelper *helper;
807 time_t request_time, response_time;
809 /* First of all, check if we should cache the resource. */
810 cacheability = soup_cache_get_cacheability (cache, msg);
811 entry = soup_cache_entry_lookup (cache, msg);
813 if (cacheability & SOUP_CACHE_INVALIDATES) {
815 soup_cache_entry_remove (cache, entry, TRUE);
819 if (cacheability & SOUP_CACHE_VALIDATES) {
820 /* It's possible to get a CACHE_VALIDATES with no
821 * entry in the hash table. This could happen if for
822 * example the soup client is the one creating the
823 * conditional request.
826 entry->being_validated = FALSE;
827 copy_end_to_end_headers (msg->response_headers, entry->headers);
828 soup_cache_entry_set_freshness (entry, msg, cache);
833 if (!(cacheability & SOUP_CACHE_CACHEABLE))
836 /* Check if we are already caching this resource */
837 if (entry && (entry->dirty || entry->being_validated))
840 /* Create a new entry, deleting any old one if present */
842 soup_cache_entry_remove (cache, entry, TRUE);
844 request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
845 response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
846 entry = soup_cache_entry_new (cache, msg, request_time, response_time);
850 /* Do not continue if it can not be stored */
851 if (!soup_cache_entry_insert (cache, entry, TRUE)) {
852 soup_cache_entry_free (entry);
856 ++cache->priv->n_pending;
858 istream = soup_cache_input_stream_new (base_stream);
860 file = get_file_from_entry (cache, entry);
861 entry->cancellable = g_cancellable_new ();
863 helper = g_slice_new (StreamHelper);
864 helper->cache = g_object_ref (cache);
865 helper->entry = entry;
867 soup_cache_input_stream_cache (SOUP_CACHE_INPUT_STREAM (istream), file, entry->cancellable,
868 (GAsyncReadyCallback) istream_cache_cb, helper);
869 g_object_unref (file);
875 soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface,
876 gpointer interface_data)
878 soup_cache_default_content_processor_interface =
879 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
881 processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY;
882 processor_interface->wrap_input = soup_cache_content_processor_wrap_input;
886 soup_cache_init (SoupCache *cache)
888 SoupCachePrivate *priv;
890 priv = cache->priv = SOUP_CACHE_GET_PRIVATE (cache);
892 priv->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
894 priv->lru_start = NULL;
900 priv->max_size = DEFAULT_MAX_SIZE;
901 priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
906 remove_cache_item (gpointer data,
909 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE);
913 soup_cache_finalize (GObject *object)
915 SoupCachePrivate *priv;
918 priv = SOUP_CACHE (object)->priv;
920 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
921 entries = g_hash_table_get_values (priv->cache);
922 g_list_foreach (entries, remove_cache_item, object);
923 g_list_free (entries);
925 g_hash_table_destroy (priv->cache);
926 g_free (priv->cache_dir);
928 g_list_free (priv->lru_start);
930 G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
934 soup_cache_set_property (GObject *object, guint prop_id,
935 const GValue *value, GParamSpec *pspec)
937 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
941 priv->cache_dir = g_value_dup_string (value);
942 /* Create directory if it does not exist (FIXME: should we?) */
943 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
944 g_mkdir_with_parents (priv->cache_dir, 0700);
946 case PROP_CACHE_TYPE:
947 priv->cache_type = g_value_get_enum (value);
948 /* TODO: clear private entries and issue a warning if moving to shared? */
951 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
957 soup_cache_get_property (GObject *object, guint prop_id,
958 GValue *value, GParamSpec *pspec)
960 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
964 g_value_set_string (value, priv->cache_dir);
966 case PROP_CACHE_TYPE:
967 g_value_set_enum (value, priv->cache_type);
970 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
976 soup_cache_constructed (GObject *object)
978 SoupCachePrivate *priv;
980 priv = SOUP_CACHE (object)->priv;
982 if (!priv->cache_dir) {
983 /* Set a default cache dir, different for each user */
984 priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
987 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
988 g_mkdir_with_parents (priv->cache_dir, 0700);
991 if (G_OBJECT_CLASS (soup_cache_parent_class)->constructed)
992 G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
996 soup_cache_class_init (SoupCacheClass *cache_class)
998 GObjectClass *gobject_class = (GObjectClass *)cache_class;
1000 gobject_class->finalize = soup_cache_finalize;
1001 gobject_class->constructed = soup_cache_constructed;
1002 gobject_class->set_property = soup_cache_set_property;
1003 gobject_class->get_property = soup_cache_get_property;
1005 cache_class->get_cacheability = get_cacheability;
1007 g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
1008 g_param_spec_string ("cache-dir",
1010 "The directory to store the cache files",
1012 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1014 g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
1015 g_param_spec_enum ("cache-type",
1017 "Whether the cache is private or shared",
1018 SOUP_TYPE_CACHE_TYPE,
1019 SOUP_CACHE_SINGLE_USER,
1020 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1022 g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
1027 * @SOUP_CACHE_SINGLE_USER: a single-user cache
1028 * @SOUP_CACHE_SHARED: a shared cache
1030 * The type of cache; this affects what kinds of responses will be
1038 * @cache_dir: the directory to store the cached data, or %NULL to use the default one
1039 * @cache_type: the #SoupCacheType of the cache
1041 * Creates a new #SoupCache.
1043 * Returns: a new #SoupCache
1048 soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1050 return g_object_new (SOUP_TYPE_CACHE,
1051 "cache-dir", cache_dir,
1052 "cache-type", cache_type,
1057 * soup_cache_has_response:
1058 * @cache: a #SoupCache
1059 * @msg: a #SoupMessage
1061 * This function calculates whether the @cache object has a proper
1062 * response for the request @msg given the flags both in the request
1063 * and the cached reply and the time ellapsed since it was cached.
1065 * Returns: whether or not the @cache has a valid response for @msg
1070 soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1072 SoupCacheEntry *entry;
1073 const char *cache_control, *pragma;
1075 int max_age, max_stale, min_fresh;
1076 GList *lru_item, *item;
1078 entry = soup_cache_entry_lookup (cache, msg);
1080 /* 1. The presented Request-URI and that of stored response
1084 return SOUP_CACHE_RESPONSE_STALE;
1086 /* Increase hit count. Take sorting into account */
1088 lru_item = g_list_find (cache->priv->lru_start, entry);
1090 while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1091 item = g_list_next (item);
1093 if (item != lru_item) {
1094 cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
1095 item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1096 g_list_free (lru_item);
1099 if (entry->dirty || entry->being_validated)
1100 return SOUP_CACHE_RESPONSE_STALE;
1102 /* 2. The request method associated with the stored response
1103 * allows it to be used for the presented request
1106 /* In practice this means we only return our resource for GET,
1107 * cacheability for other methods is a TODO in the RFC
1108 * (TODO: although we could return the headers for HEAD
1111 if (msg->method != SOUP_METHOD_GET)
1112 return SOUP_CACHE_RESPONSE_STALE;
1114 /* 3. Selecting request-headers nominated by the stored
1115 * response (if any) match those presented.
1120 /* 4. The request is a conditional request issued by the client.
1122 if (soup_message_headers_get_one (msg->request_headers, "If-Modified-Since") ||
1123 soup_message_headers_get_list (msg->request_headers, "If-None-Match"))
1124 return SOUP_CACHE_RESPONSE_STALE;
1126 /* 5. The presented request and stored response are free from
1127 * directives that would prevent its use.
1130 max_age = max_stale = min_fresh = -1;
1132 /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1134 pragma = soup_message_headers_get_list (msg->request_headers, "Pragma");
1135 if (pragma && soup_header_contains (pragma, "no-cache"))
1136 return SOUP_CACHE_RESPONSE_STALE;
1138 cache_control = soup_message_headers_get_list (msg->request_headers, "Cache-Control");
1139 if (cache_control && *cache_control) {
1140 GHashTable *hash = soup_header_parse_param_list (cache_control);
1142 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1143 soup_header_free_param_list (hash);
1144 return SOUP_CACHE_RESPONSE_STALE;
1147 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1148 soup_header_free_param_list (hash);
1149 return SOUP_CACHE_RESPONSE_STALE;
1152 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
1153 max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1154 /* Forcing cache revalidaton
1157 soup_header_free_param_list (hash);
1158 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1162 /* max-stale can have no value set, we need to use _extended */
1163 if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1165 max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1167 max_stale = G_MAXINT32;
1170 value = g_hash_table_lookup (hash, "min-fresh");
1172 min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1174 soup_header_free_param_list (hash);
1177 guint current_age = soup_cache_entry_get_current_age (entry);
1179 /* If we are over max-age and max-stale is not
1180 set, do not use the value from the cache
1181 without validation */
1182 if ((guint) max_age <= current_age && max_stale == -1)
1183 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1187 /* 6. The stored response is either: fresh, allowed to be
1188 * served stale or succesfully validated
1190 /* TODO consider also proxy-revalidate & s-maxage */
1191 if (entry->must_revalidate)
1192 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1194 if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1195 /* Not fresh, can it be served stale? */
1196 if (max_stale != -1) {
1197 /* G_MAXINT32 means we accept any staleness */
1198 if (max_stale == G_MAXINT32)
1199 return SOUP_CACHE_RESPONSE_FRESH;
1201 if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1202 return SOUP_CACHE_RESPONSE_FRESH;
1205 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1208 return SOUP_CACHE_RESPONSE_FRESH;
1212 * soup_cache_get_cacheability:
1213 * @cache: a #SoupCache
1214 * @msg: a #SoupMessage
1216 * Calculates whether the @msg can be cached or not.
1218 * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
1223 soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1225 g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1226 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1228 return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1232 force_flush_timeout (gpointer data)
1234 gboolean *forced = (gboolean *)data;
1242 * @cache: a #SoupCache
1244 * This function will force all pending writes in the @cache to be
1245 * committed to disk. For doing so it will iterate the #GMainContext
1246 * associated with @cache's session as long as needed.
1248 * Contrast with soup_cache_dump(), which writes out the cache index
1254 soup_cache_flush (SoupCache *cache)
1256 GMainContext *async_context;
1257 SoupSession *session;
1259 gboolean forced = FALSE;
1261 g_return_if_fail (SOUP_IS_CACHE (cache));
1263 session = cache->priv->session;
1264 g_return_if_fail (SOUP_IS_SESSION (session));
1265 async_context = soup_session_get_async_context (session);
1267 /* We give cache 10 secs to finish */
1268 timeout = soup_add_timeout (async_context, 10000, force_flush_timeout, &forced);
1270 while (!forced && cache->priv->n_pending > 0)
1271 g_main_context_iteration (async_context, FALSE);
1274 g_source_destroy (timeout);
1276 g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
1280 clear_cache_item (gpointer data,
1283 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE);
1287 clear_cache_files (SoupCache *cache)
1289 GFileInfo *file_info;
1290 GFileEnumerator *file_enumerator;
1291 GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
1293 file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
1294 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1295 if (file_enumerator) {
1296 while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
1297 const char *filename = g_file_info_get_name (file_info);
1299 if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
1300 GFile *cache_file = g_file_get_child (cache_dir_file, filename);
1301 g_file_delete (cache_file, NULL, NULL);
1302 g_object_unref (cache_file);
1304 g_object_unref (file_info);
1306 g_object_unref (file_enumerator);
1308 g_object_unref (cache_dir_file);
1313 * @cache: a #SoupCache
1315 * Will remove all entries in the @cache plus all the cache files.
1320 soup_cache_clear (SoupCache *cache)
1324 g_return_if_fail (SOUP_IS_CACHE (cache));
1325 g_return_if_fail (cache->priv->cache);
1327 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1328 entries = g_hash_table_get_values (cache->priv->cache);
1329 g_list_foreach (entries, clear_cache_item, cache);
1330 g_list_free (entries);
1332 /* Remove also any file not associated with a cache entry. */
1333 clear_cache_files (cache);
1337 soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1341 SoupCacheEntry *entry;
1342 const char *last_modified, *etag;
1344 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1345 g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1347 /* Add the validator entries in the header from the cached data */
1348 entry = soup_cache_entry_lookup (cache, original);
1349 g_return_val_if_fail (entry, NULL);
1351 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
1352 etag = soup_message_headers_get_one (entry->headers, "ETag");
1354 if (!last_modified && !etag)
1357 entry->being_validated = TRUE;
1359 /* Copy the data we need from the original message */
1360 uri = soup_message_get_uri (original);
1361 msg = soup_message_new_from_uri (original->method, uri);
1362 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
1364 soup_message_headers_foreach (original->request_headers,
1365 (SoupMessageHeadersForeachFunc)copy_headers,
1366 msg->request_headers);
1369 soup_message_headers_append (msg->request_headers,
1370 "If-Modified-Since",
1373 soup_message_headers_append (msg->request_headers,
1381 pack_entry (gpointer data,
1384 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1385 SoupMessageHeadersIter iter;
1386 const char *header_key, *header_value;
1387 GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1389 /* Do not store non-consolidated entries */
1390 if (entry->dirty || !entry->key)
1393 g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
1394 g_variant_builder_add (entries_builder, "s", entry->uri);
1395 g_variant_builder_add (entries_builder, "b", entry->must_revalidate);
1396 g_variant_builder_add (entries_builder, "u", entry->freshness_lifetime);
1397 g_variant_builder_add (entries_builder, "u", entry->corrected_initial_age);
1398 g_variant_builder_add (entries_builder, "u", entry->response_time);
1399 g_variant_builder_add (entries_builder, "u", entry->hits);
1400 g_variant_builder_add (entries_builder, "u", entry->length);
1401 g_variant_builder_add (entries_builder, "q", entry->status_code);
1404 g_variant_builder_open (entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_HEADERS_FORMAT));
1405 soup_message_headers_iter_init (&iter, entry->headers);
1406 while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1407 if (g_utf8_validate (header_value, -1, NULL))
1408 g_variant_builder_add (entries_builder, SOUP_CACHE_HEADERS_FORMAT,
1409 header_key, header_value);
1411 g_variant_builder_close (entries_builder); /* "a" SOUP_CACHE_HEADERS_FORMAT */
1412 g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
1417 * @cache: a #SoupCache
1419 * Synchronously writes the cache index out to disk. Contrast with
1420 * soup_cache_flush(), which writes pending cache
1421 * <emphasis>entries</emphasis> to disk.
1423 * You must call this before exiting if you want your cache data to
1424 * persist between sessions.
1429 soup_cache_dump (SoupCache *cache)
1431 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
1433 GVariantBuilder entries_builder;
1434 GVariant *cache_variant;
1436 if (!g_list_length (cache->priv->lru_start))
1439 /* Create the builder and iterate over all entries */
1440 g_variant_builder_init (&entries_builder, G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT));
1441 g_variant_builder_add (&entries_builder, "q", SOUP_CACHE_CURRENT_VERSION);
1442 g_variant_builder_open (&entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_PHEADERS_FORMAT));
1443 g_list_foreach (cache->priv->lru_start, pack_entry, &entries_builder);
1444 g_variant_builder_close (&entries_builder);
1446 /* Serialize and dump */
1447 cache_variant = g_variant_builder_end (&entries_builder);
1448 g_variant_ref_sink (cache_variant);
1449 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1450 g_file_set_contents (filename, (const char *) g_variant_get_data (cache_variant),
1451 g_variant_get_size (cache_variant), NULL);
1453 g_variant_unref (cache_variant);
1458 * @cache: a #SoupCache
1460 * Loads the contents of @cache's index into memory.
1465 soup_cache_load (SoupCache *cache)
1467 gboolean must_revalidate;
1468 guint32 freshness_lifetime, hits;
1469 guint32 corrected_initial_age, response_time;
1470 char *url, *filename = NULL, *contents = NULL;
1471 GVariant *cache_variant;
1472 GVariantIter *entries_iter = NULL, *headers_iter = NULL;
1474 SoupCacheEntry *entry;
1475 SoupCachePrivate *priv = cache->priv;
1476 guint16 version, status_code;
1478 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1479 if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1482 clear_cache_files (cache);
1487 cache_variant = g_variant_new_from_data (G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT),
1488 (const char *) contents, length, FALSE, g_free, contents);
1489 g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &version, &entries_iter);
1490 if (version != SOUP_CACHE_CURRENT_VERSION) {
1491 g_variant_iter_free (entries_iter);
1492 g_variant_unref (cache_variant);
1493 clear_cache_files (cache);
1497 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1498 &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1499 &response_time, &hits, &length, &status_code,
1501 const char *header_key, *header_value;
1502 SoupMessageHeaders *headers;
1503 SoupMessageHeadersIter soup_headers_iter;
1505 /* SoupMessage Headers */
1506 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1507 while (g_variant_iter_loop (headers_iter, SOUP_CACHE_HEADERS_FORMAT, &header_key, &header_value))
1508 if (*header_key && *header_value)
1509 soup_message_headers_append (headers, header_key, header_value);
1511 /* Check that we have headers */
1512 soup_message_headers_iter_init (&soup_headers_iter, headers);
1513 if (!soup_message_headers_iter_next (&soup_headers_iter, &header_key, &header_value)) {
1514 soup_message_headers_free (headers);
1518 /* Insert in cache */
1519 entry = g_slice_new0 (SoupCacheEntry);
1520 entry->uri = g_strdup (url);
1521 entry->must_revalidate = must_revalidate;
1522 entry->freshness_lifetime = freshness_lifetime;
1523 entry->corrected_initial_age = corrected_initial_age;
1524 entry->response_time = response_time;
1526 entry->length = length;
1527 entry->headers = headers;
1528 entry->status_code = status_code;
1530 if (!soup_cache_entry_insert (cache, entry, FALSE))
1531 soup_cache_entry_free (entry);
1534 cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
1537 g_variant_iter_free (entries_iter);
1538 g_variant_unref (cache_variant);
1542 * soup_cache_set_max_size:
1543 * @cache: a #SoupCache
1544 * @max_size: the maximum size of the cache, in bytes
1546 * Sets the maximum size of the cache.
1551 soup_cache_set_max_size (SoupCache *cache,
1554 cache->priv->max_size = max_size;
1555 cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1559 * soup_cache_get_max_size:
1560 * @cache: a #SoupCache
1562 * Gets the maximum size of the cache.
1564 * Return value: the maximum size of the cache, in bytes.
1569 soup_cache_get_max_size (SoupCache *cache)
1571 return cache->priv->max_size;