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.
36 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
38 #include "soup-cache.h"
39 #include "soup-cache-private.h"
40 #include "soup-date.h"
41 #include "soup-enum-types.h"
42 #include "soup-headers.h"
43 #include "soup-session.h"
44 #include "soup-session-feature.h"
49 static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
50 static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
52 #define DEFAULT_MAX_SIZE 50 * 1024 * 1024
53 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
54 of the cache that can be
55 filled by a single entry */
58 * Version 2: cache is now saved in soup.cache2. Added the version
59 * number to the beginning of the file.
61 * Version 3: added HTTP status code to the cache entries.
63 * Version 4: replaced several types.
64 * - freshness_lifetime,corrected_initial_age,response_time: time_t -> guint32
65 * - status_code: guint -> guint16
66 * - hits: guint -> guint32
68 * Version 5: key is no longer stored on disk as it can be easily
69 * built from the URI. Apart from that some fields in the
70 * SoupCacheEntry have changed:
71 * - entry key is now a uint32 instead of a (char *).
72 * - added uri, used to check for collisions
73 * - removed filename, it's built from the entry key.
75 #define SOUP_CACHE_CURRENT_VERSION 5
77 typedef struct _SoupCacheEntry {
80 guint32 freshness_lifetime;
81 gboolean must_revalidate;
83 guint32 corrected_initial_age;
84 guint32 response_time;
85 SoupBuffer *current_writing_buffer;
88 gboolean being_validated;
89 SoupMessageHeaders *headers;
90 GOutputStream *stream;
93 GCancellable *cancellable;
97 struct _SoupCachePrivate {
101 SoupSession *session;
102 SoupCacheType cache_type;
105 guint max_entry_data_size; /* Computed value. Here for performance reasons */
111 SoupCacheEntry *entry;
113 gulong got_chunk_handler;
114 gulong got_body_handler;
115 gulong restarted_handler;
116 GQueue *buffer_queue;
117 } SoupCacheWritingFixture;
125 #define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
127 G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
128 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
129 soup_cache_session_feature_init))
131 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry);
132 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
133 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
134 static gboolean write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture);
137 get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
139 char *filename = g_strdup_printf ("%s%s%u", cache->priv->cache_dir,
140 G_DIR_SEPARATOR_S, (guint) entry->key);
141 GFile *file = g_file_new_for_path (filename);
147 static SoupCacheability
148 get_cacheability (SoupCache *cache, SoupMessage *msg)
150 SoupCacheability cacheability;
151 const char *cache_control, *content_type;
153 /* 1. The request method must be cacheable */
154 if (msg->method == SOUP_METHOD_GET)
155 cacheability = SOUP_CACHE_CACHEABLE;
156 else if (msg->method == SOUP_METHOD_HEAD ||
157 msg->method == SOUP_METHOD_TRACE ||
158 msg->method == SOUP_METHOD_CONNECT)
159 return SOUP_CACHE_UNCACHEABLE;
161 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
163 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
164 if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
165 return SOUP_CACHE_UNCACHEABLE;
167 cache_control = soup_message_headers_get (msg->response_headers, "Cache-Control");
170 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
172 hash = soup_header_parse_param_list (cache_control);
174 /* Shared caches MUST NOT store private resources */
175 if (priv->cache_type == SOUP_CACHE_SHARED) {
176 if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
177 soup_header_free_param_list (hash);
178 return SOUP_CACHE_UNCACHEABLE;
182 /* 2. The 'no-store' cache directive does not appear in the
185 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
186 soup_header_free_param_list (hash);
187 return SOUP_CACHE_UNCACHEABLE;
190 /* This does not appear in section 2.1, but I think it makes
191 * sense to check it too?
193 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
194 soup_header_free_param_list (hash);
195 return SOUP_CACHE_UNCACHEABLE;
198 soup_header_free_param_list (hash);
201 switch (msg->status_code) {
202 case SOUP_STATUS_PARTIAL_CONTENT:
203 /* We don't cache partial responses, but they only
204 * invalidate cached full responses if the headers
207 cacheability = SOUP_CACHE_UNCACHEABLE;
210 case SOUP_STATUS_NOT_MODIFIED:
211 /* A 304 response validates an existing cache entry */
212 cacheability = SOUP_CACHE_VALIDATES;
215 case SOUP_STATUS_MULTIPLE_CHOICES:
216 case SOUP_STATUS_MOVED_PERMANENTLY:
217 case SOUP_STATUS_GONE:
218 /* FIXME: cacheable unless indicated otherwise */
219 cacheability = SOUP_CACHE_UNCACHEABLE;
222 case SOUP_STATUS_FOUND:
223 case SOUP_STATUS_TEMPORARY_REDIRECT:
224 /* FIXME: cacheable if explicitly indicated */
225 cacheability = SOUP_CACHE_UNCACHEABLE;
228 case SOUP_STATUS_SEE_OTHER:
229 case SOUP_STATUS_FORBIDDEN:
230 case SOUP_STATUS_NOT_FOUND:
231 case SOUP_STATUS_METHOD_NOT_ALLOWED:
232 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
235 /* Any 5xx status or any 4xx status not handled above
236 * is uncacheable but doesn't break the cache.
238 if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
239 msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
240 msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
241 return SOUP_CACHE_UNCACHEABLE;
243 /* An unrecognized 2xx, 3xx, or 4xx response breaks
246 if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
247 msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
248 (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
249 msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
250 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
257 /* NOTE: this function deletes the file pointed by the file argument
258 * and also unref's the GFile object representing it.
261 soup_cache_entry_free (SoupCacheEntry *entry, GFile *file)
264 g_file_delete (file, NULL, NULL);
265 g_object_unref (file);
271 if (entry->current_writing_buffer) {
272 soup_buffer_free (entry->current_writing_buffer);
273 entry->current_writing_buffer = NULL;
276 if (entry->headers) {
277 soup_message_headers_free (entry->headers);
278 entry->headers = NULL;
281 g_error_free (entry->error);
284 if (entry->cancellable) {
285 g_object_unref (entry->cancellable);
286 entry->cancellable = NULL;
289 g_slice_free (SoupCacheEntry, entry);
293 copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
295 soup_message_headers_append (headers, name, value);
298 static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
301 copy_end_to_end_headers (SoupMessageHeaders *source, SoupMessageHeaders *destination)
305 soup_message_headers_foreach (source, (SoupMessageHeadersForeachFunc) copy_headers, destination);
306 for (i = 0; i < G_N_ELEMENTS (hop_by_hop_headers); i++)
307 soup_message_headers_remove (destination, hop_by_hop_headers[i]);
308 soup_message_headers_clean_connection_headers (destination);
312 soup_cache_entry_get_current_age (SoupCacheEntry *entry)
314 time_t now = time (NULL);
315 time_t resident_time;
317 resident_time = now - entry->response_time;
318 return entry->corrected_initial_age + resident_time;
322 soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
324 guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
325 return entry->freshness_lifetime > limit;
328 static inline guint32
329 get_cache_key_from_uri (const char *uri)
331 return (guint32) g_str_hash (uri);
335 soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
337 const char *cache_control;
338 const char *expires, *date, *last_modified;
340 cache_control = soup_message_headers_get (entry->headers, "Cache-Control");
342 const char *max_age, *s_maxage;
343 gint64 freshness_lifetime = 0;
345 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
347 hash = soup_header_parse_param_list (cache_control);
349 /* Should we re-validate the entry when it goes stale */
350 entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
353 if (priv->cache_type == SOUP_CACHE_SHARED) {
354 s_maxage = g_hash_table_lookup (hash, "s-maxage");
356 freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
357 if (freshness_lifetime) {
358 /* Implies proxy-revalidate. TODO: is it true? */
359 entry->must_revalidate = TRUE;
360 soup_header_free_param_list (hash);
366 /* If 'max-age' cache directive is present, use that */
367 max_age = g_hash_table_lookup (hash, "max-age");
369 freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
371 if (freshness_lifetime) {
372 entry->freshness_lifetime = (guint32) MIN (freshness_lifetime, G_MAXUINT32);
373 soup_header_free_param_list (hash);
377 soup_header_free_param_list (hash);
380 /* If the 'Expires' response header is present, use its value
381 * minus the value of the 'Date' response header
383 expires = soup_message_headers_get (entry->headers, "Expires");
384 date = soup_message_headers_get (entry->headers, "Date");
385 if (expires && date) {
386 SoupDate *expires_d, *date_d;
387 time_t expires_t, date_t;
389 expires_d = soup_date_new_from_string (expires);
391 date_d = soup_date_new_from_string (date);
393 expires_t = soup_date_to_time_t (expires_d);
394 date_t = soup_date_to_time_t (date_d);
396 soup_date_free (expires_d);
397 soup_date_free (date_d);
399 if (expires_t && date_t) {
400 entry->freshness_lifetime = (guint32) MAX (expires_t - date_t, 0);
404 /* If Expires is not a valid date we should
405 treat it as already expired, see section
407 entry->freshness_lifetime = 0;
412 /* Otherwise an heuristic may be used */
414 /* Heuristics MUST NOT be used with these status codes
416 if (msg->status_code != SOUP_STATUS_OK &&
417 msg->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
418 msg->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
419 msg->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
420 msg->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
421 msg->status_code != SOUP_STATUS_GONE)
424 /* TODO: attach warning 113 if response's current_age is more
425 than 24h (section 2.3.1.1) when using heuristics */
427 /* Last-Modified based heuristic */
428 last_modified = soup_message_headers_get (entry->headers, "Last-Modified");
431 time_t now, last_modified_t;
433 soup_date = soup_date_new_from_string (last_modified);
434 last_modified_t = soup_date_to_time_t (soup_date);
437 #define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
439 entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
440 soup_date_free (soup_date);
446 /* If all else fails, make the entry expire immediately */
447 entry->freshness_lifetime = 0;
450 static SoupCacheEntry *
451 soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
453 SoupCacheEntry *entry;
456 entry = g_slice_new0 (SoupCacheEntry);
457 entry->dirty = FALSE;
458 entry->current_writing_buffer = NULL;
459 entry->got_body = FALSE;
460 entry->being_validated = FALSE;
462 entry->status_code = msg->status_code;
463 entry->response_time = response_time;
464 entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
467 entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
468 copy_end_to_end_headers (msg->response_headers, entry->headers);
473 /* Section 2.3.1, Freshness Lifetime */
474 soup_cache_entry_set_freshness (entry, msg, cache);
476 /* Section 2.3.2, Calculating Age */
477 date = soup_message_headers_get (entry->headers, "Date");
482 time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
484 soup_date = soup_date_new_from_string (date);
485 date_value = soup_date_to_time_t (soup_date);
486 soup_date_free (soup_date);
488 age = soup_message_headers_get (entry->headers, "Age");
490 age_value = g_ascii_strtoll (age, NULL, 10);
492 apparent_age = MAX (0, entry->response_time - date_value);
493 corrected_received_age = MAX (apparent_age, age_value);
494 response_delay = entry->response_time - request_time;
495 entry->corrected_initial_age = corrected_received_age + response_delay;
497 /* Is this correct ? */
498 entry->corrected_initial_age = time (NULL);
505 soup_cache_writing_fixture_free (SoupCacheWritingFixture *fixture)
507 /* Free fixture. And disconnect signals, we don't want to
508 listen to more SoupMessage events as we're finished with
510 if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler))
511 g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler);
512 if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler))
513 g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
514 if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
515 g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
516 g_queue_foreach (fixture->buffer_queue, (GFunc) soup_buffer_free, NULL);
517 g_queue_free (fixture->buffer_queue);
518 g_object_unref (fixture->msg);
519 g_object_unref (fixture->cache);
520 g_slice_free (SoupCacheWritingFixture, fixture);
524 close_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
526 SoupCacheEntry *entry = fixture->entry;
527 SoupCache *cache = fixture->cache;
528 GOutputStream *stream = G_OUTPUT_STREAM (source);
529 goffset content_length;
531 g_warn_if_fail (entry->error == NULL);
533 /* FIXME: what do we do on error ? */
536 g_output_stream_close_finish (stream, result, NULL);
537 g_object_unref (stream);
539 entry->stream = NULL;
541 content_length = soup_message_headers_get_content_length (entry->headers);
543 /* If the process was cancelled, then delete the entry from
544 the cache. Do it also if the size of a chunked resource is
545 too much for the cache */
546 if (g_cancellable_is_cancelled (entry->cancellable)) {
547 entry->dirty = FALSE;
548 soup_cache_entry_remove (cache, entry);
549 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
551 } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) ||
552 entry->length != (gsize) content_length) {
555 * 1. "chunked" data, entry was temporarily added to
556 * cache (as content-length is 0) and now that we have
557 * the actual size we have to evaluate if we want it
558 * in the cache or not
560 * 2. Content-Length has a different value than actual
561 * length, means that the content was encoded for
562 * transmission (typically compressed) and thus we
563 * have to substract the content-length value that was
564 * added to the cache and add the unencoded length
566 gint length_to_add = entry->length - content_length;
568 /* Make room in cache if needed */
569 if (cache_accepts_entries_of_size (cache, length_to_add)) {
570 make_room_for_new_entry (cache, length_to_add);
572 cache->priv->size += length_to_add;
574 entry->dirty = FALSE;
575 soup_cache_entry_remove (cache, entry);
576 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
582 entry->dirty = FALSE;
583 entry->got_body = FALSE;
585 if (entry->current_writing_buffer) {
586 soup_buffer_free (entry->current_writing_buffer);
587 entry->current_writing_buffer = NULL;
590 g_object_unref (entry->cancellable);
591 entry->cancellable = NULL;
594 cache->priv->n_pending--;
597 soup_cache_writing_fixture_free (fixture);
601 write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
603 GOutputStream *stream = G_OUTPUT_STREAM (source);
604 GError *error = NULL;
606 SoupCacheEntry *entry = fixture->entry;
608 if (g_cancellable_is_cancelled (entry->cancellable)) {
609 g_output_stream_close_async (stream,
612 (GAsyncReadyCallback)close_ready_cb,
617 write_size = g_output_stream_write_finish (stream, result, &error);
618 if (write_size <= 0 || error) {
620 entry->error = error;
621 g_output_stream_close_async (stream,
624 (GAsyncReadyCallback)close_ready_cb,
626 /* FIXME: We should completely stop caching the
627 resource at this point */
629 /* Are we still writing and is there new data to write
631 if (fixture->buffer_queue->length > 0)
632 write_next_buffer (entry, fixture);
634 soup_buffer_free (entry->current_writing_buffer);
635 entry->current_writing_buffer = NULL;
637 if (entry->got_body) {
638 /* If we already received 'got-body'
639 and we have written all the data,
640 we can close the stream */
641 g_output_stream_close_async (entry->stream,
644 (GAsyncReadyCallback)close_ready_cb,
652 write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture)
654 SoupBuffer *buffer = g_queue_pop_head (fixture->buffer_queue);
659 /* Free the old buffer */
660 if (entry->current_writing_buffer) {
661 soup_buffer_free (entry->current_writing_buffer);
662 entry->current_writing_buffer = NULL;
664 entry->current_writing_buffer = buffer;
666 g_output_stream_write_async (entry->stream, buffer->data, buffer->length,
667 G_PRIORITY_LOW, entry->cancellable,
668 (GAsyncReadyCallback) write_ready_cb,
674 msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, SoupCacheWritingFixture *fixture)
676 SoupCacheEntry *entry = fixture->entry;
678 /* Ignore this if the writing or appending was cancelled */
679 if (!g_cancellable_is_cancelled (entry->cancellable)) {
680 g_queue_push_tail (fixture->buffer_queue, soup_buffer_copy (chunk));
681 entry->length += chunk->length;
683 if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
684 /* Quickly cancel the caching of the resource */
685 g_cancellable_cancel (entry->cancellable);
689 /* FIXME: remove the error check when we cancel the caching at
690 the first write error */
691 /* Only write if the entry stream is ready */
692 if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
693 write_next_buffer (entry, fixture);
697 msg_got_body_cb (SoupMessage *msg, SoupCacheWritingFixture *fixture)
699 SoupCacheEntry *entry = fixture->entry;
700 g_return_if_fail (entry);
702 entry->got_body = TRUE;
704 if (!entry->stream && fixture->buffer_queue->length > 0)
705 /* The stream is not ready to be written but we still
706 have data to write, we'll write it when the stream
707 is opened for writing */
711 if (fixture->buffer_queue->length > 0) {
712 /* If we still have data to write, write it,
713 write_ready_cb will close the stream */
714 if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
715 write_next_buffer (entry, fixture);
719 if (entry->stream && entry->current_writing_buffer == NULL)
720 g_output_stream_close_async (entry->stream,
723 (GAsyncReadyCallback)close_ready_cb,
728 soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry)
732 /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */
734 g_cancellable_cancel (entry->cancellable);
738 g_assert (!entry->dirty);
739 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
741 if (!g_hash_table_remove (cache->priv->cache, GUINT_TO_POINTER (entry->key)))
744 /* Remove from LRU */
745 lru_item = g_list_find (cache->priv->lru_start, entry);
746 cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
748 /* Adjust cache size */
749 cache->priv->size -= entry->length;
751 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
757 lru_compare_func (gconstpointer a, gconstpointer b)
759 SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
760 SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
762 /* The rationale of this sorting func is
764 * 1. sort by hits -> LRU algorithm, then
766 * 2. sort by freshness lifetime, we better discard first
767 * entries that are close to expire
769 * 3. sort by size, replace first small size resources as they
770 * are cheaper to download
774 if (entry_a->hits != entry_b->hits)
775 return entry_a->hits - entry_b->hits;
777 /* Sort by freshness_lifetime */
778 if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
779 return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
782 return entry_a->length - entry_b->length;
786 cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
788 /* We could add here some more heuristics. TODO: review how
789 this is done by other HTTP caches */
791 return length_to_add <= cache->priv->max_entry_data_size;
795 make_room_for_new_entry (SoupCache *cache, guint length_to_add)
797 GList *lru_entry = cache->priv->lru_start;
799 /* Check that there is enough room for the new entry. This is
800 an approximation as we're not working out the size of the
801 cache file or the size of the headers for performance
802 reasons. TODO: check if that would be really that expensive */
805 (length_to_add + cache->priv->size > cache->priv->max_size)) {
806 SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
808 /* Discard entries. Once cancelled resources will be
809 * freed in close_ready_cb
811 if (soup_cache_entry_remove (cache, old_entry)) {
812 soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry));
813 lru_entry = cache->priv->lru_start;
815 lru_entry = g_list_next (lru_entry);
820 soup_cache_entry_insert (SoupCache *cache,
821 SoupCacheEntry *entry,
824 guint length_to_add = 0;
825 SoupCacheEntry *old_entry;
828 entry->key = get_cache_key_from_uri ((const char *) entry->uri);
830 if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED)
831 length_to_add = soup_message_headers_get_content_length (entry->headers);
833 /* Check if we are going to store the resource depending on its size */
835 if (!cache_accepts_entries_of_size (cache, length_to_add))
838 /* Make room for new entry if needed */
839 make_room_for_new_entry (cache, length_to_add);
842 /* Remove any previous entry */
843 if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
844 if (soup_cache_entry_remove (cache, old_entry))
845 soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry));
850 /* Add to hash table */
851 g_hash_table_insert (cache->priv->cache, GUINT_TO_POINTER (entry->key), entry);
853 /* Compute new cache size */
854 cache->priv->size += length_to_add;
858 cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
860 cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
862 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
867 static SoupCacheEntry*
868 soup_cache_entry_lookup (SoupCache *cache,
871 SoupCacheEntry *entry;
875 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
876 key = get_cache_key_from_uri ((const char *) uri);
878 entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (key));
880 if (entry != NULL && (strcmp (entry->uri, uri) != 0))
888 msg_restarted_cb (SoupMessage *msg, SoupCacheEntry *entry)
890 /* FIXME: What should we do here exactly? */
894 replace_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture)
896 SoupCacheEntry *entry = fixture->entry;
897 GOutputStream *stream = (GOutputStream *) g_file_replace_finish (G_FILE (source),
898 result, &entry->error);
900 if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) {
902 g_object_unref (stream);
903 fixture->cache->priv->n_pending--;
904 entry->dirty = FALSE;
905 soup_cache_entry_remove (fixture->cache, entry);
906 soup_cache_entry_free (entry, get_file_from_entry (fixture->cache, entry));
907 soup_cache_writing_fixture_free (fixture);
911 entry->stream = stream;
913 /* If we already got all the data we have to initiate the
914 * writing here, since we won't get more 'got-chunk'
917 if (!entry->got_body)
920 /* It could happen that reading the data from server
921 * was completed before this happens. In that case
924 if (!write_next_buffer (entry, fixture))
925 /* Could happen if the resource is empty */
926 g_output_stream_close_async (stream, G_PRIORITY_LOW, entry->cancellable,
927 (GAsyncReadyCallback) close_ready_cb,
933 SoupSessionFeature *feature;
934 gulong got_headers_handler;
938 msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
941 SoupCacheability cacheable;
942 RequestHelper *helper;
943 time_t request_time, response_time;
944 SoupCacheEntry *entry;
946 response_time = time (NULL);
948 helper = (RequestHelper *)user_data;
949 cache = SOUP_CACHE (helper->feature);
950 request_time = helper->request_time;
951 g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
952 g_slice_free (RequestHelper, helper);
954 cacheable = soup_cache_get_cacheability (cache, msg);
956 if (cacheable & SOUP_CACHE_CACHEABLE) {
958 SoupCacheWritingFixture *fixture;
960 /* Check if we are already caching this resource */
961 entry = soup_cache_entry_lookup (cache, msg);
963 if (entry && entry->dirty)
966 /* Create a new entry, deleting any old one if present */
968 soup_cache_entry_remove (cache, entry);
969 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
972 entry = soup_cache_entry_new (cache, msg, request_time, response_time);
975 /* Do not continue if it can not be stored */
976 if (!soup_cache_entry_insert (cache, entry, TRUE)) {
977 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
981 fixture = g_slice_new0 (SoupCacheWritingFixture);
982 fixture->cache = g_object_ref (cache);
983 fixture->entry = entry;
984 fixture->msg = g_object_ref (msg);
985 fixture->buffer_queue = g_queue_new ();
987 /* We connect now to these signals and buffer the data
988 if it comes before the file is ready for writing */
989 fixture->got_chunk_handler =
990 g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture);
991 fixture->got_body_handler =
992 g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture);
993 fixture->restarted_handler =
994 g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry);
997 cache->priv->n_pending++;
1000 entry->cancellable = g_cancellable_new ();
1001 file = get_file_from_entry (cache, entry);
1002 g_file_replace_async (file, NULL, FALSE,
1003 G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
1004 G_PRIORITY_LOW, entry->cancellable,
1005 (GAsyncReadyCallback) replace_cb, fixture);
1006 g_object_unref (file);
1007 } else if (cacheable & SOUP_CACHE_INVALIDATES) {
1008 entry = soup_cache_entry_lookup (cache, msg);
1011 if (soup_cache_entry_remove (cache, entry))
1012 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
1014 } else if (cacheable & SOUP_CACHE_VALIDATES) {
1015 entry = soup_cache_entry_lookup (cache, msg);
1017 /* It's possible to get a CACHE_VALIDATES with no
1018 * entry in the hash table. This could happen if for
1019 * example the soup client is the one creating the
1020 * conditional request.
1023 entry->being_validated = FALSE;
1024 copy_end_to_end_headers (msg->response_headers, entry->headers);
1025 soup_cache_entry_set_freshness (entry, msg, cache);
1031 soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
1033 SoupCacheEntry *entry;
1035 GInputStream *stream = NULL;
1038 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1039 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
1041 entry = soup_cache_entry_lookup (cache, msg);
1042 g_return_val_if_fail (entry, NULL);
1044 /* TODO: the original idea was to save reads, but current code
1045 assumes that a stream is always returned. Need to reach
1046 some agreement here. Also we have to handle the situation
1047 were the file was no longer there (for example files
1048 removed without notifying the cache */
1049 file = get_file_from_entry (cache, entry);
1050 stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
1051 g_object_unref (file);
1053 /* Do not change the original message if there is no resource */
1057 /* If we are told to send a response from cache any validation
1058 in course is over by now */
1059 entry->being_validated = FALSE;
1062 soup_message_set_status (msg, entry->status_code);
1065 copy_end_to_end_headers (entry->headers, msg->response_headers);
1067 /* Add 'Age' header with the current age */
1068 current_age = g_strdup_printf ("%d", soup_cache_entry_get_current_age (entry));
1069 soup_message_headers_replace (msg->response_headers,
1072 g_free (current_age);
1078 request_started (SoupSessionFeature *feature, SoupSession *session,
1079 SoupMessage *msg, SoupSocket *socket)
1081 RequestHelper *helper = g_slice_new0 (RequestHelper);
1082 helper->request_time = time (NULL);
1083 helper->feature = feature;
1084 helper->got_headers_handler = g_signal_connect (msg, "got-headers",
1085 G_CALLBACK (msg_got_headers_cb),
1090 attach (SoupSessionFeature *feature, SoupSession *session)
1092 SoupCache *cache = SOUP_CACHE (feature);
1093 cache->priv->session = session;
1095 soup_cache_default_feature_interface->attach (feature, session);
1099 soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
1100 gpointer interface_data)
1102 soup_cache_default_feature_interface =
1103 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
1105 feature_interface->attach = attach;
1106 feature_interface->request_started = request_started;
1110 soup_cache_init (SoupCache *cache)
1112 SoupCachePrivate *priv;
1114 priv = cache->priv = SOUP_CACHE_GET_PRIVATE (cache);
1116 priv->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
1118 priv->lru_start = NULL;
1121 priv->n_pending = 0;
1124 priv->max_size = DEFAULT_MAX_SIZE;
1125 priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1130 remove_cache_item (gpointer data,
1133 SoupCache *cache = (SoupCache *) user_data;
1134 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1136 if (soup_cache_entry_remove (cache, entry))
1137 soup_cache_entry_free (entry, NULL);
1141 soup_cache_finalize (GObject *object)
1143 SoupCachePrivate *priv;
1146 priv = SOUP_CACHE (object)->priv;
1148 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1149 entries = g_hash_table_get_values (priv->cache);
1150 g_list_foreach (entries, remove_cache_item, object);
1151 g_list_free (entries);
1153 g_hash_table_destroy (priv->cache);
1154 g_free (priv->cache_dir);
1156 g_list_free (priv->lru_start);
1157 priv->lru_start = NULL;
1159 G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
1163 soup_cache_set_property (GObject *object, guint prop_id,
1164 const GValue *value, GParamSpec *pspec)
1166 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1169 case PROP_CACHE_DIR:
1170 priv->cache_dir = g_value_dup_string (value);
1171 /* Create directory if it does not exist (FIXME: should we?) */
1172 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1173 g_mkdir_with_parents (priv->cache_dir, 0700);
1175 case PROP_CACHE_TYPE:
1176 priv->cache_type = g_value_get_enum (value);
1177 /* TODO: clear private entries and issue a warning if moving to shared? */
1180 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1186 soup_cache_get_property (GObject *object, guint prop_id,
1187 GValue *value, GParamSpec *pspec)
1189 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1192 case PROP_CACHE_DIR:
1193 g_value_set_string (value, priv->cache_dir);
1195 case PROP_CACHE_TYPE:
1196 g_value_set_enum (value, priv->cache_type);
1199 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1205 soup_cache_constructed (GObject *object)
1207 SoupCachePrivate *priv;
1209 priv = SOUP_CACHE (object)->priv;
1211 if (!priv->cache_dir) {
1212 /* Set a default cache dir, different for each user */
1213 priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
1216 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1217 g_mkdir_with_parents (priv->cache_dir, 0700);
1220 if (G_OBJECT_CLASS (soup_cache_parent_class)->constructed)
1221 G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
1225 soup_cache_class_init (SoupCacheClass *cache_class)
1227 GObjectClass *gobject_class = (GObjectClass *)cache_class;
1229 gobject_class->finalize = soup_cache_finalize;
1230 gobject_class->constructed = soup_cache_constructed;
1231 gobject_class->set_property = soup_cache_set_property;
1232 gobject_class->get_property = soup_cache_get_property;
1234 cache_class->get_cacheability = get_cacheability;
1236 g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
1237 g_param_spec_string ("cache-dir",
1239 "The directory to store the cache files",
1241 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1243 g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
1244 g_param_spec_enum ("cache-type",
1246 "Whether the cache is private or shared",
1247 SOUP_TYPE_CACHE_TYPE,
1248 SOUP_CACHE_SINGLE_USER,
1249 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1251 g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
1256 * @cache_dir: the directory to store the cached data, or %NULL to use the default one
1257 * @cache_type: the #SoupCacheType of the cache
1259 * Creates a new #SoupCache.
1261 * Returns: a new #SoupCache
1266 soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1268 return g_object_new (SOUP_TYPE_CACHE,
1269 "cache-dir", cache_dir,
1270 "cache-type", cache_type,
1275 * soup_cache_has_response:
1276 * @cache: a #SoupCache
1277 * @msg: a #SoupMessage
1279 * This function calculates whether the @cache object has a proper
1280 * response for the request @msg given the flags both in the request
1281 * and the cached reply and the time ellapsed since it was cached.
1283 * Returns: whether or not the @cache has a valid response for @msg
1288 soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1290 SoupCacheEntry *entry;
1291 const char *cache_control, *pragma;
1293 int max_age, max_stale, min_fresh;
1294 GList *lru_item, *item;
1296 entry = soup_cache_entry_lookup (cache, msg);
1298 /* 1. The presented Request-URI and that of stored response
1302 return SOUP_CACHE_RESPONSE_STALE;
1304 /* Increase hit count. Take sorting into account */
1306 lru_item = g_list_find (cache->priv->lru_start, entry);
1308 while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1309 item = g_list_next (item);
1311 if (item != lru_item) {
1312 cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
1313 item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1314 g_list_free (lru_item);
1317 if (entry->dirty || entry->being_validated)
1318 return SOUP_CACHE_RESPONSE_STALE;
1320 /* 2. The request method associated with the stored response
1321 * allows it to be used for the presented request
1324 /* In practice this means we only return our resource for GET,
1325 * cacheability for other methods is a TODO in the RFC
1326 * (TODO: although we could return the headers for HEAD
1329 if (msg->method != SOUP_METHOD_GET)
1330 return SOUP_CACHE_RESPONSE_STALE;
1332 /* 3. Selecting request-headers nominated by the stored
1333 * response (if any) match those presented.
1338 /* 4. The request is a conditional request issued by the client.
1340 if (soup_message_headers_get (msg->request_headers, "If-Modified-Since") ||
1341 soup_message_headers_get (msg->request_headers, "If-None-Match"))
1342 return SOUP_CACHE_RESPONSE_STALE;
1344 /* 5. The presented request and stored response are free from
1345 * directives that would prevent its use.
1348 max_age = max_stale = min_fresh = -1;
1350 /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1352 pragma = soup_message_headers_get (msg->request_headers, "Pragma");
1353 if (pragma && soup_header_contains (pragma, "no-cache"))
1354 return SOUP_CACHE_RESPONSE_STALE;
1356 cache_control = soup_message_headers_get (msg->request_headers, "Cache-Control");
1357 if (cache_control) {
1358 GHashTable *hash = soup_header_parse_param_list (cache_control);
1360 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1361 soup_header_free_param_list (hash);
1362 return SOUP_CACHE_RESPONSE_STALE;
1365 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1366 soup_header_free_param_list (hash);
1367 return SOUP_CACHE_RESPONSE_STALE;
1370 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
1371 max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1372 /* Forcing cache revalidaton
1375 soup_header_free_param_list (hash);
1376 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1380 /* max-stale can have no value set, we need to use _extended */
1381 if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1383 max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1385 max_stale = G_MAXINT32;
1388 value = g_hash_table_lookup (hash, "min-fresh");
1390 min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1392 soup_header_free_param_list (hash);
1395 guint current_age = soup_cache_entry_get_current_age (entry);
1397 /* If we are over max-age and max-stale is not
1398 set, do not use the value from the cache
1399 without validation */
1400 if ((guint) max_age <= current_age && max_stale == -1)
1401 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1405 /* 6. The stored response is either: fresh, allowed to be
1406 * served stale or succesfully validated
1408 /* TODO consider also proxy-revalidate & s-maxage */
1409 if (entry->must_revalidate)
1410 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1412 if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1413 /* Not fresh, can it be served stale? */
1414 if (max_stale != -1) {
1415 /* G_MAXINT32 means we accept any staleness */
1416 if (max_stale == G_MAXINT32)
1417 return SOUP_CACHE_RESPONSE_FRESH;
1419 if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1420 return SOUP_CACHE_RESPONSE_FRESH;
1423 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1426 return SOUP_CACHE_RESPONSE_FRESH;
1430 * soup_cache_get_cacheability:
1431 * @cache: a #SoupCache
1432 * @msg: a #SoupMessage
1434 * Calculates whether the @msg can be cached or not.
1436 * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
1441 soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1443 g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1444 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1446 return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1450 force_flush_timeout (gpointer data)
1452 gboolean *forced = (gboolean *)data;
1460 * @cache: a #SoupCache
1462 * This function will force all pending writes in the @cache to be
1463 * committed to disk. For doing so it will iterate the #GMainContext
1464 * associated with @cache's session as long as needed.
1469 soup_cache_flush (SoupCache *cache)
1471 GMainContext *async_context;
1472 SoupSession *session;
1474 gboolean forced = FALSE;
1476 g_return_if_fail (SOUP_IS_CACHE (cache));
1478 session = cache->priv->session;
1479 g_return_if_fail (SOUP_IS_SESSION (session));
1480 async_context = soup_session_get_async_context (session);
1482 /* We give cache 10 secs to finish */
1483 timeout = soup_add_timeout (async_context, 10000, force_flush_timeout, &forced);
1485 while (!forced && cache->priv->n_pending > 0)
1486 g_main_context_iteration (async_context, FALSE);
1489 g_source_destroy (timeout);
1491 g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
1495 clear_cache_item (gpointer data,
1498 SoupCache *cache = (SoupCache *) user_data;
1499 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1501 if (soup_cache_entry_remove (cache, entry))
1502 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
1507 * @cache: a #SoupCache
1509 * Will remove all entries in the @cache plus all the cache files
1510 * associated with them.
1515 soup_cache_clear (SoupCache *cache)
1519 g_return_if_fail (SOUP_IS_CACHE (cache));
1520 g_return_if_fail (cache->priv->cache);
1522 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1523 entries = g_hash_table_get_values (cache->priv->cache);
1524 g_list_foreach (entries, clear_cache_item, cache);
1525 g_list_free (entries);
1529 soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1533 SoupCacheEntry *entry;
1536 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1537 g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1539 /* First copy the data we need from the original message */
1540 uri = soup_message_get_uri (original);
1541 msg = soup_message_new_from_uri (original->method, uri);
1543 soup_message_headers_foreach (original->request_headers,
1544 (SoupMessageHeadersForeachFunc)copy_headers,
1545 msg->request_headers);
1547 /* Now add the validator entries in the header from the cached
1549 entry = soup_cache_entry_lookup (cache, original);
1550 g_return_val_if_fail (entry, NULL);
1552 entry->being_validated = TRUE;
1554 value = soup_message_headers_get (entry->headers, "Last-Modified");
1556 soup_message_headers_append (msg->request_headers,
1557 "If-Modified-Since",
1559 value = soup_message_headers_get (entry->headers, "ETag");
1561 soup_message_headers_append (msg->request_headers,
1567 #define OLD_SOUP_CACHE_FILE "soup.cache"
1568 #define SOUP_CACHE_FILE "soup.cache2"
1570 #define SOUP_CACHE_HEADERS_FORMAT "{ss}"
1571 #define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
1572 #define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
1574 /* Basically the same format than above except that some strings are
1575 prepended with &. This way the GVariant returns a pointer to the
1576 data instead of duplicating the string */
1577 #define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
1580 pack_entry (gpointer data,
1583 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1584 SoupMessageHeadersIter iter;
1585 const char *header_key, *header_value;
1586 GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1588 /* Do not store non-consolidated entries */
1589 if (entry->dirty || entry->current_writing_buffer != NULL || !entry->key)
1592 g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
1593 #if ENABLE(TIZEN_FIX_PACK_ENTRY)
1594 if (!g_utf8_validate (entry->uri, -1, NULL)) {
1595 g_variant_builder_close (entries_builder);
1599 g_variant_builder_add (entries_builder, "s", entry->uri);
1600 g_variant_builder_add (entries_builder, "b", entry->must_revalidate);
1601 g_variant_builder_add (entries_builder, "u", entry->freshness_lifetime);
1602 g_variant_builder_add (entries_builder, "u", entry->corrected_initial_age);
1603 g_variant_builder_add (entries_builder, "u", entry->response_time);
1604 g_variant_builder_add (entries_builder, "u", entry->hits);
1605 g_variant_builder_add (entries_builder, "u", entry->length);
1606 g_variant_builder_add (entries_builder, "q", entry->status_code);
1609 g_variant_builder_open (entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_HEADERS_FORMAT));
1610 soup_message_headers_iter_init (&iter, entry->headers);
1611 while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1612 if (g_utf8_validate (header_value, -1, NULL))
1613 g_variant_builder_add (entries_builder, SOUP_CACHE_HEADERS_FORMAT,
1614 header_key, header_value);
1616 g_variant_builder_close (entries_builder); /* "a" SOUP_CACHE_HEADERS_FORMAT */
1617 g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
1621 soup_cache_dump (SoupCache *cache)
1623 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
1625 GVariantBuilder entries_builder;
1626 GVariant *cache_variant;
1628 if (!g_list_length (cache->priv->lru_start))
1631 /* Create the builder and iterate over all entries */
1632 g_variant_builder_init (&entries_builder, G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT));
1633 g_variant_builder_add (&entries_builder, "q", SOUP_CACHE_CURRENT_VERSION);
1634 g_variant_builder_open (&entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_PHEADERS_FORMAT));
1635 g_list_foreach (cache->priv->lru_start, pack_entry, &entries_builder);
1636 g_variant_builder_close (&entries_builder);
1638 /* Serialize and dump */
1639 cache_variant = g_variant_builder_end (&entries_builder);
1640 g_variant_ref_sink (cache_variant);
1641 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1642 g_file_set_contents (filename, (const char *) g_variant_get_data (cache_variant),
1643 g_variant_get_size (cache_variant), NULL);
1645 g_variant_unref (cache_variant);
1649 clear_cache_files (SoupCache *cache)
1651 GFileInfo *file_info;
1652 GFileEnumerator *file_enumerator;
1653 GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
1655 file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
1656 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1657 if (file_enumerator) {
1658 while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
1659 const char *filename = g_file_info_get_name (file_info);
1661 if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
1662 GFile *cache_file = g_file_get_child (cache_dir_file, filename);
1663 g_file_delete (cache_file, NULL, NULL);
1664 g_object_unref (cache_file);
1667 g_object_unref (file_enumerator);
1669 g_object_unref (cache_dir_file);
1673 soup_cache_load (SoupCache *cache)
1675 gboolean must_revalidate;
1676 guint32 freshness_lifetime, hits;
1677 guint32 corrected_initial_age, response_time;
1678 char *url, *filename = NULL, *contents = NULL;
1679 GVariant *cache_variant;
1680 GVariantIter *entries_iter = NULL, *headers_iter = NULL;
1682 SoupCacheEntry *entry;
1683 SoupCachePrivate *priv = cache->priv;
1684 guint16 version, status_code;
1686 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1687 if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1690 clear_cache_files (cache);
1695 cache_variant = g_variant_new_from_data (G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT),
1696 (const char *) contents, length, FALSE, g_free, contents);
1697 g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &version, &entries_iter);
1698 if (version != SOUP_CACHE_CURRENT_VERSION) {
1699 g_variant_iter_free (entries_iter);
1700 g_variant_unref (cache_variant);
1701 clear_cache_files (cache);
1705 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1706 &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1707 &response_time, &hits, &length, &status_code,
1709 const char *header_key, *header_value;
1710 SoupMessageHeaders *headers;
1711 SoupMessageHeadersIter soup_headers_iter;
1713 /* SoupMessage Headers */
1714 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1715 while (g_variant_iter_loop (headers_iter, SOUP_CACHE_HEADERS_FORMAT, &header_key, &header_value))
1716 if (*header_key && *header_value)
1717 soup_message_headers_append (headers, header_key, header_value);
1719 /* Check that we have headers */
1720 soup_message_headers_iter_init (&soup_headers_iter, headers);
1721 if (!soup_message_headers_iter_next (&soup_headers_iter, &header_key, &header_value)) {
1722 soup_message_headers_free (headers);
1726 /* Insert in cache */
1727 entry = g_slice_new0 (SoupCacheEntry);
1728 entry->uri = g_strdup (url);
1729 entry->must_revalidate = must_revalidate;
1730 entry->freshness_lifetime = freshness_lifetime;
1731 entry->corrected_initial_age = corrected_initial_age;
1732 entry->response_time = response_time;
1734 entry->length = length;
1735 entry->headers = headers;
1736 entry->status_code = status_code;
1738 if (!soup_cache_entry_insert (cache, entry, FALSE))
1739 soup_cache_entry_free (entry, get_file_from_entry (cache, entry));
1742 cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
1745 g_variant_iter_free (entries_iter);
1746 g_variant_unref (cache_variant);
1750 soup_cache_set_max_size (SoupCache *cache,
1753 cache->priv->max_size = max_size;
1754 cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1758 soup_cache_get_max_size (SoupCache *cache)
1760 return cache->priv->max_size;